diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminFillSearchTermActionGroup.xml b/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminFillSearchTermActionGroup.xml new file mode 100644 index 0000000000000..e402ec0391c0a --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminFillSearchTermActionGroup.xml @@ -0,0 +1,27 @@ + + + + + + + Fills the search terms form with sample data. + + + + + + + + + + + + + + diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminOpenNewSearchTermsPageActionGroup.xml b/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminOpenNewSearchTermsPageActionGroup.xml new file mode 100644 index 0000000000000..2fbbab8a55721 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminOpenNewSearchTermsPageActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + Navigate to search terms form page. + + + + + diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminSaveSearchTermActionGroup.xml b/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminSaveSearchTermActionGroup.xml new file mode 100644 index 0000000000000..cac996d97a6bf --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Mftf/ActionGroup/AdminSaveSearchTermActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + Save a new search term from Magento admin. + + + + + diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/Data/SearchTermsData.xml b/app/code/Magento/AdvancedSearch/Test/Mftf/Data/SearchTermsData.xml new file mode 100644 index 0000000000000..913784afe66ea --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Mftf/Data/SearchTermsData.xml @@ -0,0 +1,17 @@ + + + + + + books + Default Store View + http://sample.com + 1 + + diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/Page/AdminSearchTermsFormPage.xml b/app/code/Magento/AdvancedSearch/Test/Mftf/Page/AdminSearchTermsFormPage.xml new file mode 100644 index 0000000000000..b8997093de840 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Mftf/Page/AdminSearchTermsFormPage.xml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/Section/AdminSearchTermsPageFormFieldsSection.xml b/app/code/Magento/AdvancedSearch/Test/Mftf/Section/AdminSearchTermsPageFormFieldsSection.xml new file mode 100644 index 0000000000000..ba593b332bf8a --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Mftf/Section/AdminSearchTermsPageFormFieldsSection.xml @@ -0,0 +1,17 @@ + + + + +
+ + + + +
+
diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/Test/AdminAddSearchTermTest.xml b/app/code/Magento/AdvancedSearch/Test/Mftf/Test/AdminAddSearchTermTest.xml new file mode 100644 index 0000000000000..88c04263a1bc5 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Mftf/Test/AdminAddSearchTermTest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + <description value="Admin should be able to create a new search term using search terms grid"/> + <severity value="CRITICAL"/> + <group value="AdvancedSearch"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + <actionGroup ref="AdminOpenNewSearchTermsPageActionGroup" stepKey="navigateToSearchTermPage"/> + <actionGroup ref="AdminFillSearchTermActionGroup" stepKey="fillNewSearchTermData"> + <argument name="searchQuery" value="{{SearchTerms.searchQuery}}"/> + <argument name="store" value="{{SearchTerms.store}}"/> + <argument name="redirectUrl" value="{{SearchTerms.redirectUrl}}"/> + <argument name="suggestedTerms" value="{{SearchTerms.suggestedTerms}}"/> + </actionGroup> + <actionGroup ref="AdminSaveSearchTermActionGroup" stepKey="saveSearchTerm"/> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertSaveSearchTermSuccessMessage"> + <argument name="message" value="You saved the search term."/> + <argument name="messageType" value="success"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml index 1f0ce1824fead..f669c4b9f3ca4 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml @@ -14,5 +14,6 @@ </annotations> <waitForElementVisible selector="{{AdminHeaderSection.adminUserAccountText}}" stepKey="waitForAdminAccountTextVisible"/> + <seeElement selector="{{AdminHeaderSection.adminUserAccountText}}" stepKey="assertAdminAccountTextElement"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Data/GeneralLocalConfigsData.xml b/app/code/Magento/Backend/Test/Mftf/Data/GeneralLocalConfigsData.xml index 22d595c39407f..0477befccc06e 100644 --- a/app/code/Magento/Backend/Test/Mftf/Data/GeneralLocalConfigsData.xml +++ b/app/code/Magento/Backend/Test/Mftf/Data/GeneralLocalConfigsData.xml @@ -20,4 +20,10 @@ <data key="scope_code">base</data> <data key="value">en_US</data> </entity> + <entity name="GeneralLocalCodeConfigsForMexico"> + <data key="path">general/locale/code</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + <data key="value">es_MX</data> + </entity> </entities> diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php index 51b49dcb9e48a..c6867de8040d8 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php +++ b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php @@ -111,6 +111,6 @@ public function getViewHeader(string $viewName): string public function getDropViewSql(string $viewName): string { $quotedViewName = $this->getConnection()->quoteIdentifier($viewName); - return sprintf('DROP VIEW IF EXISTS %s;\n', $quotedViewName); + return sprintf("DROP VIEW IF EXISTS %s;\n", $quotedViewName); } } diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/AdminBackupDeleteActionGroup.xml similarity index 96% rename from app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml rename to app/code/Magento/Backup/Test/Mftf/ActionGroup/AdminBackupDeleteActionGroup.xml index 9a7c2005d7ed1..a5ecc44509604 100644 --- a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml +++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/AdminBackupDeleteActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="deleteBackup"> + <actionGroup name="AdminBackupDeleteActionGroup"> <annotations> <description>Deletes a Backup using provided Backup Entity.</description> </annotations> diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/AdminBackupIndexPageOpenActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/AdminBackupIndexPageOpenActionGroup.xml new file mode 100644 index 0000000000000..45432161ceab3 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/AdminBackupIndexPageOpenActionGroup.xml @@ -0,0 +1,14 @@ +<?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="AdminBackupIndexPageOpenActionGroup"> + <amOnPage url="{{AdminBackupIndexPage.url}}" stepKey="navigateToBackupIndexPage"/> + <waitForPageLoad stepKey="waitForBackupIndexPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/deleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/deleteBackupActionGroup.xml new file mode 100644 index 0000000000000..b879a2aa9647a --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/deleteBackupActionGroup.xml @@ -0,0 +1,12 @@ +<?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="deleteBackup" extends="AdminBackupDeleteActionGroup" deprecated="Use DeleteBackupActionGroup"/> +</actionGroups> diff --git a/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml b/app/code/Magento/Backup/Test/Mftf/Page/AdminBackupIndexPage.xml similarity index 82% rename from app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml rename to app/code/Magento/Backup/Test/Mftf/Page/AdminBackupIndexPage.xml index aad29e6e6d566..f21435bc775a5 100644 --- a/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml +++ b/app/code/Magento/Backup/Test/Mftf/Page/AdminBackupIndexPage.xml @@ -8,7 +8,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> - <page name="BackupIndexPage" url="/backup/index/" area="admin" module="Magento_Backup"> + <page name="AdminBackupIndexPage" url="/backup/index/" area="admin" module="Magento_Backup"> <section name="AdminMainActionsSection"/> <section name="AdminGridTableSection"/> <section name="AdminCreateBackupFormSection"/> diff --git a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml index 383c1122ee07f..6db3aa7cbded9 100644 --- a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml +++ b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml @@ -26,8 +26,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!--Go to backup index page--> - <amOnPage url="{{BackupIndexPage.url}}" stepKey="goToBackupPage"/> - <waitForPageLoad stepKey="waitForBackupPage"/> + <actionGroup ref="AdminBackupIndexPageOpenActionGroup" stepKey="navigateToBackupPage"/> <!--Create system backup--> <actionGroup ref="CreateSystemBackupActionGroup" stepKey="createSystemBackup"/> @@ -39,17 +38,17 @@ <actionGroup ref="CreateDatabaseBackupActionGroup" stepKey="createDatabaseBackup"/> <!--Delete system backup--> - <actionGroup ref="deleteBackup" stepKey="deleteSystemBackup"> + <actionGroup ref="AdminBackupDeleteActionGroup" stepKey="deleteSystemBackup"> <argument name="backup" value="SystemBackup"/> </actionGroup> <!--Delete database/media backup--> - <actionGroup ref="deleteBackup" stepKey="deleteMediaBackup"> + <actionGroup ref="AdminBackupDeleteActionGroup" stepKey="deleteMediaBackup"> <argument name="backup" value="MediaBackup"/> </actionGroup> <!--Delete database backup--> - <actionGroup ref="deleteBackup" stepKey="deleteDatabaseBackup"> + <actionGroup ref="AdminBackupDeleteActionGroup" stepKey="deleteDatabaseBackup"> <argument name="backup" value="DatabaseBackup"/> </actionGroup> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml deleted file mode 100644 index 3caa2c9681f55..0000000000000 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml +++ /dev/null @@ -1,70 +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="AdminAddDefaultVideoBundleProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> - <annotations> - <features value="Bundle"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to add default video for a Bundle Product"/> - <description value="Admin should be able to add default video for a Bundle Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-110"/> - <group value="Bundle"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> - <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> - <magentoCron stepKey="runCronIndex" groups="index"/> - </before> - <after> - <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> - </after> - - <!-- Create a bundle product --> - <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - <actionGroup ref="FillProductNameAndSkuInProductFormActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - - <!-- Add two bundle items --> - <scrollTo selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> - <conditionalClick selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" dependentSelector="{{AdminProductFormBundleSection.bundleItemsToggle}}" visible="false" stepKey="openBundleSection" after="scrollToSection"/> - <click selector="{{AdminProductFormBundleSection.addOption}}" stepKey="clickAddOption" after="openBundleSection"/> - <waitForElementVisible selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" stepKey="waitForBundleTitle" after="clickAddOption"/> - <fillField selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" userInput="{{BundleProduct.optionTitle1}}" stepKey="fillBundleTitle" after="waitForBundleTitle"/> - <selectOption selector="{{AdminProductFormBundleSection.bundleOptionXInputType('0')}}" userInput="{{BundleProduct.optionInputType1}}" stepKey="selectOptionBundleTitle" after="fillBundleTitle"/> - <waitForElementVisible selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="waitForAddProducts" after="selectOptionBundleTitle"/> - <click selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="clickAddProducts" after="waitForAddProducts"/> - <waitForPageLoad stepKey="waitForPageLoad" after="clickAddProducts"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku1" after="waitForPageLoad"> - <argument name="product" value="$$simpleProduct1$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku2" after="checkOption1"> - <argument name="product" value="$$simpleProduct2$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> - <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="addProducts" after="checkOption2"/> - <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty1" after="addProducts"/> - <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty2" before="saveProductForm"/> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml deleted file mode 100644 index 06ddd43d0e2cc..0000000000000 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml +++ /dev/null @@ -1,70 +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="AdminRemoveDefaultVideoBundleProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> - <annotations> - <features value="Bundle"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to remove default video from a Bundle Product"/> - <description value="Admin should be able to remove default video from a Bundle Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-205"/> - <group value="Bundle"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> - <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> - <magentoCron stepKey="runCronIndex" groups="index"/> - </before> - <after> - <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> - </after> - - <!-- Create a bundle product --> - <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - <actionGroup ref="FillProductNameAndSkuInProductFormActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - - <!-- Add two bundle items --> - <scrollTo selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> - <conditionalClick selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" dependentSelector="{{AdminProductFormBundleSection.bundleItemsToggle}}" visible="false" stepKey="openBundleSection" after="scrollToSection"/> - <click selector="{{AdminProductFormBundleSection.addOption}}" stepKey="clickAddOption" after="openBundleSection"/> - <waitForElementVisible selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" stepKey="waitForBundleTitle" after="clickAddOption"/> - <fillField selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" userInput="{{BundleProduct.optionTitle1}}" stepKey="fillBundleTitle" after="waitForBundleTitle"/> - <selectOption selector="{{AdminProductFormBundleSection.bundleOptionXInputType('0')}}" userInput="{{BundleProduct.optionInputType1}}" stepKey="selectOptionBundleTitle" after="fillBundleTitle"/> - <waitForElementVisible selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="waitForAddProducts" after="selectOptionBundleTitle"/> - <click selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="clickAddProducts" after="waitForAddProducts"/> - <waitForPageLoad stepKey="waitForPageLoad" after="clickAddProducts"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku1" after="waitForPageLoad"> - <argument name="product" value="$$simpleProduct1$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku2" after="checkOption1"> - <argument name="product" value="$$simpleProduct2$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> - <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="addProducts" after="checkOption2"/> - <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty1" after="addProducts"/> - <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty2" before="saveProductForm"/> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml index a37ec0cffbbb8..8e8df1f4f16f0 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml @@ -80,8 +80,7 @@ <waitForPageLoad stepKey="waitForElementAdded"/> <!-- Go to the shopping cart page and grab the value of the option title --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="onPageShoppingCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="onPageShoppingCart"/> <grabTextFrom selector="{{CheckoutCartProductSection.nthBundleOptionName('1')}}" stepKey="grabTotalBefore"/> <!-- Find the product that we just created using the product grid --> @@ -101,8 +100,7 @@ <see stepKey="assertSuccess2" selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product."/> <!-- Go to the shopping cart page and make sure the title has changed --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="onPageShoppingCart1"/> - <waitForPageLoad stepKey="waitForCartPageLoad1"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="onPageShoppingCart1"/> <grabTextFrom selector="{{CheckoutCartProductSection.nthBundleOptionName('1')}}" stepKey="grabTotalAfter"/> <assertNotEquals stepKey="assertNotEquals"> <actualResult type="string">{$grabTotalAfter}</actualResult> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml index 85be636ec269c..e6f8834336683 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml @@ -100,8 +100,7 @@ <see selector="{{StorefrontMessagesSection.success}}" userInput="You added {{BundleProduct.name}} to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> <!-- Verify cart contents --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCart"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('1')}}" userInput="Option One" stepKey="seeOption1"/> <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('2')}}" userInput="Option Two" stepKey="seeOption2"/> <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('3')}}" userInput="Option Three" stepKey="seeOption3"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml index 3082e467ec734..161d308044b4a 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml @@ -89,8 +89,7 @@ <waitForPageLoad stepKey="waitForElementAdded2"/> <!-- Go to the shopping cart page and edit the first product --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="onPageShoppingCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="onPageShoppingCart"/> <waitForElementVisible stepKey="waitForInfoDropdown" selector="{{CheckoutCartSummarySection.total}}"/> <waitForPageLoad stepKey="waitForCartPageLoad3"/> <grabTextFrom selector="{{CheckoutCartSummarySection.total}}" stepKey="grabTotalBefore"/> @@ -107,8 +106,7 @@ <waitForPageLoad stepKey="waitForElementAdded3"/> <!-- Go to the shopping cart page --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="onPageShoppingCart2"/> - <waitForPageLoad stepKey="waitForCartPageLoad2"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="onPageShoppingCart2"/> <!-- Assert that the options are both there and the proce no longer matches --> <see stepKey="assertBothOptions" selector="{{CheckoutCartProductSection.nthItemOption('2')}}" userInput="$$simpleProduct1.sku$$"/> diff --git a/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundlePriceTest.php b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundlePriceTest.php index b0519f1ebddba..5d06150d197a9 100644 --- a/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundlePriceTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundlePriceTest.php @@ -68,7 +68,8 @@ public function testModifyMeta() ]; $priceParams = [ 'imports' => [ - 'disabled' => 'ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked' + 'disabled' => 'ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['disabled' => false], ] ]; $priceMeta = [ diff --git a/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundleWeightTest.php b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundleWeightTest.php index 8c89a18c292f6..c826bba4dbabb 100644 --- a/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundleWeightTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Form/Modifier/BundleWeightTest.php @@ -58,6 +58,7 @@ public function testModifyMeta() $weightParams = [ 'imports' => [ 'disabled' => 'ns = ${ $.ns }, index = ' . BundleWeight::CODE_WEIGHT_TYPE . ':checked', + '__disableTmpl' => ['disabled' => false], ] ]; $hasWeightParams = [ diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleAdvancedPricing.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleAdvancedPricing.php index 1b04bdc4f8104..312a812402e97 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleAdvancedPricing.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleAdvancedPricing.php @@ -34,7 +34,7 @@ public function __construct(ArrayManager $arrayManager) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -63,7 +63,7 @@ public function modifyMeta(array $meta) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -88,7 +88,8 @@ private function modifyMsrpMeta(array $meta) $meta, [ 'imports' => [ - 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked' + 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['disabled' => false], ] ] ); @@ -103,7 +104,8 @@ private function modifyMsrpMeta(array $meta) $meta, [ 'imports' => [ - 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked' + 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['disabled' => false], ] ] ); diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleCustomOptions.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleCustomOptions.php index 1a0e08bd07255..b25fad0a40677 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleCustomOptions.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleCustomOptions.php @@ -15,7 +15,7 @@ class BundleCustomOptions extends AbstractModifier { /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -55,6 +55,7 @@ public function modifyCustomOptionsButton(array $meta, $group, $container, $butt if (!empty($meta[$group]['children'][$container]['children'][$button])) { $meta[$group]['children'][$container]['children'][$button]['arguments']['data']['config']['imports'] = [ 'visible' => '!ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['visible' => false], ]; } return $meta; @@ -79,6 +80,7 @@ public function getErrorMessage($sortOrder) 'sortOrder' => $sortOrder, 'imports' => [ 'visible' => 'ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['visible' => false], ], ], ], @@ -87,7 +89,7 @@ public function getErrorMessage($sortOrder) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php index 18f970069c3c9..5ff9e674acad9 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php @@ -319,6 +319,7 @@ protected function getBundleHeader() * Get Bundle Options structure * * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function getBundleOptions() { @@ -348,7 +349,8 @@ protected function getBundleOptions() 'component' => 'Magento_Ui/js/dynamic-rows/record', 'positionProvider' => 'product_bundle_container.position', 'imports' => [ - 'label' => '${ $.name }' . '.product_bundle_container.option_info.title:value' + 'label' => '${ $.name }' . '.product_bundle_container.option_info.title:value', + '__disableTmpl' => ['label' => false], ], ], ], @@ -381,6 +383,7 @@ protected function getBundleOptions() 'template' => 'ui/dynamic-rows/templates/default', 'provider' => 'product_form.product_form_data_source', 'dataProvider' => '${ $.dataScope }' . '.bundle_button_proxy', + '__disableTmpl' => ['dataProvider' => false], 'identificationDRProperty' => 'product_id', 'identificationProperty' => 'product_id', 'map' => [ @@ -395,9 +398,13 @@ protected function getBundleOptions() 'selection_price_value' => '', 'selection_qty' => '', ], - 'links' => ['insertData' => '${ $.provider }:${ $.dataProvider }'], + 'links' => [ + 'insertData' => '${ $.provider }:${ $.dataProvider }', + '__disableTmpl' => ['insertData' => false], + ], 'imports' => [ 'inputType' => '${$.provider}:${$.dataScope}.type', + '__disableTmpl' => ['inputType' => false], ], 'source' => 'product', ], @@ -619,9 +626,11 @@ protected function getBundleSelections() 'is_collection' => true, 'imports' => [ 'inputType' => '${$.parentName}:inputType', + '__disableTmpl' => ['inputType' => false], ], 'exports' => [ 'isDefaultValue' => '${$.parentName}:isDefaultValue.${$.index}', + '__disableTmpl' => ['isDefaultValue' => false], ], ], ], @@ -702,7 +711,8 @@ protected function getBundleSelections() 'validate-greater-than-zero' => true ], 'imports' => [ - 'isInteger' => '${ $.provider }:${ $.parentScope }.selection_qty_is_integer' + 'isInteger' => '${ $.provider }:${ $.parentScope }.selection_qty_is_integer', + '__disableTmpl' => ['isInteger' => false], ], ], ], @@ -723,6 +733,7 @@ protected function getBundleSelections() 'sortOrder' => 110, 'imports' => [ 'inputType' => '${$.parentName}:inputType', + '__disableTmpl' => ['inputType' => false], ], ], ], @@ -764,7 +775,8 @@ protected function getSelectionPriceValue() 'dataScope' => 'selection_price_value', 'value' => '0.00', 'imports' => [ - 'visible' => '!ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked' + 'visible' => '!ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['visible' => false], ], 'sortOrder' => 80, ], @@ -801,7 +813,8 @@ protected function getSelectionPriceType() ] ], 'imports' => [ - 'visible' => '!ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked' + 'visible' => '!ns = ${ $.ns }, index = ' . BundlePrice::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['visible' => false], ], 'sortOrder' => 90, ], diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php index d7da7513c3aac..598b762c061a1 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php @@ -71,7 +71,8 @@ public function modifyMeta(array $meta) $meta, [ 'imports' => [ - 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked' + 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['disabled' => false], ] ] ); @@ -86,7 +87,8 @@ public function modifyMeta(array $meta) $meta, [ 'imports' => [ - 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked' + 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_PRICE_TYPE . ':checked', + '__disableTmpl' => ['disabled' => false], ] ] ); diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleWeight.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleWeight.php index 83e045d3562cf..39e9b3d7b5cd4 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleWeight.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundleWeight.php @@ -30,7 +30,7 @@ public function __construct(ArrayManager $arrayManager) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -73,15 +73,16 @@ public function modifyMeta(array $meta) [ 'imports' => [ 'disabled' => 'ns = ${ $.ns }, index = ' . static::CODE_WEIGHT_TYPE . ':checked', + '__disableTmpl' => ['disabled' => false], ] ] ); - + return $meta; } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { diff --git a/app/code/Magento/Catalog/Helper/Output.php b/app/code/Magento/Catalog/Helper/Output.php index 93b67965e7234..c61268cd7664e 100644 --- a/app/code/Magento/Catalog/Helper/Output.php +++ b/app/code/Magento/Catalog/Helper/Output.php @@ -182,7 +182,7 @@ public function productAttribute($product, $attributeHtml, $attributeName) if ($attributeHtml !== null && $attribute->getIsHtmlAllowedOnFront() && $attribute->getIsWysiwygEnabled() - && $this->isDirectivesExists($attributeHtml) + && $this->isDirectivesExists((string)$attributeHtml) ) { $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml); } @@ -219,7 +219,7 @@ public function categoryAttribute($category, $attributeHtml, $attributeName) if ($attributeHtml !== null && $attribute->getIsHtmlAllowedOnFront() && $attribute->getIsWysiwygEnabled() - && $this->isDirectivesExists($attributeHtml) + && $this->isDirectivesExists((string)$attributeHtml) ) { $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml); @@ -238,7 +238,7 @@ public function categoryAttribute($category, $attributeHtml, $attributeName) * @param string $attributeHtml * @return bool */ - public function isDirectivesExists($attributeHtml) + public function isDirectivesExists(string $attributeHtml): bool { $matches = false; foreach ($this->directivePatterns as $pattern) { diff --git a/app/code/Magento/Catalog/Helper/Product/ProductList.php b/app/code/Magento/Catalog/Helper/Product/ProductList.php index 3aa6aeed3779a..9f976654d9676 100644 --- a/app/code/Magento/Catalog/Helper/Product/ProductList.php +++ b/app/code/Magento/Catalog/Helper/Product/ProductList.php @@ -3,33 +3,37 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Helper\Product; +use Magento\Catalog\Model\Config; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Registry; +use Magento\Store\Model\ScopeInterface; + /** - * Class ProductList + * Returns data for toolbars of Sorting and Pagination * * @api * @since 100.0.2 */ class ProductList { - /** - * List mode configuration path - */ - const XML_PATH_LIST_MODE = 'catalog/frontend/list_mode'; + public const XML_PATH_LIST_MODE = 'catalog/frontend/list_mode'; + public const DEFAULT_SORT_DIRECTION = 'asc'; const VIEW_MODE_LIST = 'list'; const VIEW_MODE_GRID = 'grid'; - const DEFAULT_SORT_DIRECTION = 'asc'; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ protected $scopeConfig; /** - * @var \Magento\Framework\Registry + * @var Registry */ private $coreRegistry; @@ -38,20 +42,18 @@ class ProductList * * @var array */ - protected $_defaultAvailableLimit = [10 => 10,20 => 20,50 => 50]; + protected $_defaultAvailableLimit = [10 => 10, 20 => 20, 50 => 50]; /** - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Framework\Registry $coreRegistry + * @param ScopeConfigInterface $scopeConfig + * @param Registry $coreRegistry */ public function __construct( - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Framework\Registry $coreRegistry = null + ScopeConfigInterface $scopeConfig, + Registry $coreRegistry = null ) { $this->scopeConfig = $scopeConfig; - $this->coreRegistry = $coreRegistry ?: \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\Registry::class - ); + $this->coreRegistry = $coreRegistry ?? ObjectManager::getInstance()->get(Registry::class); } /** @@ -61,31 +63,23 @@ public function __construct( */ public function getAvailableViewMode() { - $value = $this->scopeConfig->getValue( - self::XML_PATH_LIST_MODE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); + $value = $this->scopeConfig->getValue(self::XML_PATH_LIST_MODE, ScopeInterface::SCOPE_STORE); + switch ($value) { case 'grid': - $availableMode = ['grid' => __('Grid')]; - break; + return ['grid' => __('Grid')]; case 'list': - $availableMode = ['list' => __('List')]; - break; + return ['list' => __('List')]; case 'grid-list': - $availableMode = ['grid' => __('Grid'), 'list' => __('List')]; - break; + return ['grid' => __('Grid'), 'list' => __('List')]; case 'list-grid': - $availableMode = ['list' => __('List'), 'grid' => __('Grid')]; - break; - default: - $availableMode = null; - break; + return ['list' => __('List'), 'grid' => __('Grid')]; } - return $availableMode; + + return null; } /** @@ -99,12 +93,14 @@ public function getDefaultViewMode($options = []) if (empty($options)) { $options = $this->getAvailableViewMode(); } + return current(array_keys($options)); } /** * Get default sort field * + * @FIXME Helper should be context-independent * @return null|string */ public function getDefaultSortField() @@ -114,34 +110,28 @@ public function getDefaultSortField() return $currentCategory->getDefaultSortBy(); } - return $this->scopeConfig->getValue( - \Magento\Catalog\Model\Config::XML_PATH_LIST_DEFAULT_SORT_BY, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); + return $this->scopeConfig->getValue(Config::XML_PATH_LIST_DEFAULT_SORT_BY, ScopeInterface::SCOPE_STORE); } /** * Retrieve available limits for specified view mode * - * @param string $mode + * @param string $viewMode * @return array */ - public function getAvailableLimit($mode) + public function getAvailableLimit($viewMode): array { - if (!in_array($mode, [self::VIEW_MODE_GRID, self::VIEW_MODE_LIST])) { + $availableViewModes = $this->getAvailableViewMode(); + + if (!isset($availableViewModes[$viewMode])) { return $this->_defaultAvailableLimit; } - $perPageConfigKey = 'catalog/frontend/' . $mode . '_per_page_values'; - $perPageValues = (string)$this->scopeConfig->getValue( - $perPageConfigKey, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); + + $perPageConfigPath = 'catalog/frontend/' . $viewMode . '_per_page_values'; + $perPageValues = (string)$this->scopeConfig->getValue($perPageConfigPath, ScopeInterface::SCOPE_STORE); $perPageValues = explode(',', $perPageValues); $perPageValues = array_combine($perPageValues, $perPageValues); - if ($this->scopeConfig->isSetFlag( - 'catalog/frontend/list_allow_all', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )) { + if ($this->scopeConfig->isSetFlag('catalog/frontend/list_allow_all', ScopeInterface::SCOPE_STORE)) { return ($perPageValues + ['all' => __('All')]); } else { return $perPageValues; @@ -149,24 +139,17 @@ public function getAvailableLimit($mode) } /** - * Retrieve default per page values + * Returns default value of `per_page` for view mode provided * * @param string $viewMode - * @return string (comma separated) + * @return int */ - public function getDefaultLimitPerPageValue($viewMode) + public function getDefaultLimitPerPageValue($viewMode): int { - if ($viewMode == self::VIEW_MODE_LIST) { - return $this->scopeConfig->getValue( - 'catalog/frontend/list_per_page', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - } elseif ($viewMode == self::VIEW_MODE_GRID) { - return $this->scopeConfig->getValue( - 'catalog/frontend/grid_per_page', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - } - return 0; + $xmlConfigPath = sprintf('catalog/frontend/%s_per_page', $viewMode); + $defaultLimit = $this->scopeConfig->getValue($xmlConfigPath, ScopeInterface::SCOPE_STORE); + + $availableLimits = $this->getAvailableLimit($viewMode); + return (int)($availableLimits[$defaultLimit] ?? current($availableLimits)); } } diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index fd59130db4bdf..bc8d274fb6e63 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -73,9 +73,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements const STORE_ID = 'store_id'; /** - * @var string + * @var string|bool */ - protected $_cacheTag = self::CACHE_TAG; + protected $_cacheTag = false; /** * @var string @@ -878,7 +878,6 @@ public function getAttributes($groupId = null, $skipSuper = false) */ public function beforeSave() { - $this->cleanCache(); $this->setTypeHasOptions(false); $this->setTypeHasRequiredOptions(false); $this->setHasOptions(false); diff --git a/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php b/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php index 57d08916bcd40..89c550d4e0ab3 100644 --- a/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php +++ b/app/code/Magento/Catalog/Model/Product/AttributeSet/Options.php @@ -10,11 +10,22 @@ */ class Options implements \Magento\Framework\Data\OptionSourceInterface { + /** * @var array */ protected $options; + /** + * @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory + */ + protected $collectionFactory; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Product + */ + protected $product; + /** * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $collectionFactory * @param \Magento\Catalog\Model\ResourceModel\Product $product @@ -36,13 +47,6 @@ public function toOptionArray() $this->options = $this->collectionFactory->create() ->setEntityTypeFilter($this->product->getTypeId()) ->toOptionArray(); - - array_walk( - $this->options, - function (&$option) { - $option['__disableTmpl'] = true; - } - ); } return $this->options; diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryActionGroup.xml index 535a08382aabb..95f11901c97f0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryActionGroup.xml @@ -10,6 +10,5 @@ <actionGroup name="AdminSaveCategoryActionGroup"> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryWithProducts"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see userInput="You saved the category." stepKey="seeSuccessMessage"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategorySaveSuccessMessageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategorySaveSuccessMessageActionGroup.xml new file mode 100644 index 0000000000000..ddc2a5d44acbd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategorySaveSuccessMessageActionGroup.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="AssertAdminCategorySaveSuccessMessageActionGroup"> + <annotations> + <description>Checks success message after category was saved.</description> + </annotations> + + <waitForElementVisible selector="{{AdminMessagesSection.success}}" time="30" stepKey="waitForElement"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the category." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index 1aff1a5031413..540db609f550b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridSection"> <element name="productRowBySku" type="block" selector="//td[count(../../..//th[./*[.='SKU']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]" parameterized="true" /> + <element name="productRowByName" type="block" selector="//td[count(../../..//th[./*[.='Name']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]" parameterized="true" /> <element name="productRowCheckboxBySku" type="block" selector="//td[count(../../..//th[./*[.='SKU']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]/../td//input[@data-action='select-row']" parameterized="true" /> <element name="loadingMask" type="text" selector=".admin__data-grid-loading-mask[data-component*='product_listing']"/> <element name="columnHeader" type="button" selector="//div[@data-role='grid-wrapper']//table[contains(@class, 'data-grid')]/thead/tr/th[contains(@class, 'data-grid-th')]/span[text() = '{{label}}']" parameterized="true" timeout="30"/> @@ -35,5 +36,6 @@ <element name="productGridContentsOnRow" type="checkbox" selector="//*[@id='container']//tr[{{row}}]/td" parameterized="true"/> <element name="selectRowBasedOnName" type="input" selector="//td/div[text()='{{var1}}']" parameterized="true"/> <element name="changeStatus" type="button" selector="//div[contains(@class,'admin__data-grid-header-row') and contains(@class, 'row')]//div[contains(@class, 'action-menu-item')]//ul/li/span[text() = '{{status}}']" parameterized="true"/> + <element name="productRowByTypeAndName" type="block" selector="//div[@data-role='grid-wrapper']//table[contains(@class, 'data-grid')]//td[count(../../..//th[./*[.='Type']]/preceding-sibling::th) + 1][./*[.='{{type}}']]/../td[contains(.,'{{name}}')]" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml deleted file mode 100644 index b2cd0f5f9af9f..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?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="StorefrontMiniCartSection"> - <element name="quantity" type="button" selector="span.counter-number"/> - <element name="show" type="button" selector="a.showcart"/> - <element name="goToCheckout" type="button" selector="#top-cart-btn-checkout" timeout="30"/> - <element name="emptyMiniCart" type="text" selector="//div[@class='minicart-wrapper']//span[@class='counter qty empty']/../.."/> - </section> -</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml index 83a05fef83607..e00b3fe2994eb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml @@ -77,8 +77,7 @@ </actionGroup> <!-- Check that cart page contains cross-sell to simpleProduct2 and simpleProduct3--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart1"/> - <waitForPageLoad stepKey="waitForCartToLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart1"/> <waitForElementVisible selector="{{CheckoutCartCrossSellSection.products}}" stepKey="waitForCrossSellLoading"/> <see stepKey="seeProduct2InCrossSell" selector="{{CheckoutCartCrossSellSection.products}}" userInput="$simpleProduct2.name$"/> <see stepKey="seeProduct3InCrossSell" selector="{{CheckoutCartCrossSellSection.products}}" userInput="$simpleProduct3.name$"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml deleted file mode 100644 index 61d197d34a31d..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml +++ /dev/null @@ -1,51 +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="AdminAddDefaultVideoSimpleProductTest"> - <annotations> - <features value="Catalog"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to add default product video for a Simple Product"/> - <description value="Admin should be able to add default product video for a Simple Product"/> - <severity value="BLOCKER"/> - <testCaseId value="MC-111"/> - <group value="Catalog"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - </before> - <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> - </after> - - <!-- Create product --> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="adminProductIndexPageAdd"/> - <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> - <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGridColumnsInitial"/> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="ApiSimpleProduct"/> - </actionGroup> - <actionGroup ref="FillMainProductFormNoWeightActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="ApiSimpleProduct"/> - </actionGroup> - - <!-- Save product --> - <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="ApiSimpleProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml deleted file mode 100644 index e1c81c8ae0303..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml +++ /dev/null @@ -1,37 +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="AdminAddDefaultVideoVirtualProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> - <annotations> - <features value="Catalog"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to add default product video for a Virtual Product"/> - <description value="Admin should be able to add default product video for a Virtual Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-109"/> - <group value="Catalog"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - - <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> - - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="defaultVirtualProduct"/> - </actionGroup> - <actionGroup ref="FillMainProductFormNoWeightActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="defaultVirtualProduct"/> - </actionGroup> - <actionGroup ref="AssertProductInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="defaultVirtualProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml index b2181164070dc..cbbd496e8cb34 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml @@ -171,8 +171,7 @@ <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('1')}}" userInput="25" stepKey="selectProductTierPricePercentageValue2"/> <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton4"/> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct4"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage1"/> - <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad1"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage1"/> <seeInField userInput="20" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="seeInQtyField20"/> <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField4"/> <assertEquals message="Shopping cart should contain subtotal $1,500" stepKey="assertSubtotalField4"> @@ -198,8 +197,7 @@ <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig1"/> <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeConfigSuccessMessage1"/> <actionGroup ref="ClearCacheActionGroup" stepKey="flushCache1"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage2"/> - <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad2"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage2"/> <seeInField userInput="20" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="seeInQtyField20_2"/> <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField5"/> <assertEquals message="Shopping cart should contain subtotal $1,500" stepKey="assertSubtotalField5"> @@ -212,8 +210,7 @@ <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig2"/> <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeConfigSuccessMessage2"/> <actionGroup ref="ClearCacheActionGroup" stepKey="flushCache2"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage3"/> - <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad3"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage3"/> <seeInField userInput="20" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="seeInQtyField20_3"/> <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField6"/> <assertEquals message="Shopping cart should contain subtotal $1,500" stepKey="assertSubtotalField6"> @@ -250,8 +247,7 @@ <waitForElementVisible selector="{{AdminProductFormSection.productPrice}}" stepKey="waitForAdminProductFormSectionProductPriceInput"/> <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="200" stepKey="fillProductPrice200"/> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage4"/> - <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad4"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage4"/> <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField7"/> <assertEquals message="Shopping cart should contain subtotal $4,000" stepKey="assertSubtotalField7"> <expectedResult type="string">$4,000.00</expectedResult> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml index 2640ab04023a3..96d0c209aba34 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml @@ -41,8 +41,7 @@ </actionGroup> <!-- Go to the cart page and verify we see the product --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="gotoCart"/> - <waitForPageLoad stepKey="waitForCartLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="gotoCart"/> <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertProductItemInCheckOutCart"> <argument name="productName" value="$$createProduct.name$$"/> <argument name="productSku" value="$$createProduct.sku$$"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml index 549b1bb37cce4..6ac71c4a7982d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml @@ -122,7 +122,7 @@ <click selector="{{AdminCategoryProductsGridSection.productSelectAll}}" stepKey="selectSelectAll"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> <!--Open Category Store Front Page--> <comment userInput="Open Category Store Front Page" stepKey="commentOpenCategoryOnStorefront"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml index ca83f746ee969..4c1993eb803b3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml @@ -59,7 +59,7 @@ <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> <!--Verify the Category Title--> <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml index 967cb773fe3b0..b27d9239c53e1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml @@ -43,35 +43,35 @@ <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveFirstSubCategory"/> <waitForPageLoad stepKey="waitForSFirstSubCategorySaved"/> <!-- Verify success message --> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <!--Create Nested Second Sub Category--> <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton1"/> <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SecondLevelSubCat.name}}" stepKey="fillSecondSubCategoryName"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSecondSubCategory"/> <waitForPageLoad stepKey="waitForSecondCategory"/> <!-- Verify success message --> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage1"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage1"/> <!--Create Nested Third Sub Category/>--> <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton2"/> <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{ThirdLevelSubCat.name}}" stepKey="fillThirdSubCategoryName"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveThirdSubCategory"/> <waitForPageLoad stepKey="waitForThirdCategorySaved"/> <!-- Verify success message --> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage2"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage2"/> <!--Create Nested fourth Sub Category />--> <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton3"/> <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FourthLevelSubCat.name}}" stepKey="fillFourthSubCategoryName"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveFourthSubCategory"/> <waitForPageLoad stepKey="waitForFourthCategorySaved"/> <!-- Verify success message --> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage3"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage3"/> <!--Create Nested fifth Sub Category />--> <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton4"/> <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FifthLevelCat.name}}" stepKey="fillFifthSubCategoryName"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveFifthLevelCategory"/> <waitForPageLoad stepKey="waitForFifthCategorySaved"/> <!-- Verify success message --> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage4"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage4"/> <amOnPage url="/{{FirstLevelSubCat.name}}/{{SecondLevelSubCat.name}}/{{ThirdLevelSubCat.name}}/{{FourthLevelSubCat.name}}/{{FifthLevelCat.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> <waitForPageLoad time="60" stepKey="waitForStoreFrontPageLoad"/> <!--<Verify category displayed in store front page--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml index c82658d283cfb..a7dab57173377 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml @@ -32,7 +32,7 @@ <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="dontCategoryIsChecked"/> <!--Verify InActive Category is created--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml index 0019c9c4e948e..d3a766be2c99f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml @@ -32,7 +32,7 @@ <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <waitForPageLoad stepKey="waitForPageSaved"/> <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> <!--Verify Category is created/>--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml index 9ae3149d94039..3273fb62e7d9c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml @@ -61,7 +61,7 @@ </actionGroup> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> <!-- Get Category ID --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml index 00e93122d28bc..0b269749c5dd6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml @@ -74,7 +74,7 @@ <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectDefaultProductFromTableRow"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="WaitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="successMessageYouSavedTheCategory"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="successMessageYouSavedTheCategory"/> <!--Verify product with grid filter is not not visible--> <amOnPage url="{{StorefrontProductPage.url(defaultSimpleProduct.urlKey)}}" stepKey="seeOnProductPage"/> <waitForPageLoad stepKey="waitForStoreFrontProductPageToLoad"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml index 20d4da8e6f09b..19552ddaab729 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml @@ -32,7 +32,7 @@ <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> <!-- Verify success message --> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <!-- Verify subcategory created with required fields --> <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> <seeElement selector="{{AdminCategoryContentSection.activeCategoryInTree(_defaultCategory.name)}}" stepKey="seeCategoryInTree" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml index 7b44743e2e1a8..29c7bc6828662 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml @@ -35,7 +35,7 @@ <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="EnableCheckOption"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="ClickSaveButton"/> <waitForPageLoad stepKey="WaitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="AssertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="AssertSuccessMessage"/> <seeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="SeeCheckBoxisSelected"/> <seeInField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="SeedFieldInput"/> </test> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml deleted file mode 100644 index 60c32004e3ca8..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml +++ /dev/null @@ -1,54 +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="AdminRemoveDefaultVideoSimpleProductTest"> - <annotations> - <features value="Catalog"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to remove default product video from a Simple Product"/> - <description value="Admin should be able to remove default product video from a Simple Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-206"/> - <group value="Catalog"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - </before> - <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> - </after> - - <!-- Create product --> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="adminProductIndexPageAdd"/> - <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> - <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="ApiSimpleProduct"/> - </actionGroup> - <actionGroup ref="FillMainProductFormNoWeightActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="ApiSimpleProduct"/> - </actionGroup> - - <!-- Save product --> - <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> - - <!-- Save product --> - <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductFormAfterRemove"/> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="ApiSimpleProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml deleted file mode 100644 index 0104eff068e0b..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml +++ /dev/null @@ -1,37 +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="AdminRemoveDefaultVideoVirtualProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> - <annotations> - <features value="Catalog"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to remove default product video from a Virtual Product"/> - <description value="Admin should be able to remove default product video from a Virtual Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-204"/> - <group value="Catalog"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - - <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> - - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="defaultVirtualProduct"/> - </actionGroup> - <actionGroup ref="FillMainProductFormNoWeightActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="defaultVirtualProduct"/> - </actionGroup> - <actionGroup ref="AssertProductInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="defaultVirtualProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml index a36f62bc4bedf..a4ba859714982 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml @@ -35,7 +35,7 @@ <click selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="disableCategory"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="dontCategoryIsChecked"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml index b313f14627fe5..db6cfce167bce 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml @@ -46,7 +46,7 @@ <fillField selector="{{AdminCategorySEOSection.MetaTitleInput}}" userInput="{{SimpleRootSubCategory.name}}" stepKey="fillUpdatedMetaTitle"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <!--Open UrlRewrite Page--> <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml index 6412bf975003b..9b827550a6817 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml @@ -57,7 +57,7 @@ <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProduct1FromTableRow"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> <!--Verify Category Title--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml index d89ffa0055d3b..3eaae60d789f5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml @@ -39,19 +39,19 @@ <createData entity="CustomerAccountSharingGlobal" stepKey="setConfigCustomerAccountToGlobal"/> </before> - <!--Create website, Sore adn Store View--> + <!--Create website, Store and Store View--> <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="AdminCreateWebsite"> - <argument name="newWebsiteName" value="secondWebsite"/> - <argument name="websiteCode" value="second_website"/> + <argument name="newWebsiteName" value="{{customWebsite.name}}"/> + <argument name="websiteCode" value="{{customWebsite.code}}"/> </actionGroup> <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="AdminCreateStore"> - <argument name="website" value="secondWebsite"/> - <argument name="storeGroupName" value="secondStore"/> - <argument name="storeGroupCode" value="second_store"/> + <argument name="website" value="{{customWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> </actionGroup> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="AdminCreateStoreView"> - <argument name="StoreGroup" value="customStoreTierPrice"/> - <argument name="customStore" value="customStoreView"/> + <argument name="StoreGroup" value="customStoreGroup"/> + <argument name="customStore" value="customStore"/> </actionGroup> <!--Set Configuration--> <createData entity="CatalogPriceScopeWebsite" stepKey="paymentMethodsSettingConfig"/> @@ -63,10 +63,10 @@ <argument name="product" value="$$product1$$"/> </actionGroup> <actionGroup ref="ProductSetWebsiteActionGroup" stepKey="ProductSetWebsite"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="ProductSetAdvancedPricingActionGroup" stepKey="ProductSetAdvancedPricing1"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct2"> @@ -76,10 +76,10 @@ <argument name="product" value="$$product2$$"/> </actionGroup> <actionGroup ref="ProductSetWebsiteActionGroup" stepKey="ProductSetWebsite2"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="ProductSetAdvancedPricingActionGroup" stepKey="ProductSetAdvancedPricing2"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct3"> @@ -89,10 +89,10 @@ <argument name="product" value="$$product3$$"/> </actionGroup> <actionGroup ref="ProductSetWebsiteActionGroup" stepKey="ProductSetWebsite3"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="ProductSetAdvancedPricingActionGroup" stepKey="ProductSetAdvancedPricing3"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct4"> @@ -102,10 +102,10 @@ <argument name="product" value="$$product4$$"/> </actionGroup> <actionGroup ref="ProductSetWebsiteActionGroup" stepKey="ProductSetWebsite4"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="ProductSetAdvancedPricingActionGroup" stepKey="ProductSetAdvancedPricing4"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{customWebsite.name}}"/> </actionGroup> <actionGroup ref="ClearProductsFilterActionGroup" stepKey="ClearProductsFilterActionGroup"/> @@ -120,7 +120,7 @@ <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="ClickOnAccountInformationSection"/> <waitForPageLoad stepKey="waitForPageOpened1"/> <selectOption selector="{{AdminCustomerAccountInformationSection.group}}" userInput="Retailer" stepKey="Group"/> - <selectOption selector="{{AdminCustomerAccountInformationSection.storeView}}" userInput="secondStoreView" stepKey="clickToSelectStore"/> + <selectOption selector="{{AdminCustomerAccountInformationSection.storeView}}" userInput="{{customStore.name}}" stepKey="clickToSelectStore"/> <click selector="{{AdminCustomerAccountInformationSection.saveCustomer}}" stepKey="save"/> <waitForPageLoad stepKey="waitForCustomersPage"/> <see userInput="You saved the customer." stepKey="CustomerIsSaved"/> @@ -136,7 +136,7 @@ <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> <waitForPageLoad stepKey="waitForPageDiscountPageIsLoaded"/> <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="ship" stepKey="fillRuleName"/> - <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="secondWebsite" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{customWebsite.name}}" stepKey="selectWebsites"/> <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="Retailer" stepKey="selectCustomerGroup"/> <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="Specific Coupon" stepKey="selectCouponType"/> <fillField selector="{{AdminCartPriceRulesFormSection.couponCode}}" userInput="ship" stepKey="setCode"/> @@ -152,7 +152,7 @@ <!--Create new order--> <actionGroup ref="NavigateToNewOrderPageExistingCustomerActionGroup" stepKey="CreateNewOrder"> <argument name="customer" value="Simple_US_Customer"/> - <argument name="storeView" value="customStoreView"/> + <argument name="storeView" value="customStore"/> </actionGroup> <click selector="{{OrdersGridSection.addProducts}}" stepKey="clickToAddProduct"/> @@ -327,7 +327,7 @@ <argument name="ruleName" value="ship"/> </actionGroup> <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="DeleteWebsite"> - <argument name="websiteName" value="secondWebsite"/> + <argument name="websiteName" value="{{customWebsite.name}}"/> </actionGroup> <createData entity="CustomerAccountSharingDefault" stepKey="setConfigCustomerAccountDefault"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml index 4a36ebe7a2a42..9b5fa25085e1a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml @@ -47,7 +47,7 @@ <click selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="EnableCategory"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryWithProducts"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see userInput="You saved the category." stepKey="seeSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="seeSuccessMessage"/> <!--Run re-index task--> <magentoCLI command="indexer:reindex" stepKey="reindex"/> diff --git a/app/code/Magento/Catalog/Test/Unit/Helper/Product/ProductListTest.php b/app/code/Magento/Catalog/Test/Unit/Helper/Product/ProductListTest.php new file mode 100644 index 0000000000000..9d4e3527fd34d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Helper/Product/ProductListTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Helper\Product; + +use Magento\Catalog\Helper\Product\ProductList; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ProductListTest extends TestCase +{ + const STUB_VIEW_MODE = 'grid'; + /** + * @var ScopeConfigInterface|MockObject + */ + private $scopeConfigMock; + + /** + * @var ProductList + */ + private $productListHelper; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); + $this->productListHelper = $objectManager->getObject(ProductList::class, [ + 'scopeConfig' => $this->scopeConfigMock + ]); + } + + /** + * @dataProvider defaultAvailableLimitsDataProvider + */ + public function testGetDefaultLimitPerPageValueReturnsOneOfAvailableLimits( + string $availableValues, + int $defaultValue, + int $expectedReturn + ) { + $this->scopeConfigMock->method('getValue') + ->willReturnMap([ + [sprintf('catalog/frontend/%s_per_page_values', self::STUB_VIEW_MODE), $availableValues], + [sprintf('catalog/frontend/%s_per_page', self::STUB_VIEW_MODE), $defaultValue] + ]); + + $returnedValue = $this->productListHelper->getDefaultLimitPerPageValue(self::STUB_VIEW_MODE); + + $this->assertSame($expectedReturn, $returnedValue); + } + + public function defaultAvailableLimitsDataProvider(): array + { + return [ + 'limit-available' => [ + 'values' => '10,20,30', + 'default' => 10, + 'expected' => 10 + ], + 'limit-not-available' => [ + 'values' => '10,20,30', + 'default' => 1, + 'expected' => 10 + ] + ]; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php index b3acaa4b8bbed..ba81d42e75c76 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php @@ -129,7 +129,6 @@ public function testCreateWithNotFilterableInGridAttribute(array $filterModifier 'visible' => null, 'filter' => $filter, 'component' => 'Magento_Ui/js/grid/columns/column', - '__disableTmpl' => ['label' => true] ], ], 'context' => $this->context, @@ -206,7 +205,6 @@ public function testCreateDateColumn( 'component' => 'Magento_Ui/js/grid/columns/date', 'timezone' => $expectedTimezone, 'dateFormat' => $expectedDateFormat, - '__disableTmpl' => ['label' => true], 'options' => [ 'showsTime' => $showsTime ] diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php index c704d9f89581d..f34ebd2e1cf4d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/Product/MassActionTest.php @@ -11,14 +11,15 @@ use Magento\Framework\AuthorizationInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Element\UiComponent\ContextInterface; +use PHPUnit\Framework\MockObject\MockObject; /** - * MassAction test + * MassAction test for Component Product */ class MassActionTest extends \PHPUnit\Framework\TestCase { /** - * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ContextInterface|MockObject */ private $contextMock; @@ -28,7 +29,7 @@ class MassActionTest extends \PHPUnit\Framework\TestCase private $objectManager; /** - * @var AuthorizationInterface|\PHPUnit_Framework_MockObject_MockObject + * @var AuthorizationInterface|MockObject */ private $authorizationMock; @@ -107,7 +108,6 @@ public function getPrepareDataProvider() : array 'type' => 'first_action', 'label' => 'First Action', 'url' => '/module/controller/firstAction', - '__disableTmpl' => true ], ], [ @@ -127,7 +127,6 @@ public function getPrepareDataProvider() : array 'url' => '/module/controller/secondSubAction2' ], ], - '__disableTmpl' => true ], ], [ @@ -147,7 +146,6 @@ public function getPrepareDataProvider() : array 'url' => '/module/controller/disable' ], ], - '__disableTmpl' => true ], ], [ @@ -167,7 +165,6 @@ public function getPrepareDataProvider() : array 'url' => '/module/controller/disable' ], ], - '__disableTmpl' => true ], false, false @@ -178,7 +175,6 @@ public function getPrepareDataProvider() : array 'type' => 'delete', 'label' => 'First Action', 'url' => '/module/controller/delete', - '__disableTmpl' => true ], ], [ @@ -187,7 +183,6 @@ public function getPrepareDataProvider() : array 'type' => 'delete', 'label' => 'First Action', 'url' => '/module/controller/delete', - '__disableTmpl' => true ], false, false @@ -198,7 +193,6 @@ public function getPrepareDataProvider() : array 'type' => 'delete', 'label' => 'First Action', 'url' => '/module/controller/attributes', - '__disableTmpl' => true ], ], [ @@ -207,7 +201,6 @@ public function getPrepareDataProvider() : array 'type' => 'delete', 'label' => 'First Action', 'url' => '/module/controller/attributes', - '__disableTmpl' => true ], false, false diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php index 91e22407acc43..917ef2bd522f8 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php @@ -570,7 +570,6 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, - '__disableTmpl' => ['label' => true, 'code' => true] ], ], 'default_null_prod_not_new_locked_and_required' => [ @@ -590,7 +589,6 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, - '__disableTmpl' => ['label' => true, 'code' => true] ], 'locked' => true, ], @@ -611,7 +609,6 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, - '__disableTmpl' => ['label' => true, 'code' => true] ], ], 'default_null_prod_new_and_not_required' => [ @@ -631,7 +628,6 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, - '__disableTmpl' => ['label' => true, 'code' => true] ], ], 'default_null_prod_new_locked_and_not_required' => [ @@ -651,7 +647,6 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, - '__disableTmpl' => ['label' => true, 'code' => true] ], 'locked' => true, ], @@ -672,7 +667,6 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, - '__disableTmpl' => ['label' => true, 'code' => true] ], ], 'datetime_null_prod_not_new_and_required' => [ @@ -692,7 +686,6 @@ public function setupAttributeMetaDataProvider() 'scopeLabel' => '', 'globalScope' => false, 'sortOrder' => 0, - '__disableTmpl' => ['label' => true, 'code' => true] ], 'locked' => false, 'frontendInput' => 'datetime', diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php index b902e741c006c..c7afc61e38ca4 100644 --- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php @@ -92,7 +92,6 @@ public function create($attribute, $context, array $config = []) 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers)) ? $this->getFilterType($attribute->getFrontendInput()) : null, - '__disableTmpl' => ['label' => true], ], $config ); diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php index 596b0f4118599..c54cfa28fcda0 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/ProductActions.php @@ -11,7 +11,7 @@ use Magento\Framework\UrlInterface; /** - * Class ProductActions + * Class ProductActions for Listing Columns * * @api * @since 100.0.2 @@ -60,7 +60,6 @@ public function prepareDataSource(array $dataSource) ), 'label' => __('Edit'), 'hidden' => false, - '__disableTmpl' => true ]; } } diff --git a/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php b/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php index f770f6b9c497f..be6de45e20de2 100644 --- a/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php +++ b/app/code/Magento/Catalog/Ui/Component/Product/MassAction.php @@ -13,7 +13,7 @@ use Magento\Ui\Component\AbstractComponent; /** - * Class MassAction + * Class MassAction for Component Product */ class MassAction extends AbstractComponent { @@ -53,7 +53,7 @@ public function prepare() : void $actionType = $actionComponent->getConfiguration()['type']; if ($this->isActionAllowed($actionType)) { // phpcs:ignore Magento2.Performance.ForeachArrayMerge - $config['actions'][] = array_merge($actionComponent->getConfiguration(), ['__disableTmpl' => true]); + $config['actions'][] = array_merge($actionComponent->getConfiguration()); } } $origConfig = $this->getConfiguration(); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php index f19f5abd0b423..174a01b72a109 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php @@ -26,7 +26,7 @@ use Magento\Framework\Stdlib\ArrayManager; /** - * Class AdvancedPricing + * Class for Product Modifier Advanced Pricing * * @api * @@ -559,6 +559,7 @@ private function getTierPriceStructure($tierPricePath) ], 'imports' => [ 'priceValue' => '${ $.provider }:data.product.price', + '__disableTmpl' => ['priceValue' => false], ], ], ], @@ -664,6 +665,7 @@ private function customizeAdvancedPricing() 'actions' => [ [ 'targetName' => '${ $.name }', + '__disableTmpl' => ['targetName' => false], 'actionName' => 'actionDone' ] ] diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php index 53c9595b59e76..cb87e4dc9910f 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AttributeSet.php @@ -80,13 +80,6 @@ public function getOptions() $collectionData = $collection->getData() ?? []; - array_walk( - $collectionData, - function (&$attribute) { - $attribute['__disableTmpl'] = true; - } - ); - return $collectionData; } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php index a6b9856a4a0ed..5690e4af72375 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php @@ -13,7 +13,7 @@ use Magento\Ui\Component\Container; /** - * Class Attributes + * Class for Product Modifier Attributes * * @api * @since 101.0.0 @@ -138,6 +138,7 @@ private function customizeAddAttributeModal(array $meta) 'actions' => [ [ 'targetName' => '${ $.name }', + '__disableTmpl' => ['targetName' => false], 'actionName' => 'actionCancel' ] ] @@ -148,6 +149,7 @@ private function customizeAddAttributeModal(array $meta) 'actions' => [ [ 'targetName' => '${ $.name }.product_attributes_grid', + '__disableTmpl' => ['targetName' => false], 'actionName' => 'save' ], [ @@ -271,6 +273,7 @@ private function customizeCreateAttributeModal(array $meta) 'externalProvider' => 'product_attribute_add_form' . '.product_attribute_add_form_data_source', 'toolbarContainer' => '${ $.parentName }', + '__disableTmpl' => ['toolbarContainer' => false], 'formSubmitType' => 'ajax', 'saveUrl' => $this->urlBuilder->getUrl('catalog/product_attribute/save', $params), 'validateUrl' => $this->urlBuilder->getUrl( @@ -281,11 +284,17 @@ private function customizeCreateAttributeModal(array $meta) 'productType' => $this->locator->getProduct()->getTypeId(), 'imports' => [ 'attributeSetId' => '${ $.provider }:data.product.attribute_set_id', + '__disableTmpl' => ['attributeSetId' => false], ], 'exports' => [ 'saveUrl' => '${ $.externalProvider }:client.urls.save', 'validateUrl' => '${ $.externalProvider }:client.urls.beforeSave', 'attributeSetId' => '${ $.externalProvider }:params.set', + '__disableTmpl' => [ + 'saveUrl' => false, + 'validateUrl' => false, + 'attributeSetId' => false + ], ] ] ] @@ -321,6 +330,7 @@ private function customizeAttributesGrid(array $meta) 'externalFilterMode' => true, 'dataLinks' => ['imports' => false, 'exports' => false], 'formProvider' => 'ns = ${ $.namespace }, index = product_form', + '__disableTmpl' => ['selectionsProvider' => false, 'formProvider' => false], 'groupCode' => static::GROUP_CODE, 'groupName' => static::GROUP_NAME, 'groupSortOrder' => static::GROUP_SORT_ORDER, @@ -330,10 +340,12 @@ private function customizeAttributesGrid(array $meta) 'productType' => $this->locator->getProduct()->getTypeId(), 'loading' => false, 'imports' => [ - 'attributeSetId' => '${ $.provider }:data.product.attribute_set_id' + 'attributeSetId' => '${ $.provider }:data.product.attribute_set_id', + '__disableTmpl' => ['attributeSetId' => false], ], 'exports' => [ - 'attributeSetId' => '${ $.externalProvider }:params.template_id' + 'attributeSetId' => '${ $.externalProvider }:params.template_id', + '__disableTmpl' => ['attributeSetId' => false], ] ], ], diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php index cd1f8e8e3379b..7608173c8edfc 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php @@ -210,6 +210,7 @@ protected function createNewCategoryModal(array $meta) 'ns' => 'new_category_form', 'externalProvider' => 'new_category_form.new_category_form_data_source', 'toolbarContainer' => '${ $.parentName }', + '__disableTmpl' => ['toolbarContainer' => false], 'formSubmitType' => 'ajax', ], ], diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php index 65792b395dc06..2e1ea59573c3b 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php @@ -351,6 +351,7 @@ protected function getHeaderContainerConfig($sortOrder) [ 'targetName' => '${ $.ns }.${ $.ns }.' . static::GROUP_CUSTOM_OPTIONS_NAME . '.' . static::GRID_OPTIONS_NAME, + '__disableTmpl' => ['targetName' => false], 'actionName' => 'processingAddChild', ] ] @@ -388,7 +389,10 @@ protected function getOptionsGridConfig($sortOrder) 'collapsibleHeader' => true, 'sortOrder' => $sortOrder, 'dataProvider' => static::CUSTOM_OPTIONS_LISTING, - 'imports' => ['insertData' => '${ $.provider }:${ $.dataProvider }'], + 'imports' => [ + 'insertData' => '${ $.provider }:${ $.dataProvider }', + '__disableTmpl' => ['insertData' => false], + ], ], ], ], @@ -513,7 +517,8 @@ protected function getImportOptionsModalConfig() 'exports' => true ], 'exports' => [ - 'currentProductId' => '${ $.externalProvider }:params.current_product_id' + 'currentProductId' => '${ $.externalProvider }:params.current_product_id', + '__disableTmpl' => ['currentProductId' => false], ] ], ], @@ -559,7 +564,8 @@ protected function getCommonContainerConfig($sortOrder) 'valueUpdate' => 'input', 'imports' => [ 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', - 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default', + '__disableTmpl' => ['optionId' => false, 'isUseDefault' => false], ] ], ], @@ -638,7 +644,8 @@ protected function getSelectTypeGridConfig($sortOrder) 'imports' => [ 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', 'optionTypeId' => '${ $.provider }:${ $.parentScope }.option_type_id', - 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default', + '__disableTmpl' => ['optionId' => false, 'optionTypeId' => false, 'isUseDefault' => false], ], 'service' => [ 'template' => 'Magento_Catalog/form/element/helper/custom-option-type-service', diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index 25e816f79639a..0295e778f2b9b 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -686,7 +686,6 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC 'scopeLabel' => $this->getScopeLabel($attribute), 'globalScope' => $this->isScopeGlobal($attribute), 'sortOrder' => $sortOrder * self::SORT_ORDER_MULTIPLIER, - '__disableTmpl' => ['label' => true, 'code' => true] ] ); $product = $this->locator->getProduct(); @@ -861,7 +860,6 @@ public function setupAttributeContainerMeta(ProductAttributeInterface $attribute 'breakLine' => false, 'label' => $attribute->getDefaultFrontendLabel(), 'required' => $attribute->getIsRequired(), - '__disableTmpl' => ['label' => true] ] ); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php index ebc0425be0188..371022e70123d 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php @@ -228,7 +228,8 @@ protected function customizeWeightField(array $meta) 'addafter' => $this->locator->getStore()->getConfig('general/locale/weight_unit'), 'imports' => $disabled ? [] : [ 'disabled' => '!${$.provider}:' . self::DATA_SCOPE_PRODUCT - . '.product_has_weight:value' + . '.product_has_weight:value', + '__disableTmpl' => ['disabled' => false], ] ] ); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php index b4acb93dcd14f..fb1c310a8a2b4 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php @@ -25,7 +25,7 @@ use Magento\Catalog\Model\Product\Attribute\Source\Status; /** - * Class Related + * Class for Product Modifier Related * * @api * @@ -143,7 +143,8 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -182,7 +183,8 @@ public function modifyMeta(array $meta) } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ public function modifyData(array $data) @@ -528,10 +530,12 @@ protected function getGenericModal(Phrase $title, $scope) 'imports' => [ 'productId' => '${ $.provider }:data.product.current_product_id', 'storeId' => '${ $.provider }:data.product.current_store_id', + '__disableTmpl' => ['productId' => false, 'storeId' => false], ], 'exports' => [ 'productId' => '${ $.externalProvider }:params.current_product_id', 'storeId' => '${ $.externalProvider }:params.current_store_id', + '__disableTmpl' => ['productId' => false, 'storeId' => false], ] ], ], @@ -582,7 +586,8 @@ protected function getGrid($scope) 'thumbnail' => 'thumbnail_src', ], 'links' => [ - 'insertData' => '${ $.provider }:${ $.dataProvider }' + 'insertData' => '${ $.provider }:${ $.dataProvider }', + '__disableTmpl' => ['insertData' => false], ], 'sortOrder' => 2, ], diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php index 9c5fffc5db9b9..c64d3e2e4effb 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php @@ -138,6 +138,10 @@ private function getUpdatedTierPriceStructure(array $priceMeta) . ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PRICE, ProductPriceOptionsInterface::VALUE_PERCENT => '${ $.parentName }.' . ProductAttributeInterface::CODE_TIER_PRICE_FIELD_PERCENTAGE_VALUE, + '__disableTmpl' => [ + ProductPriceOptionsInterface::VALUE_FIXED => false, + ProductPriceOptionsInterface::VALUE_PERCENT => false, + ], ], ], ], diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php index de204b312a3fd..430b6c004e772 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php @@ -259,7 +259,8 @@ protected function getDynamicRow($websiteId, $sortOrder) 'columnsHeader' => true, 'dndConfig' => ['enabled' => false], 'imports' => [ - 'visible' => '${$.namespace}.${$.namespace}.websites.' . $websiteId . ':checked' + 'visible' => '${$.namespace}.${$.namespace}.websites.' . $websiteId . ':checked', + '__disableTmpl' => ['visible' => false], ], 'itemTemplate' => 'record', 'dataScope' => '', diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/tier-price/value-type-select.js b/app/code/Magento/Catalog/view/adminhtml/web/js/tier-price/value-type-select.js index e1a15a93e793d..ff8a3586d0077 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/tier-price/value-type-select.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/tier-price/value-type-select.js @@ -19,8 +19,9 @@ define([ * {@inheritdoc} */ initialize: function () { - this._super() - .prepareForm(); + this._super(); + delete this.prices.__disableTmpl; + this.prepareForm(); }, /** diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml index 89dcef49d2bac..c8b35e4dc5aa6 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml @@ -242,8 +242,8 @@ $_item = null; <?php // phpcs:disable ?> <div class="product actions product-item-actions"> <?php if ($showCart):?> + <div class="actions-primary"> <?php if ($_item->isSaleable()):?> - <div class="actions-primary"> <?php if (!$_item->getTypeInstance()->isPossibleBuyFromList($_item)):?> <button class="action tocart primary" diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml index 2b4609f1219df..97eac1ff723a0 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml @@ -80,15 +80,10 @@ <requiredEntity createDataKey="secondSimpleProductForFixedWithAttribute"/> </createData> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> - - <!-- Login as admin --> + <magentoCron stepKey="runCron"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete products creations --> <deleteData createDataKey="createDynamicBundleProduct" stepKey="deleteDynamicBundleProduct"/> <deleteData createDataKey="firstSimpleProductForDynamic" stepKey="deleteFirstSimpleProductForDynamic"/> <deleteData createDataKey="secondSimpleProductForDynamic" stepKey="deleteSecondSimpleProductForDynamic"/> @@ -100,10 +95,7 @@ <deleteData createDataKey="secondSimpleProductForFixedWithAttribute" stepKey="deleteSecondSimpleProductForFixedWithAttribute"/> <deleteData createDataKey="createProductAttribute" stepKey="deleteProductAttribute"/> - <!-- Log out --> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - - <!-- Reindex invalidated indices after product attribute has been created/deleted --> <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> @@ -113,10 +105,6 @@ <!-- Export created below products --> <actionGroup ref="ExportAllProductsActionGroup" stepKey="exportCreatedProducts"/> - <!-- Run cron --> - <magentoCLI command="cron:run" stepKey="runCron3"/> - <magentoCLI command="cron:run" stepKey="runCron4"/> - <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> <!-- Download product --> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml index 3449a39679666..5078fa5c571db 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml @@ -48,20 +48,13 @@ <requiredEntity createDataKey="createSecondSimpleProduct"/> </updateData> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> - - <!-- Login as admin --> + <magentoCron stepKey="runCron"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> - <!-- Deleted created products --> <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> - - <!-- Delete category --> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <!-- Log out --> @@ -75,9 +68,7 @@ <!-- Export created below products --> <actionGroup ref="ExportAllProductsActionGroup" stepKey="exportCreatedProducts"/> - <!-- Run cron --> - <magentoCLI command="cron:run" stepKey="runCron3"/> - <magentoCLI command="cron:run" stepKey="runCron4"/> + <magentoCron stepKey="runCronIndex" groups="index"/> <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index 64b1680d2e9a1..8eba6a39f6199 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -126,7 +126,8 @@ <requiredEntity createDataKey="createConfigChildProduct"/> </createData> - <!-- Login as admin --> + <magentoCron stepKey="runCronIndex" groups="index"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> @@ -151,8 +152,6 @@ <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGridColumnsInitial"/> <!-- Admin logout--> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> - - <!-- Reindex invalidated indices after product attribute has been created/deleted --> <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml index ac730a6c6a7cc..44f7b91324025 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml @@ -73,11 +73,7 @@ <requiredEntity createDataKey="createConfigSecondChildProduct"/> </createData> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> - - <!-- Login as admin --> + <magentoCron stepKey="runCronIndex" groups="index"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> @@ -88,10 +84,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Log out --> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - - <!-- Reindex invalidated indices after product attribute has been created/deleted --> <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> @@ -105,9 +98,7 @@ <argument name="attributeData" value="$$createConfigProduct.sku$$"/> </actionGroup> - <!-- Run cron --> - <magentoCLI command="cron:run" stepKey="runCron3"/> - <magentoCLI command="cron:run" stepKey="runCron4"/> + <magentoCron stepKey="runCron"/> <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml index a0a3efdedadc8..744e51bfe8896 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml @@ -89,11 +89,7 @@ <requiredEntity createDataKey="createConfigProduct"/> </createData> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> - - <!-- Login as admin --> + <magentoCron stepKey="runCronIndex" groups="index"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> @@ -104,10 +100,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Log out --> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - - <!-- Reindex invalidated indices after product attribute has been created/deleted --> <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> @@ -120,9 +113,7 @@ <argument name="attributeData" value="$$createConfigProduct.sku$$"/> </actionGroup> - <!-- Run cron --> - <magentoCLI command="cron:run" stepKey="runCron3"/> - <magentoCLI command="cron:run" stepKey="runCron4"/> + <magentoCron stepKey="runCron"/> <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml index f9669dfbdac27..09f37a10fb14d 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml @@ -71,11 +71,7 @@ <requiredEntity createDataKey="createConfigSecondChildProduct"/> </createData> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> - - <!-- Login as admin --> + <magentoCron stepKey="runCronIndex" groups="index"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> @@ -90,10 +86,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Log out --> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - - <!-- Reindex invalidated indices after product attribute has been created/deleted --> <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> @@ -104,9 +97,7 @@ <!-- Export created below products --> <actionGroup ref="ExportAllProductsActionGroup" stepKey="exportCreatedProducts"/> - <!-- Run cron --> - <magentoCLI command="cron:run" stepKey="runCron3"/> - <magentoCLI command="cron:run" stepKey="runCron4"/> + <magentoCron stepKey="runCron"/> <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml index 2e8e62667b314..8cea7985aa6d7 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml @@ -28,11 +28,7 @@ <requiredEntity createDataKey="createAttributeSet"/> </createData> - <!-- Run cron twice --> - <magentoCLI command="cron:run" stepKey="runCron1"/> - <magentoCLI command="cron:run" stepKey="runCron2"/> - - <!-- Login as admin --> + <magentoCron stepKey="runCron"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> @@ -52,9 +48,7 @@ <!-- Export created below products --> <actionGroup ref="ExportAllProductsActionGroup" stepKey="exportCreatedProducts"/> - <!-- Run cron --> - <magentoCLI command="cron:run" stepKey="runCron3"/> - <magentoCLI command="cron:run" stepKey="runCron4"/> + <magentoCron stepKey="runCronIndex" groups="index"/> <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php index 53f00529b9bcc..31b2ada809823 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php @@ -6,13 +6,21 @@ namespace Magento\CatalogInventory\Model\ResourceModel; +use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Model\Configuration; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Context; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; /** * Stock resource model */ -class Stock extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb implements QtyCounterInterface +class Stock extends AbstractDb implements QtyCounterInterface { /** * @var StockConfigurationInterface @@ -64,12 +72,12 @@ class Stock extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb impleme /** * Core store config * - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ protected $_scopeConfig; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime + * @var DateTime */ protected $dateTime; @@ -80,17 +88,17 @@ class Stock extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb impleme protected $storeManager; /** - * @param \Magento\Framework\Model\ResourceModel\Db\Context $context - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param Context $context + * @param ScopeConfigInterface $scopeConfig + * @param DateTime $dateTime * @param StockConfigurationInterface $stockConfiguration * @param StoreManagerInterface $storeManager * @param string $connectionName */ public function __construct( - \Magento\Framework\Model\ResourceModel\Db\Context $context, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, + Context $context, + ScopeConfigInterface $scopeConfig, + DateTime $dateTime, StockConfigurationInterface $stockConfiguration, StoreManagerInterface $storeManager, $connectionName = null @@ -125,9 +133,18 @@ public function lockProductsStock(array $productIds, $websiteId) return []; } $itemTable = $this->getTable('cataloginventory_stock_item'); - $select = $this->getConnection()->select()->from(['si' => $itemTable]) + + //get indexed entries for row level lock instead of table lock + $itemIds = []; + $preSelect = $this->getConnection()->select()->from($itemTable, 'item_id') ->where('website_id = ?', $websiteId) - ->where('product_id IN(?)', $productIds) + ->where('product_id IN(?)', $productIds); + 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) ->forUpdate(true); $productTable = $this->getTable('catalog_product_entity'); @@ -147,12 +164,12 @@ public function lockProductsStock(array $productIds, $websiteId) foreach ($this->getConnection()->fetchAll($selectProducts) as $p) { $items[$p['product_id']]['type_id'] = $p['type_id']; } - + return $items; } /** - * {@inheritdoc} + * @inheritdoc */ public function correctItemsQty(array $items, $websiteId, $operator) { @@ -185,16 +202,16 @@ protected function _initConfig() { if (!$this->_isConfig) { $configMap = [ - '_isConfigManageStock' => \Magento\CatalogInventory\Model\Configuration::XML_PATH_MANAGE_STOCK, - '_isConfigBackorders' => \Magento\CatalogInventory\Model\Configuration::XML_PATH_BACKORDERS, - '_configMinQty' => \Magento\CatalogInventory\Model\Configuration::XML_PATH_MIN_QTY, - '_configNotifyStockQty' => \Magento\CatalogInventory\Model\Configuration::XML_PATH_NOTIFY_STOCK_QTY, + '_isConfigManageStock' => Configuration::XML_PATH_MANAGE_STOCK, + '_isConfigBackorders' => Configuration::XML_PATH_BACKORDERS, + '_configMinQty' => Configuration::XML_PATH_MIN_QTY, + '_configNotifyStockQty' => Configuration::XML_PATH_NOTIFY_STOCK_QTY, ]; foreach ($configMap as $field => $const) { $this->{$field} = (int) $this->_scopeConfig->getValue( $const, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE ); } @@ -317,11 +334,11 @@ public function updateLowStockDate($website) /** * Add low stock filter to product collection * - * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + * @param Collection $collection * @param array $fields * @return $this */ - public function addLowStockFilter(\Magento\Catalog\Model\ResourceModel\Product\Collection $collection, $fields) + public function addLowStockFilter(Collection $collection, $fields) { $this->_initConfig(); $connection = $collection->getSelect()->getConnection(); @@ -344,14 +361,14 @@ public function addLowStockFilter(\Magento\Catalog\Model\ResourceModel\Product\C $where = []; foreach ($conditions as $k => $part) { - $where[$k] = join(' ' . \Magento\Framework\DB\Select::SQL_AND . ' ', $part); + $where[$k] = join(' ' . Select::SQL_AND . ' ', $part); } $where = $connection->prepareSqlCondition( 'invtr.low_stock_date', ['notnull' => true] - ) . ' ' . \Magento\Framework\DB\Select::SQL_AND . ' ((' . join( - ') ' . \Magento\Framework\DB\Select::SQL_OR . ' (', + ) . ' ' . Select::SQL_AND . ' ((' . join( + ') ' . Select::SQL_OR . ' (', $where ) . '))'; diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml index 9db6f250dc114..5be8ad25b9658 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml @@ -23,6 +23,7 @@ <before> <createData entity="DefaultValueForMaxSaleQty" stepKey="setDefaultValueForMaxSaleQty"/> <createData entity="SimpleProduct2" stepKey="createdProduct"/> + <magentoCron stepKey="runCronIndex" groups="index"/> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> </before> <after> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml index 95d3943d04b80..2cdb2413122bd 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml @@ -77,6 +77,7 @@ <createData entity="Simple_US_Customer" stepKey="createSimpleUsCustomer"> <field key="group_id">1</field> </createData> + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> @@ -103,7 +104,7 @@ <selectOption userInput="$$createConfigProductAttributeOption1.option[store_labels][1][label]$$" selector="{{StorefrontProductInfoMainSection.optionByAttributeId($$createConfigProductAttribute.attribute_id$$)}}" stepKey="configProductFillOption" /> <click stepKey="addSimpleProductToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage"/> <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php index c9a148f3d8869..d5c1570fea510 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/StockTest.php @@ -7,73 +7,78 @@ namespace Magento\CatalogInventory\Test\Unit\Model\ResourceModel; -use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\CatalogInventory\Model\Configuration as StockConfiguration; use Magento\CatalogInventory\Model\ResourceModel\Stock; use Magento\Framework\App\Config; use Magento\Framework\DB\Adapter\Pdo\Mysql; +use Magento\Framework\DB\Select; use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * Test for \Magento\CatalogInventory\Model\ResourceModel\Stock */ -class StockTest extends \PHPUnit\Framework\TestCase +class StockTest extends TestCase { const PRODUCT_TABLE = 'testProductTable'; const ITEM_TABLE = 'testItemTableName'; /** - * @var Stock|\PHPUnit_Framework_MockObject_MockObject + * @var Stock|MockObject */ private $stock; /** - * @var Mysql|\PHPUnit_Framework_MockObject_MockObject + * @var Mysql|MockObject */ private $connectionMock; /** - * @var Config|\PHPUnit_Framework_MockObject_MockObject + * @var Config|MockObject */ private $scopeConfigMock; /** - * @var DateTime|\PHPUnit_Framework_MockObject_MockObject + * @var DateTime|MockObject */ private $dateTimeMock; /** - * @var StockConfiguration|\PHPUnit_Framework_MockObject_MockObject + * @var StockConfiguration|MockObject */ private $stockConfigurationMock; /** - * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|MockObject */ private $storeManagerMock; /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|MockObject */ private $contextMock; /** - * @var \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject + * @var Select|MockObject */ private $selectMock; /** - * @var \Zend_Db_Statement_Interface|\PHPUnit_Framework_MockObject_MockObject + * @var \Zend_Db_Statement_Interface|MockObject */ private $statementMock; - + + /** + * @inheritdoc + */ protected function setUp() { $objectManager = new ObjectManager($this); - $this->selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + $this->selectMock = $this->getMockBuilder(Select::class) ->disableOriginalConstructor() ->getMock(); $this->contextMock = $objectManager->getObject(Context::class); @@ -115,23 +120,35 @@ protected function setUp() * @param array $productIds * @param array $products * @param array $result + * @param array $items * * @return void */ - public function testLockProductsStock(int $websiteId, array $productIds, array $products, array $result) - { - $this->selectMock->expects($this->exactly(2)) + public function testLockProductsStock( + int $websiteId, + array $productIds, + array $products, + array $result, + array $items + ) { + $itemIds = []; + foreach ($items as $item) { + $itemIds[] = $item['item_id']; + } + $this->selectMock->expects($this->exactly(3)) ->method('from') ->withConsecutive( + [$this->identicalTo(self::ITEM_TABLE)], [$this->identicalTo(['si' => self::ITEM_TABLE])], [$this->identicalTo(['p' => self::PRODUCT_TABLE]), $this->identicalTo([])] ) ->willReturnSelf(); - $this->selectMock->expects($this->exactly(3)) + $this->selectMock->expects($this->exactly(4)) ->method('where') ->withConsecutive( [$this->identicalTo('website_id = ?'), $this->identicalTo($websiteId)], [$this->identicalTo('product_id IN(?)'), $this->identicalTo($productIds)], + [$this->identicalTo('item_id IN (?)'), $this->identicalTo($itemIds)], [$this->identicalTo('entity_id IN (?)'), $this->identicalTo($productIds)] ) ->willReturnSelf(); @@ -143,14 +160,17 @@ public function testLockProductsStock(int $websiteId, array $productIds, array $ ->method('columns') ->with($this->identicalTo(['product_id' => 'entity_id', 'type_id' => 'type_id'])) ->willReturnSelf(); - $this->connectionMock->expects($this->exactly(2)) + $this->connectionMock->expects($this->exactly(3)) ->method('select') ->willReturn($this->selectMock); - $this->connectionMock->expects($this->once()) + $this->connectionMock->expects($this->exactly(2)) ->method('query') ->with($this->identicalTo($this->selectMock)) ->willReturn($this->statementMock); - $this->statementMock->expects($this->once()) + $this->statementMock->expects($this->at(0)) + ->method('fetchAll') + ->willReturn($items); + $this->statementMock->expects($this->at(1)) ->method('fetchAll') ->willReturn($products); $this->connectionMock->expects($this->once()) @@ -166,7 +186,7 @@ public function testLockProductsStock(int $websiteId, array $productIds, array $ self::ITEM_TABLE, self::PRODUCT_TABLE )); - $this->stock->expects($this->exactly(4)) + $this->stock->expects($this->exactly(6)) ->method('getConnection') ->willReturn($this->connectionMock); @@ -203,6 +223,7 @@ public function productsDataProvider(): array 'type_id' => 'simple', ], ], + [['item_id' => 1], ['item_id' => 2], ['item_id' => 3]] ], ]; } diff --git a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php index 38a33b75f552a..64db51132dc36 100644 --- a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php +++ b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php @@ -186,7 +186,6 @@ private function prepareMeta() if ($pathField) { $fieldsetPath = $this->arrayManager->slicePath($pathField, 0, -4); - $this->meta = $this->arrayManager->merge( $pathField . '/arguments/data/config', $this->meta, @@ -197,6 +196,7 @@ private function prepareMeta() 'scopeLabel' => '[GLOBAL]', 'imports' => [ 'visible' => '${$.provider}:data.product.stock_data.manage_stock', + '__disableTmpl' => ['visible' => false], ], ] ); @@ -240,6 +240,7 @@ private function prepareMeta() ], 'imports' => [ 'handleChanges' => '${$.provider}:data.product.stock_data.is_qty_decimal', + '__disableTmpl' => ['handleChanges' => false], ], 'sortOrder' => 10, 'disabled' => $this->locator->getProduct()->isLockedAttribute($fieldCode), @@ -269,7 +270,6 @@ private function prepareMeta() 'qty' => $qty, 'advanced_inventory_button' => $advancedInventoryButton, ]; - $this->meta = $this->arrayManager->merge( $fieldsetPath . '/children', $this->meta, diff --git a/app/code/Magento/CatalogInventory/etc/db_schema.xml b/app/code/Magento/CatalogInventory/etc/db_schema.xml index b5c4a96f24a94..3747f3f89633f 100644 --- a/app/code/Magento/CatalogInventory/etc/db_schema.xml +++ b/app/code/Magento/CatalogInventory/etc/db_schema.xml @@ -87,6 +87,10 @@ <index referenceId="CATALOGINVENTORY_STOCK_ITEM_WEBSITE_ID" indexType="btree"> <column name="website_id"/> </index> + <index referenceId="CATALOGINVENTORY_STOCK_ITEM_WEBSITE_ID_PRODUCT_ID" indexType="btree"> + <column name="website_id"/> + <column name="product_id"/> + </index> <index referenceId="CATALOGINVENTORY_STOCK_ITEM_STOCK_ID" indexType="btree"> <column name="stock_id"/> </index> diff --git a/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json b/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json index 2580ec1e336f1..fd881ac6e521c 100644 --- a/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json +++ b/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json @@ -43,6 +43,7 @@ }, "index": { "CATALOGINVENTORY_STOCK_ITEM_WEBSITE_ID": true, + "CATALOGINVENTORY_STOCK_ITEM_WEBSITE_ID_PRODUCT_ID": true, "CATALOGINVENTORY_STOCK_ITEM_STOCK_ID": true }, "constraint": { @@ -123,4 +124,4 @@ "PRIMARY": true } } -} \ No newline at end of file +} diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml new file mode 100644 index 0000000000000..1170b08b1add9 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.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="AdminCatalogPriceRuleDeleteAllActionGroup"> + <annotations> + <description>Open Catalog Price Rule grid and delete all rules one by one. Need to avoid interference with other tests that test catalog price rules.</description> + </annotations> + + <amOnPage url="{{AdminCatalogPriceRuleGridPage.url}}" stepKey="goToAdminCatalogPriceRuleGridPage"/> + <!-- It sometimes is loading too long for default 10s --> + <waitForPageLoad time="60" stepKey="waitForPageFullyLoaded"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingFilters"/> + <helper class="\Magento\CatalogRule\Test\Mftf\Helper\CatalogPriceRuleHelper" method="deleteAllCatalogPriceRules" stepKey="deleteAllCatalogPriceRulesOneByOne"> + <argument name="firstNotEmptyRow">{{AdminDataGridTableSection.firstNotEmptyRow}}</argument> + <argument name="modalAcceptButton">{{AdminConfirmationModalSection.ok}}</argument> + <argument name="deleteButton">{{AdminMainActionsSection.delete}}</argument> + <argument name="successMessageContainer">{{AdminMessagesSection.success}}</argument> + <argument name="successMessage">You deleted the rule.</argument> + </helper> + <waitForElementVisible selector="{{AdminDataGridTableSection.dataGridEmpty}}" stepKey="waitDataGridEmptyMessageAppears"/> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Helper/CatalogPriceRuleHelper.php b/app/code/Magento/CatalogRule/Test/Mftf/Helper/CatalogPriceRuleHelper.php new file mode 100644 index 0000000000000..2119f5c6ca45d --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Helper/CatalogPriceRuleHelper.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogRule\Test\Mftf\Helper; + +use Facebook\WebDriver\Remote\RemoteWebDriver as FacebookWebDriver; +use Facebook\WebDriver\WebDriverBy; +use Magento\FunctionalTestingFramework\Helper\Helper; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; + +/** + * Class for MFTF helpers for CatalogRule module. + */ +class CatalogPriceRuleHelper extends Helper +{ + /** + * Delete all Catalog Price Rules obe by one. + * + * @param string $emptyRow + * @param string $modalAceptButton + * @param string $deleteButton + * @param string $successMessageContainer + * @param string $successMessage + * + * @return void + */ + public function deleteAllCatalogPriceRules( + string $firstNotEmptyRow, + string $modalAcceptButton, + string $deleteButton, + string $successMessageContainer, + string $successMessage + ): void { + try { + /** @var MagentoWebDriver $webDriver */ + $magentoWebDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + /** @var FacebookWebDriver $webDriver */ + $webDriver = $magentoWebDriver->webDriver; + $rows = $webDriver->findElements(WebDriverBy::cssSelector($firstNotEmptyRow)); + while (!empty($rows)) { + $rows[0]->click(); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->click($deleteButton); + $magentoWebDriver->waitForPageLoad(30); + $magentoWebDriver->waitForElementVisible($modalAcceptButton, 10); + $magentoWebDriver->waitForPageLoad(60); + $magentoWebDriver->click($modalAcceptButton); + $magentoWebDriver->waitForPageLoad(60); + $magentoWebDriver->waitForLoadingMaskToDisappear(); + $magentoWebDriver->waitForElementVisible($successMessageContainer, 10); + $magentoWebDriver->see($successMessage, $successMessageContainer); + $rows = $webDriver->findElements(WebDriverBy::cssSelector($firstNotEmptyRow)); + } + } catch (\Exception $e) { + $this->fail($e->getMessage()); + } + } +} diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml index 4d97338333be8..d1f9ebd4c99a4 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml @@ -26,7 +26,7 @@ <createData entity="ApiSimpleProduct" stepKey="createSimpleProductTwo"> <requiredEntity createDataKey="createCategoryTwo"/> </createData> - + <magentoCron stepKey="runCronIndex" groups="index"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml index 9cc0090fef756..882a92a2ee433 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml @@ -74,6 +74,7 @@ <requiredEntity createDataKey="createConfigProduct"/> <requiredEntity createDataKey="createSecondConfigChildProduct"/> </createData> + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> <!-- Delete the catalog price rule --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml index 8c3d1dad1b2a3..fcae0065f1b53 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml @@ -63,8 +63,7 @@ <!-- Add the product to cart and check that the price is correct there --> <click stepKey="addToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> <waitForPageLoad stepKey="waitForAddedToCart"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> <see stepKey="seeNewPriceInCart" selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$110.70"/> </test> </tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml index 49a9d5e8ae51b..59fa4fde1c88a 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml @@ -84,7 +84,7 @@ <see selector="{{StorefrontMinicartSection.productPriceByName($$createProduct1.name$$)}}" userInput="$$createProduct1.price$$" stepKey="seeCorrectProductPrice1"/> <!-- Assert that the rule isn't present on the Checkout page --> - <click selector="{{StorefrontMiniCartSection.goToCheckout}}" stepKey="goToCheckout1"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout1"/> <conditionalClick selector="{{CheckoutCartSummarySection.expandShoppingCartSummary}}" dependentSelector="{{CheckoutCartSummarySection.expandShoppingCartSummary}}" visible="true" stepKey="expandShoppingCartSummary1"/> <see selector="{{CheckoutCartProductSection.ProductRegularPriceByName($$createProduct1.name$$)}}" userInput="$$createProduct1.price$$" stepKey="seeCorrectProductPriceOnCheckout1"/> </test> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml index c6b3569f3b597..69508490774dd 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml @@ -19,12 +19,11 @@ <group value="CatalogRule"/> </annotations> <before> - <!-- Create a category --> <createData entity="ApiCategory" stepKey="createCategory"/> - <!-- Create a simple product --> <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> <requiredEntity createDataKey="createCategory"/> </createData> + <magentoCron stepKey="runCronIndex" groups="index"/> <!-- Login to Admin page --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <!-- Create a configurable product --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml index 973d73da2ab6a..6c436fee808a7 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml @@ -44,6 +44,7 @@ <createData entity="productDropDownAttribute" stepKey="createSecondProductAttribute"> <field key="scope">website</field> </createData> + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> @@ -63,8 +64,7 @@ <deleteData createDataKey="createSecondProductAttribute" stepKey="deleteSecondProductAttribute"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> + <magentoCron stepKey="runCronIndex" groups="index"/> </after> <!--Create catalog price rule--> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml index 0747d42dc8737..1919f7d5cc544 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml @@ -83,7 +83,7 @@ <requiredEntity createDataKey="createConfigProduct"/> <requiredEntity createDataKey="createConfigChildProduct2"/> </createData> - + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml index 101883f5ea9f2..23fc7e1a9ffba 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml @@ -77,6 +77,7 @@ <requiredEntity createDataKey="createConfigProduct"/> <requiredEntity createDataKey="createConfigChildProduct2"/> </createData> + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> <!-- Delete the catalog price rule --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml index 552a6dc48eb7f..dfd34181108b8 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml @@ -33,6 +33,7 @@ <!-- Update all products to have custom options --> <updateData createDataKey="createProduct1" entity="productWithFixedOptions" stepKey="updateProductWithOptions1"/> + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> <!-- Delete products and category --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml index 6e8cb01f64ea2..25351ca650db9 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml @@ -24,20 +24,15 @@ <!-- Create new customer group --> <createData entity="CustomCustomerGroup" stepKey="customerGroup" /> - - <!--Creating customer--> <createData entity="Simple_US_Customer" stepKey="createCustomer" > <field key="group_id">$customerGroup.id$</field> </createData> - - <!-- Create category --> <createData entity="_defaultCategory" stepKey="createCategory"/> - - <!-- Create Simple Product --> <createData entity="_defaultProduct" stepKey="createSimpleProduct"> <requiredEntity createDataKey="createCategory"/> <field key="price">56.78</field> </createData> + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> <!-- Delete products and category --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml index aa20a1d72d063..28564d12ceb31 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml @@ -21,23 +21,15 @@ <before> <!-- Login as Admin --> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - - <!-- Create category --> <createData entity="_defaultCategory" stepKey="createCategory"/> - - <!-- Create Simple Product 1 --> <createData entity="_defaultProduct" stepKey="createProduct1"> <requiredEntity createDataKey="createCategory"/> <field key="price">56.78</field> </createData> - - <!-- Create Simple Product 2 --> <createData entity="_defaultProduct" stepKey="createProduct2"> <requiredEntity createDataKey="createCategory"/> <field key="price">56.78</field> </createData> - - <!-- Create Simple Product 3 --> <createData entity="_defaultProduct" stepKey="createProduct3"> <requiredEntity createDataKey="createCategory"/> <field key="price">56.78</field> @@ -47,6 +39,7 @@ <updateData createDataKey="createProduct1" entity="productWithCustomOptions" stepKey="updateProductWithOptions1"/> <updateData createDataKey="createProduct2" entity="productWithCustomOptions" stepKey="updateProductWithOptions2"/> <updateData createDataKey="createProduct3" entity="productWithCustomOptions" stepKey="updateProductWithOptions3"/> + <magentoCron stepKey="runCronIndex" groups="index"/> </before> <after> <!-- Delete products and category --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml index 46ba6e30100b1..2df891b24223b 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml @@ -60,7 +60,7 @@ <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"> <argument name="productName" value="$createProduct.name$"/> </actionGroup> - <actionGroup ref="StorefrontOpenCartPageActionGroup" stepKey="openCartPage" /> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openCartPage" /> <waitForElementVisible selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="waitForSubtotalAppears"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$createProduct.price$" stepKey="seeProductPriceOnCartPage"/> </test> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml index dec157484dfef..213099d3ba974 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml @@ -57,7 +57,7 @@ <argument name="categoryName" value="admin"/> <argument name="categoryUrlKey" value="{{SimpleSubCategory.name}}"/> </actionGroup> - <see selector="{{AdminMessagesSection.success}}" userInput="You saved the category." stepKey="seeAdminSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="seeAdminSuccessMessage"/> <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('admin')}}" stepKey="seeAdminCategoryInTree"/> <!--Check category creation with restricted url key 'soap'--> <comment userInput="Check category creation with restricted url key 'soap'" stepKey="commentCheckSoapCategoryCreation"/> @@ -78,7 +78,7 @@ <argument name="categoryName" value="soap"/> <argument name="categoryUrlKey" value="{{ApiCategory.name}}"/> </actionGroup> - <see selector="{{AdminMessagesSection.success}}" userInput="You saved the category." stepKey="seeSoapSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="seeSoapSuccessMessage"/> <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('soap')}}" stepKey="seeSoapCategoryInTree"/> <!--Check category creation with restricted url key 'rest'--> <comment userInput="Check category creation with restricted url key 'rest'" stepKey="commentCheckRestCategoryCreation"/> @@ -99,7 +99,7 @@ <argument name="categoryName" value="rest"/> <argument name="categoryUrlKey" value="{{SubCategoryWithParent.name}}"/> </actionGroup> - <see selector="{{AdminMessagesSection.success}}" userInput="You saved the category." stepKey="seeRestSuccessMesdgssage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="seeRestSuccessMesdgssage"/> <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('rest')}}" stepKey="seeRestCategoryInTree"/> <!--Check category creation with restricted url key 'graphql'--> <comment userInput="Check category creation with restricted url key 'graphql'" stepKey="commentCheckGraphQlCategoryCreation"/> @@ -120,7 +120,7 @@ <argument name="categoryName" value="graphql"/> <argument name="categoryUrlKey" value="{{NewSubCategoryWithParent.name}}"/> </actionGroup> - <see selector="{{AdminMessagesSection.success}}" userInput="You saved the category." stepKey="seeGraphQlSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="seeGraphQlSuccessMessage"/> <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('graphql')}}" stepKey="seeGraphQlCategoryInTree"/> </test> </tests> diff --git a/app/code/Magento/CatalogUrlRewrite/Ui/DataProvider/Product/Form/Modifier/ProductUrlRewrite.php b/app/code/Magento/CatalogUrlRewrite/Ui/DataProvider/Product/Form/Modifier/ProductUrlRewrite.php index 10791eae5405f..9567f78414d78 100644 --- a/app/code/Magento/CatalogUrlRewrite/Ui/DataProvider/Product/Form/Modifier/ProductUrlRewrite.php +++ b/app/code/Magento/CatalogUrlRewrite/Ui/DataProvider/Product/Form/Modifier/ProductUrlRewrite.php @@ -125,6 +125,7 @@ protected function addUrlRewriteCheckbox(array $meta) 'handleUseDefault' => '${ $.parentName }.url_key:isUseDefault', 'handleChanges' => '${ $.provider }:data.product.' . ProductAttributeInterface::CODE_SEO_FIELD_URL_KEY, + '__disableTmpl' => ['urlKey' => false, 'handleUseDefault' => false, 'handleChanges' => false], ], 'description' => __('Create Permanent Redirect for old URL'), 'dataScope' => 'url_key_create_redirect', diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml b/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml index 288134a889db0..59f0cd7437f44 100644 --- a/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml +++ b/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOperatorsTest.xml @@ -73,7 +73,7 @@ <selectOption stepKey="selectStaticBlockOnlyOption" userInput="Static block only" selector="{{AdminCategoryDisplaySettingsSection.displayMode}}"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryWithProducts"/> <waitForPageLoad stepKey="waitForCategorySaved"/> - <see userInput="You saved the category." stepKey="seeSuccessMessage"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="seeSuccessMessage"/> <!--Go to Storefront > category--> <amOnPage url="$$simplecategory.name$$.html" stepKey="goToStorefrontCategoryPage"/> diff --git a/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php index 00cc06ea0ff47..16450ec6ff2c2 100644 --- a/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php +++ b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php @@ -299,6 +299,7 @@ private function getBillingAddressComponent($paymentCode, $elements) 'deps' => 'checkoutProvider', 'dataScopePrefix' => 'billingAddress' . $paymentCode, 'billingAddressListProvider' => '${$.name}.billingAddressList', + '__disableTmpl' => ['billingAddressListProvider' => false], 'sortOrder' => 1, 'children' => [ 'billingAddressList' => [ @@ -329,6 +330,7 @@ private function getBillingAddressComponent($paymentCode, $elements) ], 'filterBy' => [ 'target' => '${ $.provider }:${ $.parentScope }.country_id', + '__disableTmpl' => ['target' => false], 'field' => 'country_id', ], ], diff --git a/app/code/Magento/Checkout/CustomerData/DefaultItem.php b/app/code/Magento/Checkout/CustomerData/DefaultItem.php index 21580d1275d0c..6c88e96cd535a 100644 --- a/app/code/Magento/Checkout/CustomerData/DefaultItem.php +++ b/app/code/Magento/Checkout/CustomerData/DefaultItem.php @@ -10,7 +10,7 @@ use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; /** - * Default item + * Default cart item */ class DefaultItem extends AbstractItem { @@ -78,7 +78,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function doGetItemData() { @@ -106,6 +106,7 @@ protected function doGetItemData() ], 'canApplyMsrp' => $this->msrpHelper->isShowBeforeOrderConfirm($this->item->getProduct()) && $this->msrpHelper->isMinimalPriceLessMsrp($this->item->getProduct()), + 'message' => $this->item->getMessage(), ]; } @@ -121,6 +122,8 @@ protected function getOptionList() } /** + * Returns product for thumbnail. + * * @return \Magento\Catalog\Model\Product * @codeCoverageIgnore */ @@ -130,6 +133,8 @@ protected function getProductForThumbnail() } /** + * Returns product. + * * @return \Magento\Catalog\Model\Product * @codeCoverageIgnore */ diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCartPageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCartPageOpenActionGroup.xml similarity index 89% rename from app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCartPageActionGroup.xml rename to app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCartPageOpenActionGroup.xml index fe1e48e00c5bb..67301ce96c40b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontOpenCartPageActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontCartPageOpenActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="StorefrontOpenCartPageActionGroup"> + <actionGroup name="StorefrontCartPageOpenActionGroup"> <amOnPage url="{{CheckoutCartPage.url}}" stepKey="openCartPage" /> <waitForPageLoad stepKey="waitForPageLoaded" /> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml similarity index 97% rename from app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml rename to app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml index 80ed4f90c2cd0..668d33d26f37a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml @@ -36,6 +36,7 @@ <element name="itemDiscount" type="text" selector="//tr[@class='totals']//td[@class='amount']/span"/> <element name="subtotal" type="text" selector="//tr[@class='totals sub']//td[@class='amount']/span"/> <element name="emptyCart" type="text" selector=".counter.qty.empty"/> + <element name="emptyMiniCart" type="text" selector="//div[@class='minicart-wrapper']//span[@class='counter qty empty']/../.."/> <element name="minicartContent" type="block" selector="#minicart-content-wrapper"/> <element name="messageEmptyCart" type="text" selector="//*[@id='minicart-content-wrapper']//*[contains(@class,'subtitle empty')]"/> <element name="visibleItemsCountText" type="text" selector="//div[@class='items-total']"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml index 58a44f5b89f90..561e73bc24f61 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml @@ -48,8 +48,7 @@ </actionGroup> <!--Go to shopping cart--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> - + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnPageShoppingCart"/> <!--Verify country options in checkout top destination section--> <actionGroup ref="VerifyTopDestinationsCountryActionGroup" stepKey="verifyTopDestinationsCountry"> <argument name="country" value="Bahamas"/> @@ -68,7 +67,7 @@ </actionGroup> <!--Go to shopping cart--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart2"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnPageShoppingCart2"/> <!--Verify country options is shown by default--> <actionGroup ref="VerifyTopDestinationsCountryActionGroup" stepKey="verifyTopDestinationsCountry2"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleDynamicProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleDynamicProductFromShoppingCartTest.xml index ef5f5b640b0a2..5fd201290655a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleDynamicProductFromShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleDynamicProductFromShoppingCartTest.xml @@ -60,8 +60,7 @@ </actionGroup> <!-- Remove product from cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> <argument name="productName" value="$$createBundleDynamicProduct.name$$"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleFixedProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleFixedProductFromShoppingCartTest.xml index e141d0628cc4d..603ee1ecea4df 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleFixedProductFromShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteBundleFixedProductFromShoppingCartTest.xml @@ -52,8 +52,7 @@ </actionGroup> <!-- Remove product from cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> <argument name="productName" value="$$createFixedBundleProduct.name$$"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml index 62fa3063de08a..e7b61415723cc 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml @@ -74,8 +74,7 @@ </actionGroup> <!-- Remove product from cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> <argument name="productName" value="$$createConfigProduct.name$$"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteDownloadableProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteDownloadableProductFromShoppingCartTest.xml index 988e3b8d3129d..93fd115ae8577 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteDownloadableProductFromShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteDownloadableProductFromShoppingCartTest.xml @@ -44,8 +44,7 @@ </actionGroup> <!-- Remove product from cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> <argument name="productName" value="$$createDownloadableProduct.name$$"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteGroupedProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteGroupedProductFromShoppingCartTest.xml index eb8e753ea0b79..b82df28ebb95f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteGroupedProductFromShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteGroupedProductFromShoppingCartTest.xml @@ -58,8 +58,7 @@ </actionGroup> <!-- Remove products from cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <click selector="{{CheckoutCartProductSection.removeProductByName($$createFirstSimpleProduct.name$$)}}" stepKey="deleteFirstProductFromCheckoutCart"/> <click selector="{{CheckoutCartProductSection.removeProductByName($$createSecondSimpleProduct.name$$)}}" stepKey="deleteSecondProductFromCheckoutCart"/> <click selector="{{CheckoutCartProductSection.removeProductByName($$createThirdSimpleProduct.name$$)}}" stepKey="deleteThirdProductFromCheckoutCart"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteVirtualProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteVirtualProductFromShoppingCartTest.xml index 969a827a8a461..39b4e66ef9f07 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteVirtualProductFromShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteVirtualProductFromShoppingCartTest.xml @@ -38,8 +38,7 @@ </actionGroup> <!-- Remove product from cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <actionGroup ref="DeleteProductFromShoppingCartActionGroup" stepKey="deleteProduct"> <argument name="productName" value="$$createVirtualProduct.name$$"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml index c3713206ed1d2..e17a940dfa685 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml @@ -10,25 +10,24 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="IdentityOfDefaultBillingAndShippingAddressTest"> <annotations> - <features value="Customer"/> - <title value="Checking assignment of default billing address after placing an orde"/> + <features value="Checkout"/> + <stories value="Customer checkout"/> + <title value="Checking assignment of default billing address after placing an order"/> <description value="In 'Address book' field 'Default Billing Address' should be the same as 'Default Shipping Address'"/> <severity value="MAJOR"/> - <testCaseId value="MAGETWO-94108"/> - <stories value="MAGETWO-62891: New address is not marked as 'Default Billing'"/> + <testCaseId value="MC-25738"/> + <useCaseId value="MAGETWO-62891"/> + <group value="checkout"/> <group value="customer"/> </annotations> <before> - <!--Create product--> - <createData stepKey="category" entity="SimpleSubCategory"/> - <createData stepKey="product" entity="SimpleProduct"> - <requiredEntity createDataKey="category"/> - </createData> + <!-- Create product --> + <createData entity="SimpleProduct2" stepKey="createProduct"/> </before> - <!--Go to Storefront--> - <amOnPage url="" stepKey="DoToStorefront"/> + <!-- Go to Storefront Homepage --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="goToStorefrontHomepage"/> <!-- Fill out form for a new user with address --> <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> @@ -41,38 +40,54 @@ <argument name="message" value="Thank you for registering with Main Website Store."/> </actionGroup> - <!-- Add simple product to cart --> - <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToCart1"> - <argument name="product" value="$$product$$"/> + <!-- Add product to cart --> + <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrlKey" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$createProduct$"/> + <argument name="productCount" value="1"/> </actionGroup> - <!--Proceed to shipment--> - <amOnPage url="{{CheckoutPage.url}}/" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForShippingSection"/> + <!-- Proceed to Checkout --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutPage"/> - <!--Fill shipment form--> + <!-- Fill Shipment form --> <actionGroup ref="LoggedInUserCheckoutFillingShippingSectionActionGroup" stepKey="checkoutFillingShippingSection" > <argument name="customerVar" value="Simple_US_Customer_NY" /> <argument name="customerAddressVar" value="US_Address_NY" /> </actionGroup> - <!--Fill cart data--> + <!-- Fill Cart data --> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyOrderPayment" /> + <checkOption selector="{{StorefrontCheckoutPaymentMethodSection.billingAddressSameAsShipping}}" stepKey="checkBillingAddressSameAsShippingCheckbox"/> + <!-- Place Order --> <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeorder"> <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage" /> <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> </actionGroup> - <!--Go To My Account--> - <amOnPage stepKey="goToMyAccountPage" url="/customer/account/"/> - <!--Assert That Shipping And Billing Address are the same--> + <!-- Go To My Account Page --> + <actionGroup ref="StorefrontOpenMyAccountPageActionGroup" stepKey="goToMyAccountPage" /> + + <!-- Assert That Shipping And Billing Address are the same --> <actionGroup ref="AssertThatShippingAndBillingAddressTheSame" stepKey="assertThatShippingAndBillingAddressTheSame"/> <after> - <!--Delete created Product--> - <deleteData stepKey="deleteProduct" createDataKey="product"/> - <deleteData stepKey="deleteCategory" createDataKey="category"/> + <!-- Delete created Product --> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + + <!-- Logout Customer --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + + <!-- Delete Customer --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomerFromAdmin"> + <argument name="customerEmail" value="Simple_US_Customer_NY.email"/> + </actionGroup> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearCustomersGridFilter"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> </after> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ShoppingCartAndMiniShoppingCartPerCustomerTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ShoppingCartAndMiniShoppingCartPerCustomerTest.xml index 984f8933efa76..571aa24209389 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/ShoppingCartAndMiniShoppingCartPerCustomerTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/ShoppingCartAndMiniShoppingCartPerCustomerTest.xml @@ -115,8 +115,7 @@ <argument name="Customer" value="$$createFirstCustomer$$"/> </actionGroup> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> - <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnPageShoppingCart"/> <!-- Assert first products present in shopping cart --> <actionGroup ref="StorefrontCheckCartSimpleProductActionGroup" stepKey="checkFirstProductInCart"> @@ -155,8 +154,7 @@ </actionGroup> <!-- Assert first products present in shopping cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnShoppingCartPage"/> - <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnShoppingCartPage"/> <actionGroup ref="StorefrontCheckCartSimpleProductActionGroup" stepKey="checkProductInCart"> <argument name="product" value="$$createSimpleProduct$$"/> <argument name="productQuantity" value="quoteQty2Price123.qty"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml index 9e5542838745a..a5c8eb0da6530 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml @@ -78,7 +78,7 @@ <!-- Verify mini cart is empty --> <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="assertEmptryCart"> - <argument name="selector" value="{{StorefrontMiniCartSection.emptyMiniCart}}"/> + <argument name="selector" value="{{StorefrontMinicartSection.emptyMiniCart}}"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml index 57ed8e442af7c..af3a2e6870cd7 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml @@ -64,8 +64,7 @@ <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart1"/> <waitForPageLoad stepKey="waitForpageLoad1"/> <dontSee selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Free')}}" stepKey="dontSeeFreeShipping"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage"/> - <waitForPageLoad stepKey="waitForShoppingCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage"/> <conditionalClick selector="{{DiscountSection.DiscountTab}}" dependentSelector="{{DiscountSection.CouponInput}}" visible="false" stepKey="clickIfDiscountTabClosed1"/> <waitForPageLoad stepKey="waitForCouponTabOpen1"/> <click selector="{{DiscountSection.CancelCoupon}}" stepKey="cancelCoupon"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontApplyPromoCodeDuringCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontApplyPromoCodeDuringCheckoutTest.xml index 3648ac361ccc9..4320eb8e6160f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontApplyPromoCodeDuringCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontApplyPromoCodeDuringCheckoutTest.xml @@ -29,21 +29,23 @@ <createData entity="SimpleSalesRuleCoupon" stepKey="createCouponForCartPriceRule"> <requiredEntity createDataKey="createCartPriceRule"/> </createData> + + <!-- Login as admin --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> <!-- Delete simple product --> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> - <!-- Delete sales rule --> <deleteData createDataKey="createCartPriceRule" stepKey="deleteCartPriceRule"/> - + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> <!-- Admin log out --> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> <!-- Go to Storefront as Guest and add simple product to cart --> <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToCart"> - <argument name="product" value="$$createProduct$$"/> + <argument name="product" value="$createProduct$"/> </actionGroup> <!-- Go to Checkout --> @@ -56,16 +58,18 @@ <!-- Click Apply Discount Code: section is expanded. Input promo code, apply and see success message --> <actionGroup ref="StorefrontApplyDiscountCodeActionGroup" stepKey="applyCoupon"> - <argument name="discountCode" value="$$createCouponForCartPriceRule.code$$"/> + <argument name="discountCode" value="$createCouponForCartPriceRule.code$"/> </actionGroup> <!-- Apply button is disappeared --> <dontSeeElement selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="dontSeeApplyButton"/> <!-- Cancel coupon button is appeared --> + <waitForElementVisible selector="{{DiscountSection.CancelCouponBtn}}" stepKey="waitCancelButtonAppears"/> <seeElement selector="{{DiscountSection.CancelCouponBtn}}" stepKey="seeCancelCouponButton"/> <!-- Order summary contains information about applied code --> + <waitForElementVisible selector="{{CheckoutPaymentSection.discount}}" stepKey="waitForDiscountCouponInSummaryBlock"/> <seeElement selector="{{CheckoutPaymentSection.discount}}" stepKey="seeDiscountCouponInSummaryBlock"/> <see selector="{{CheckoutPaymentSection.discountPrice}}" userInput="-$5.00" stepKey="seeDiscountPrice"/> @@ -76,16 +80,13 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> - <!-- Login as admin --> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <!-- Verify total on order page --> <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderById"> - <argument name="orderId" value="$grabOrderNumber"/> + <argument name="orderId" value="{$grabOrderNumber}"/> </actionGroup> <actionGroup ref="AdminOrderGridClickFirstRowActionGroup" stepKey="clickOrderRow"/> <scrollTo selector="{{AdminOrderTotalSection.grandTotal}}" stepKey="scrollToOrderTotalSection"/> - <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$$createProduct.price$$" stepKey="checkTotal"/> + <see selector="{{AdminOrderTotalSection.grandTotal}}" userInput="$createProduct.price$" stepKey="checkTotal"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckPagerShoppingCartWithMoreThan20ProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckPagerShoppingCartWithMoreThan20ProductsTest.xml index caec34c5ef1aa..93d1c4092c05e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckPagerShoppingCartWithMoreThan20ProductsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckPagerShoppingCartWithMoreThan20ProductsTest.xml @@ -131,7 +131,7 @@ <deleteData createDataKey="simpleProduct20" stepKey="deleteCartItem20"/> <deleteData createDataKey="simpleProduct21" stepKey="deleteCartItem21"/> </after> - <actionGroup ref="StorefrontOpenCartPageActionGroup" stepKey="goToCartPage" /> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage" /> <actionGroup ref="AssertToolbarTextIsVisibleInCartActionGroup" stepKey="VerifyPagerText"> <argument name="text" value="Items 1 to 20 of 21 total"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutDisabledBundleProductTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutDisabledBundleProductTest.xml index 444ebf653b94f..27d4e4c207ae7 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutDisabledBundleProductTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutDisabledBundleProductTest.xml @@ -71,7 +71,7 @@ </actionGroup> <closeTab stepKey="closeTab"/> <!-- Go to cart page--> - <actionGroup ref="StorefrontOpenCartPageActionGroup" stepKey="openCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openCartPage"/> <!-- Assert checkout button exists on the page--> <seeElement selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="seeCheckoutButton"/> <!-- Assert no error message is not shown on the page--> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml index ce7c5c3b29353..38efc9d7eca24 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml @@ -89,7 +89,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert Empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> <!-- Register customer after checkout --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml index 16c7a0609a6ff..eda6d5f867540 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndRegisterCustomerAfterCheckoutTest.xml @@ -70,7 +70,7 @@ <!--Place order --> <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> <!-- Register customer after checkout --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml index 787d8f58891d0..0042c73b13826 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml @@ -146,7 +146,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> <!-- Open Order Index Page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutDisabledProductAndCouponTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutDisabledProductAndCouponTest.xml index b962d80a4d88b..a2ff149af1a87 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutDisabledProductAndCouponTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutDisabledProductAndCouponTest.xml @@ -85,7 +85,7 @@ </actionGroup> <!-- Check cart --> - <click selector="{{StorefrontMiniCartSection.show}}" stepKey="clickMiniCart2"/> - <dontSeeElement selector="{{StorefrontMiniCartSection.quantity}}" stepKey="dontSeeCartItem"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickMiniCart2"/> + <dontSeeElement selector="{{StorefrontMinicartSection.quantity}}" stepKey="dontSeeCartItem"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml index 2787627b935c1..dbb695fb4fb00 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml @@ -191,7 +191,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> <!-- Open Order Index Page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml index b63766e0cd374..e9d056417330d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithCouponAndZeroSubtotalTest.xml @@ -69,7 +69,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> <!-- Open Order Index Page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontMissingPagerShoppingCartWith20ProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontMissingPagerShoppingCartWith20ProductsTest.xml index 32b0985c290a3..a5a3675ea0a0b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontMissingPagerShoppingCartWith20ProductsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontMissingPagerShoppingCartWith20ProductsTest.xml @@ -126,7 +126,7 @@ <deleteData createDataKey="simpleProduct20" stepKey="deleteCartItem20"/> </after> <!-- Go to the shopping cart and check if the pager is missing--> - <actionGroup ref="StorefrontOpenCartPageActionGroup" stepKey="goToCartPage" /> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage" /> <actionGroup ref="AssertPagerTextIsNotVisibleActionGroup" stepKey="VerifyMissingPagerText" > <argument name="text" value="Items 1 to 20"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml index 9b536882dc64b..5df8338030efc 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml @@ -65,8 +65,7 @@ <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> <!--Go to cart page, update qty and proceed to checkout--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <see userInput="Shopping Cart" stepKey="seeCartPageIsOpened"/> <fillField selector="{{CheckoutCartProductSection.qty($$createProduct.name$$)}}" userInput="2" stepKey="updateProductQty"/> <click selector="{{CheckoutCartProductSection.updateShoppingCartButton}}" stepKey="clickUpdateShoppingCart"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml index 391cfd254101a..9aea4ac79312a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml @@ -35,7 +35,7 @@ <argument name="product" value="$$createProduct$$"/> </actionGroup> <!-- 2. Go to Shopping Cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutCartIndexPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckoutCartIndexPage"/> <!-- 3. Open "Estimate Shipping and Tax" section and input data --> <actionGroup ref="StorefrontCartEstimateShippingAndTaxActionGroup" stepKey="fillEstimateShippingAndTaxSection"/> <actionGroup ref="StorefrontAssertShippingMethodPresentInCartActionGroup" stepKey="assertShippingMethodFlatRateIsPresentInCart"> @@ -78,7 +78,7 @@ <argument name="shippingMethod" value="Free Shipping"/> </actionGroup> <!-- 11. Go back to the shopping cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutCartIndexPage1"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckoutCartIndexPage1"/> <actionGroup ref="StorefrontAssertCartEstimateShippingAndTaxActionGroup" stepKey="assertCartEstimateShippingAndTaxAfterGoingBackToShoppingCart"> <argument name="customerData" value="Simple_UK_Customer_For_Shipment"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml index 146db30aa7c86..ffdbab03ca337 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityChangesInBackendAfterCustomerCheckoutTest.xml @@ -63,7 +63,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> <!-- Open Order Index Page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartPagerForOneItemPerPageAnd2ProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartPagerForOneItemPerPageAnd2ProductsTest.xml index 2691dc2b9fd06..1bab01aac4641 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartPagerForOneItemPerPageAnd2ProductsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartPagerForOneItemPerPageAnd2ProductsTest.xml @@ -37,7 +37,7 @@ <deleteData createDataKey="createSimpleProduct1" stepKey="deleteProduct1"/> <deleteData createDataKey="createSimpleProduct2" stepKey="deleteProduct2"/> </after> - <actionGroup ref="StorefrontOpenCartPageActionGroup" stepKey="goToCartPage" /> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage" /> <actionGroup ref="AssertToolbarTextIsVisibleInCartActionGroup" stepKey="VerifyPagerTextWithChangedConfiguration"> <argument name="text" value="Items 1 to 1 of 2 total"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml index 849b586594955..792025acf1708 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKCustomerCheckoutWithCouponTest.xml @@ -105,7 +105,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> <!-- Login to Admin Page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml index ec99bb0e46722..76a3adfb67057 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUKGuestCheckoutWithConditionProductQuantityEqualsToOrderedQuantityTest.xml @@ -58,7 +58,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> <!-- Open Order Index Page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml index 642814ded6e3e..8410dd15fa04e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml @@ -70,7 +70,7 @@ <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"/> <!-- Assert empty Mini Cart --> - <seeElement selector="{{StorefrontMiniCartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> + <seeElement selector="{{StorefrontMinicartSection.emptyMiniCart}}" stepKey="assertEmptyCart" /> <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumberWithoutLink}}" stepKey="orderId"/> <!-- Open Order Index Page --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml index d166bfdac0ba4..f0c3a23a8d39c 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml @@ -36,8 +36,7 @@ </after> <!-- Go to the shopping cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> - <waitForPageLoad stepKey="waitForCheckoutPageLoad1"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnPageShoppingCart"/> <!-- Change the product QTY --> <fillField selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" userInput="{{quoteQty3Price123.qty}}" stepKey="changeCartQty"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml index 91a601dc6ef52..afb4ff03a4fc9 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml @@ -43,8 +43,7 @@ </after> <!-- Go to the shopping cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> - <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnPageShoppingCart"/> <!-- Change the product QTY --> <fillField selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" userInput="{{quoteQty11Subtotal1320.qty}}" stepKey="changeCartQty"/> diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Checkout/LayoutProcessorTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Checkout/LayoutProcessorTest.php index 9fff4b622e596..7b7d956f457f0 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Checkout/LayoutProcessorTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Checkout/LayoutProcessorTest.php @@ -255,6 +255,7 @@ private function getBillingComponent($paymentCode) ], 'filterBy' => [ 'target' => '${ $.provider }:${ $.parentScope }.country_id', + '__disableTmpl' => ['target' => false], 'field' => 'country_id', ], ], diff --git a/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php b/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php index 9a408f1ecd1c8..7ac0ee645df63 100644 --- a/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php +++ b/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php @@ -6,6 +6,7 @@ namespace Magento\Checkout\Test\Unit\CustomerData; use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; +use PHPUnit\Framework\MockObject\MockObject; class DefaultItemTest extends \PHPUnit\Framework\TestCase { @@ -25,7 +26,7 @@ class DefaultItemTest extends \PHPUnit\Framework\TestCase private $configurationPool; /** - * @var ItemResolverInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ItemResolverInterface|MockObject */ private $itemResolver; @@ -102,5 +103,6 @@ public function testGetItemData() $this->assertArrayHasKey('product_price_value', $itemData); $this->assertArrayHasKey('product_image', $itemData); $this->assertArrayHasKey('canApplyMsrp', $itemData); + $this->assertArrayHasKey('message', $itemData); } } diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml index 4b5d1033408e4..81ee1a5e6db4c 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml @@ -184,7 +184,7 @@ <block class="Magento\Framework\View\Element\RendererList" name="checkout.cart.item.renderers" as="renderer.list"/> <block class="Magento\Framework\View\Element\Text\ListText" name="checkout.cart.order.actions"/> </block> - <container name="checkout.cart.widget" as="checkout_cart_widget" label="Shopping Cart Items After"/> + <container name="checkout.cart.widget" as="checkout_cart_widget" label="Shopping Cart Items After" after="cart-items"/> </container> <block class="Magento\Checkout\Block\Cart\Crosssell" name="checkout.cart.crosssell" template="Magento_Catalog::product/list/items.phtml" after="-" ifconfig="checkout/cart/crosssell_enabled"> <arguments> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html index 32bbd66d13e68..5489089452d85 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html @@ -112,4 +112,7 @@ </div> </div> </div> + <div class="message notice" if="message"> + <div data-bind="text: message"></div> + </div> </li> diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Gallery/DefaultConfigProvider.php b/app/code/Magento/Cms/Model/Wysiwyg/Gallery/DefaultConfigProvider.php index 822f9ce2b1cb5..a580365c89df8 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Gallery/DefaultConfigProvider.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Gallery/DefaultConfigProvider.php @@ -8,6 +8,11 @@ namespace Magento\Cms\Model\Wysiwyg\Gallery; +use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; + +/** + * @inheritdoc + */ class DefaultConfigProvider implements \Magento\Framework\Data\Wysiwyg\ConfigProviderInterface { /** @@ -30,26 +35,34 @@ class DefaultConfigProvider implements \Magento\Framework\Data\Wysiwyg\ConfigPro */ private $currentTreePath; + /** + * @var OpednDialogUrl + */ + private $openDialogUrl; + /** * @param \Magento\Backend\Model\UrlInterface $backendUrl * @param \Magento\Cms\Helper\Wysiwyg\Images $imagesHelper + * @param OpenDialogUrl $openDialogUrl * @param array $windowSize * @param string|null $currentTreePath */ public function __construct( \Magento\Backend\Model\UrlInterface $backendUrl, \Magento\Cms\Helper\Wysiwyg\Images $imagesHelper, + OpenDialogUrl $openDialogUrl, array $windowSize = [], $currentTreePath = null ) { $this->backendUrl = $backendUrl; $this->imagesHelper = $imagesHelper; + $this->openDialogUrl = $openDialogUrl; $this->windowSize = $windowSize; $this->currentTreePath = $currentTreePath; } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfig(\Magento\Framework\DataObject $config) : \Magento\Framework\DataObject { @@ -72,7 +85,7 @@ public function getConfig(\Magento\Framework\DataObject $config) : \Magento\Fram [ 'add_images' => true, 'files_browser_window_url' => $this->backendUrl->getUrl( - 'cms/wysiwyg_images/index', + $this->openDialogUrl->get(), $fileBrowserUrlParams ), 'files_browser_window_width' => $this->windowSize['width'], diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDisableWYSIWYGActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDisableWYSIWYGActionGroup.xml new file mode 100644 index 0000000000000..7e035a47824ee --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminDisableWYSIWYGActionGroup.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="AdminDisableWYSIWYGActionGroup"> + <annotations> + <description>Runs bin/magento command to disable WYSIWYG</description> + </annotations> + + <magentoCLI stepKey="disableWYSIWYG" command="config:set cms/wysiwyg/enabled disabled"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEnableWYSIWYGActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEnableWYSIWYGActionGroup.xml new file mode 100644 index 0000000000000..6c9b439e2941b --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEnableWYSIWYGActionGroup.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="AdminEnableWYSIWYGActionGroup"> + <annotations> + <description>Runs bin/magento command to enable WYSIWYG</description> + </annotations> + + <magentoCLI stepKey="enableWYSIWYG" command="config:set cms/wysiwyg/enabled enabled"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertStorefrontTextOnCategoryPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertStorefrontTextOnCategoryPageActionGroup.xml index aa761854a10fa..eb060f6d2cef8 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertStorefrontTextOnCategoryPageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertStorefrontTextOnCategoryPageActionGroup.xml @@ -16,6 +16,6 @@ <waitForPageLoad stepKey="waitForPageLoad1"/> <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="{{category.name_lwr}}" stepKey="assertCategoryOnStorefront"/> <seeInTitle userInput="{{category.name}}" stepKey="seeCategoryNameInTitle"/> - <see selector="{{StorefrontCMSPageSection.mainContent}}" userInput="{{text}}" stepKey="seeAssertTextInMainContent"/> + <see userInput="{{text}}" stepKey="seeAssertTextInMainContent"/> </actionGroup> </actionGroups> diff --git a/dev/tests/acceptance/tests/_suite/WYSIWYGDisabledSuite.xml b/app/code/Magento/Cms/Test/Mftf/Suite/WYSIWYGDisabledSuite.xml similarity index 73% rename from dev/tests/acceptance/tests/_suite/WYSIWYGDisabledSuite.xml rename to app/code/Magento/Cms/Test/Mftf/Suite/WYSIWYGDisabledSuite.xml index 924c712404dfe..20b596d843f74 100644 --- a/dev/tests/acceptance/tests/_suite/WYSIWYGDisabledSuite.xml +++ b/app/code/Magento/Cms/Test/Mftf/Suite/WYSIWYGDisabledSuite.xml @@ -8,7 +8,7 @@ <suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> <suite name="WYSIWYGDisabledSuite"> <before> - <magentoCLI stepKey="disableWYSYWYG" command="config:set cms/wysiwyg/enabled disabled" /> + <actionGroup ref="AdminDisableWYSIWYGActionGroup" stepKey="disableWYSYWYG" /> </before> <include> <group name="WYSIWYGDisabled"/> @@ -17,7 +17,7 @@ <group name="skip"/> </exclude> <after> - <magentoCLI stepKey="disableWYSYWYG" command="config:set cms/wysiwyg/enabled enabled" /> + <actionGroup ref="AdminEnableWYSIWYGActionGroup" stepKey="disableWYSYWYG" /> </after> </suite> </suites> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateDisabledCmsBlockEntityAndAssignToCategoryTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateDisabledCmsBlockEntityAndAssignToCategoryTest.xml index 9924d814b0487..2008278c53eeb 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateDisabledCmsBlockEntityAndAssignToCategoryTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateDisabledCmsBlockEntityAndAssignToCategoryTest.xml @@ -46,6 +46,7 @@ <argument name="value" value="Static block only"/> </actionGroup> <actionGroup ref="AdminSaveCategoryActionGroup" stepKey="saveCategory"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <actionGroup ref="AssertStorefrontNoTextOnCategoryPageActionGroup" stepKey="assertBlockOnCategoryFrontPage"> <argument name="category" value="$$newDefaultCategory$$"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateEnabledCmsBlockEntityAndAssignToCategoryTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateEnabledCmsBlockEntityAndAssignToCategoryTest.xml index 98a617bec5366..a69b6156af849 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateEnabledCmsBlockEntityAndAssignToCategoryTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateEnabledCmsBlockEntityAndAssignToCategoryTest.xml @@ -40,6 +40,7 @@ <argument name="value" value="Static block only"/> </actionGroup> <actionGroup ref="AdminSaveCategoryActionGroup" stepKey="saveCategory"/> + <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> <actionGroup ref="AssertStorefrontTextOnCategoryPageActionGroup" stepKey="assertBlockOnCategoryFrontPage"> <argument name="category" value="$$newDefaultCategory$$"/> diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php index 4ffe4a6ad8774..e036e2a8ad200 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php @@ -11,7 +11,7 @@ use Magento\Framework\UrlInterface; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponent\Processor; -use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\MockObject\MockObject; /** * BlockActionsTest contains unit tests for \Magento\Cms\Ui\Component\Listing\Column\BlockActions class. @@ -96,7 +96,6 @@ public function testPrepareDataSource() 'edit' => [ 'href' => 'test/url/edit', 'label' => __('Edit'), - '__disableTmpl' => true, ], 'delete' => [ 'href' => 'test/url/delete', @@ -106,7 +105,6 @@ public function testPrepareDataSource() 'message' => __('Are you sure you want to delete a %1 record?', $title), ], 'post' => true, - '__disableTmpl' => true, ], ], ], diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php index 781d6d31246ca..e187534f46ad5 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php @@ -15,7 +15,7 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponent\Processor; use PHPUnit\Framework\TestCase; -use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\MockObject\MockObject; /** * Test for Magento\Cms\Ui\Component\Listing\Column\PageActions class. @@ -176,7 +176,6 @@ public function configDataProvider():array 'edit' => [ 'href' => 'test/url/edit', 'label' => __('Edit'), - '__disableTmpl' => true, ], 'delete' => [ 'href' => 'test/url/delete', @@ -184,15 +183,12 @@ public function configDataProvider():array 'confirm' => [ 'title' => __('Delete %1', $title), 'message' => __('Are you sure you want to delete a %1 record?', $title), - '__disableTmpl' => true, ], 'post' => true, - '__disableTmpl' => true, ], 'preview' => [ 'href' => 'test/url/view', 'label' => __('View'), - '__disableTmpl' => true, 'target' => '_blank' ] ], diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php index 65940c5d7b4f9..6e9eef47281c0 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php @@ -70,7 +70,6 @@ public function prepareDataSource(array $dataSource) ] ), 'label' => __('Edit'), - '__disableTmpl' => true, ], 'delete' => [ 'href' => $this->urlBuilder->getUrl( @@ -85,7 +84,6 @@ public function prepareDataSource(array $dataSource) 'message' => __('Are you sure you want to delete a %1 record?', $title), ], 'post' => true, - '__disableTmpl' => true, ], ]; } diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php index 0c6000bdbab84..7c04ce3e2e09e 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php @@ -87,7 +87,6 @@ public function prepareDataSource(array $dataSource) $item[$name]['edit'] = [ 'href' => $this->urlBuilder->getUrl($this->editUrl, ['page_id' => $item['page_id']]), 'label' => __('Edit'), - '__disableTmpl' => true, ]; $title = $this->getEscaper()->escapeHtml($item['title']); $item[$name]['delete'] = [ @@ -96,10 +95,8 @@ public function prepareDataSource(array $dataSource) 'confirm' => [ 'title' => __('Delete %1', $title), 'message' => __('Are you sure you want to delete a %1 record?', $title), - '__disableTmpl' => true, ], 'post' => true, - '__disableTmpl' => true, ]; } if (isset($item['identifier'])) { @@ -110,7 +107,6 @@ public function prepareDataSource(array $dataSource) isset($item['store_code']) ? $item['store_code'] : null ), 'label' => __('View'), - '__disableTmpl' => true, 'target' => '_blank' ]; } diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml index ff3d5f24dd5a1..168d1d7ace504 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/files.phtml @@ -7,11 +7,16 @@ /** @var $block \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Files */ $_width = $block->getImagesWidth(); -$_height = $block->getImagesHeight(); ?> -<?php if ($block->getFilesCount() > 0) : ?> - <?php foreach ($block->getFiles() as $file) : ?> +<?php if ($block->getFilesCount() > 0): ?> + <?php foreach ($block->getFiles() as $file): ?> + <?php + $src = $block->getFileThumbUrl($file); + $width = $block->getFileWidth($file); + $height = $block->getFileHeight($file); + $filename = $block->getFileName($file); + ?> <div data-row="file" class="filecnt" @@ -19,17 +24,18 @@ $_height = $block->getImagesHeight(); data-size="<?= $block->escapeHtmlAttr($file->getSize()) ?>" data-mime-type="<?= $block->escapeHtmlAttr($file->getMimeType()) ?>" > - <p class="nm" style="height:<?= $block->escapeHtmlAttr($_height) ?>px;"> - <?php if ($block->getFileThumbUrl($file)) : ?> - <img src="<?= $block->escapeHtmlAttr($block->getFileThumbUrl($file)) ?>" alt="<?= $block->escapeHtmlAttr($block->getFileName($file)) ?>"/> + <p class="nm"> + <?php if ($block->getFileThumbUrl($file)): ?> + <img src="<?= $block->escapeHtmlAttr($src) ?>" alt="<?= $block->escapeHtmlAttr($filename) ?>"/> <?php endif; ?> </p> - <?php if ($block->getFileWidth($file)) : ?> - <small><?= $block->escapeHtml($block->getFileWidth($file)) ?>x<?= $block->escapeHtml($block->getFileHeight($file)) ?> <?= $block->escapeHtml(__('px.')) ?></small><br/> + <?php if ($block->getFileWidth($file)): ?> + <small><?= $block->escapeHtmlAttr($width) ?>x<?= $block->escapeHtmlAttr($height) ?> + <?= $block->escapeHtml(__('px.')) ?></small><br/> <?php endif; ?> <small><?= $block->escapeHtml($block->getFileShortName($file)) ?></small> </div> <?php endforeach; ?> -<?php else : ?> +<?php else: ?> <div class="empty"><?= $block->escapeHtml(__('No files found')) ?></div> <?php endif; ?> diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php index c63ccae871657..522ed73fa37d2 100644 --- a/app/code/Magento/Config/App/Config/Type/System.php +++ b/app/code/Magento/Config/App/Config/Type/System.php @@ -20,6 +20,8 @@ use Magento\Store\Model\Config\Processor\Fallback; use Magento\Framework\Encryption\Encryptor; use Magento\Store\Model\ScopeInterface as StoreScope; +use Magento\Framework\App\Cache\StateInterface; +use Magento\Framework\App\Cache\Type\Config; /** * System configuration type @@ -98,6 +100,12 @@ class System implements ConfigTypeInterface private $lockQuery; /** + * @var StateInterface + */ + private $cacheState; + + /** + * System constructor. * @param ConfigSourceInterface $source * @param PostProcessorInterface $postProcessor * @param Fallback $fallback @@ -110,6 +118,7 @@ class System implements ConfigTypeInterface * @param Encryptor|null $encryptor * @param LockManagerInterface|null $locker * @param LockGuardedCacheLoader|null $lockQuery + * @param StateInterface|null $cacheState * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -125,7 +134,8 @@ public function __construct( Reader $reader = null, Encryptor $encryptor = null, LockManagerInterface $locker = null, - LockGuardedCacheLoader $lockQuery = null + LockGuardedCacheLoader $lockQuery = null, + StateInterface $cacheState = null ) { $this->postProcessor = $postProcessor; $this->cache = $cache; @@ -136,6 +146,8 @@ public function __construct( ?: ObjectManager::getInstance()->get(Encryptor::class); $this->lockQuery = $lockQuery ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class); + $this->cacheState = $cacheState + ?: ObjectManager::getInstance()->get(StateInterface::class); } /** @@ -220,6 +232,10 @@ private function getWithParts($path) */ private function loadAllData() { + if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { + return $this->readData(); + } + $loadAction = function () { $cachedData = $this->cache->load($this->configType); $data = false; @@ -245,6 +261,10 @@ private function loadAllData() */ private function loadDefaultScopeData($scopeType) { + if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { + return $this->readData(); + } + $loadAction = function () use ($scopeType) { $cachedData = $this->cache->load($this->configType . '_' . $scopeType); $scopeData = false; @@ -271,6 +291,10 @@ private function loadDefaultScopeData($scopeType) */ private function loadScopeData($scopeType, $scopeId) { + if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { + return $this->readData(); + } + $loadAction = function () use ($scopeType, $scopeId) { $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); $scopeData = false; @@ -393,6 +417,10 @@ public function clean() $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); }; + if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) { + return $cleanAction(); + } + $this->lockQuery->lockedCleanData( self::$lockName, $cleanAction 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 e4f1ae65bcacd..ac168f16ca182 100644 --- a/app/code/Magento/Config/Model/Config/Source/Email/Template.php +++ b/app/code/Magento/Config/Model/Config/Source/Email/Template.php @@ -64,12 +64,6 @@ public function toOptionArray() $templateLabel = $this->_emailConfig->getTemplateLabel($templateId); $templateLabel = __('%1 (Default)', $templateLabel); array_unshift($options, ['value' => $templateId, 'label' => $templateLabel]); - array_walk( - $options, - function (&$item) { - $item['__disableTmpl'] = true; - } - ); return $options; } } 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 9fabe6fef0c8e..1fc730ccef30e 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 @@ -6,6 +6,8 @@ namespace Magento\Config\Test\Unit\Model\Config\Source\Email; +use PHPUnit\Framework\MockObject\MockObject; + /** * Test class for Template. */ @@ -17,12 +19,12 @@ class TemplateTest extends \PHPUnit\Framework\TestCase protected $_model; /** - * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Registry|MockObject */ protected $_coreRegistry; /** - * @var \Magento\Email\Model\Template\Config|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Email\Model\Template\Config|MockObject */ protected $_emailConfig; @@ -82,17 +84,14 @@ public function testToOptionArray() [ 'value' => 'template_new', 'label' => 'Template New (Default)', - '__disableTmpl' => true ], [ 'value' => 'template_one', 'label' => 'Template One', - '__disableTmpl' => true ], [ 'value' => 'template_two', 'label' => 'Template Two', - '__disableTmpl' => true ], ]; $this->_model->setPath('template/new'); diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml index 920cac382fcbf..70318553710d3 100644 --- a/app/code/Magento/Config/etc/di.xml +++ b/app/code/Magento/Config/etc/di.xml @@ -97,8 +97,6 @@ <virtualType name="systemConfigQueryLocker" type="Magento\Framework\Cache\LockGuardedCacheLoader"> <arguments> <argument name="locker" xsi:type="object">Magento\Framework\Lock\Backend\Cache</argument> - <argument name="lockTimeout" xsi:type="number">42000</argument> - <argument name="delayTimeout" xsi:type="number">100</argument> </arguments> </virtualType> diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php index b9fcf307b613f..e64a92a8bd6f4 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php +++ b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Variations/Config/Matrix.php @@ -12,6 +12,7 @@ /** * Product variations matrix block + * All disableTmpl flag are required here for configurable products * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml index bceabb27e3ae7..f2a8e78523758 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml @@ -131,8 +131,7 @@ <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" stepKey="waitForSuccessMessage"/> <!-- Assert configurable product in cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnShoppingCartPage"/> - <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnShoppingCartPage"/> <actionGroup ref="StorefrontCheckCartConfigurableProductActionGroup" stepKey="storefrontCheckCartConfigurableProductActionGroup"> <argument name="product" value="ApiConfigurableProduct"/> <argument name="optionProduct" value="colorConfigurableProductAttribute1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml index 9bb5b5073215b..273e37089973b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml @@ -113,8 +113,7 @@ <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" stepKey="waitForSuccessMessage"/> <!-- Assert configurable product in cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnShoppingCartPage"/> - <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnShoppingCartPage"/> <actionGroup ref="StorefrontCheckCartConfigurableProductActionGroup" stepKey="storefrontCheckCartConfigurableProductActionGroup"> <argument name="product" value="ApiConfigurableProduct"/> <argument name="optionProduct" value="colorConfigurableProductAttribute1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminConfigurableProductTypeSwitchingToVirtualProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminConfigurableProductTypeSwitchingToVirtualProductTest.xml index 36450313f9f3a..dd176455a03ba 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminConfigurableProductTypeSwitchingToVirtualProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminConfigurableProductTypeSwitchingToVirtualProductTest.xml @@ -13,7 +13,7 @@ <stories value="Product type switching"/> <title value="Configurable product type switching on editing to virtual product"/> <description value="Configurable product type switching on editing to virtual product"/> - <testCaseId value="MC-17952"/> + <testCaseId value="MC-28742"/> <useCaseId value="MAGETWO-44170"/> <severity value="MAJOR"/> <group value="catalog"/> @@ -21,7 +21,7 @@ <!--Delete product configurations--> <comment userInput="Delete product configuration" stepKey="commentDeleteConfigs"/> <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="gotToConfigProductPage"> - <argument name="productId" value="$$createProduct.id$$"/> + <argument name="productId" value="$createProduct.id$"/> </actionGroup> <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> <conditionalClick selector="{{ AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> @@ -38,13 +38,12 @@ <comment userInput="Assert virtual product on Admin product page grid" stepKey="commentAssertVirtualProductOnAdmin"/> <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForVirtual"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGridBySkuForVirtual"> - <argument name="sku" value="$$createProduct.sku$$"/> + <argument name="sku" value="$createProduct.sku$"/> </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeVirtualProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Virtual Product" stepKey="seeVirtualProductTypeInGrid"/> + <seeElement selector="{{AdminProductGridSection.productRowByTypeAndName('Virtual Product',$createProduct.name$)}}" stepKey="seeVirtualProductInGrid"/> <!--Assert virtual product on storefront--> <comment userInput="Assert virtual product on storefront" stepKey="commentAssertVirtualProductOnStorefront"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openVirtualProductPage"/> + <amOnPage url="{{StorefrontProductPage.url($createProduct.name$)}}" stepKey="openVirtualProductPage"/> <waitForPageLoad stepKey="waitForStorefrontVirtualProductPageLoad"/> <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertVirtualProductInStock"/> </test> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminSimpleProductTypeSwitchingToConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminSimpleProductTypeSwitchingToConfigurableProductTest.xml index c5fdbfb36490a..14979f93ca423 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminSimpleProductTypeSwitchingToConfigurableProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminSimpleProductTypeSwitchingToConfigurableProductTest.xml @@ -32,6 +32,8 @@ <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> <requiredEntity createDataKey="createConfigProductAttribute"/> </createData> + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </before> <after> <!--Delete product--> @@ -39,7 +41,7 @@ <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> <actionGroup ref="DeleteAllDuplicateProductUsingProductGridActionGroup" stepKey="deleteAllDuplicateProducts"> - <argument name="product" value="$$createProduct$$"/> + <argument name="product" value="$createProduct$"/> </actionGroup> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> @@ -50,27 +52,25 @@ <!--Add configurations to product--> <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="gotToSimpleProductPage"> - <argument name="productId" value="$$createProduct.id$$"/> + <argument name="productId" value="$createProduct.id$"/> </actionGroup> <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> <actionGroup ref="GenerateConfigurationsByAttributeCodeActionGroup" stepKey="setupConfigurations"> - <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + <argument name="attributeCode" value="$createConfigProductAttribute.attribute_code$"/> </actionGroup> <actionGroup ref="SaveConfiguredProductActionGroup" stepKey="saveConfigProductForm"/> <!--Assert configurable product on Admin product page grid--> <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> <actionGroup ref="FilterProductGridBySku2ActionGroup" stepKey="filterProductGridBySku"> - <argument name="sku" value="$$createProduct.sku$$"/> + <argument name="sku" value="$createProduct.sku$"/> </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductNameInGrid1"/> - <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductNameInGrid2"/> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <seeElement selector="{{AdminProductGridSection.productRowByTypeAndName('Configurable Product',$createProduct.name$)}}" stepKey="seeConfigurableProductInGrid"/> + <seeElement selector="{{AdminProductGridSection.productRowByTypeAndName('Simple Product',$createProduct.name$-option1)}}" stepKey="seeSimpleProduct1NameInGrid"/> + <seeElement selector="{{AdminProductGridSection.productRowByTypeAndName('Simple Product',$createProduct.name$-option2)}}" stepKey="seeSimpleProduct2NameInGrid"/> <!--Assert configurable product on storefront--> <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> + <amOnPage url="{{StorefrontProductPage.url($createProduct.name$)}}" stepKey="openProductPage"/> <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml index e983559fb6bcf..bbd5dbd8068f7 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml @@ -65,8 +65,7 @@ <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{BaseConfigurableProduct.name}} to your shopping cart." stepKey="seeSuccessMessage"/> <!--Check item in cart--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <seeElement selector="{{CheckoutCartProductSection.ProductLinkByName(BaseConfigurableProduct.name)}}" stepKey="seeProductInCart"/> <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, colorProductAttribute.default_label)}}" userInput="{{colorProductAttribute2.name}}" stepKey="seeSelectedOption"/> <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, ProductOptionFile.title)}}" userInput="{{MagentoLogo.file}}" stepKey="seeCorrectOptionFile"/> diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableAttributeSetHandler.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableAttributeSetHandler.php index 67773f019be8c..82bc6faac6bcc 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableAttributeSetHandler.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableAttributeSetHandler.php @@ -33,7 +33,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -41,7 +41,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -204,6 +204,7 @@ protected function getNewAttributeSet() 'visible' => 'ns = ${ $.ns }, index = affectedAttributeSetNew:checked', 'disabled' => '!ns = ${ $.ns }, index = affectedAttributeSetNew:checked', + '__disableTmpl' => ['disabled' => false, 'visible' => false], ] ], ], @@ -243,6 +244,7 @@ protected function getExistingAttributeSet($meta) 'visible' => 'ns = ${ $.ns }, index = affectedAttributeSetExisting:checked', 'disabled' => '!ns = ${ $.ns }, index = affectedAttributeSetExisting:checked', + '__disableTmpl' => ['disabled' => false, 'value' => false, 'visible' => false], ], ], ], diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php index 16119dcb5c866..4e11c49f10162 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php @@ -167,6 +167,7 @@ public function modifyMeta(array $meta) 'imports' => [ 'visible' => '!ns = ${ $.ns }, index = ' . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', + '__disableTmpl' => ['visible' => false], ], ], ], @@ -185,6 +186,7 @@ public function modifyMeta(array $meta) 'imports' => [ 'visible' => 'ns = ${ $.ns }, index = ' . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', + '__disableTmpl' => ['visible' => false], ], ], ], @@ -284,6 +286,7 @@ protected function getButtonSet() ), 'template' => 'ui/form/components/complex', 'createConfigurableButton' => 'ns = ${ $.ns }, index = create_configurable_products_button', + '__disableTmpl' => ['createConfigurableButton' => false], ], ], ], @@ -314,6 +317,7 @@ protected function getButtonSet() 'imports' => [ 'visible' => 'ns = ${ $.ns }, index = ' . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isShowAddProductButton', + '__disableTmpl' => ['visible' => false], ], ], ], @@ -392,6 +396,11 @@ protected function getGrid() 'insertDataFromGrid' => '${$.provider}:${$.dataProviderFromGrid}', 'insertDataFromWizard' => '${$.provider}:${$.dataProviderFromWizard}', 'changeDataFromGrid' => '${$.provider}:${$.dataProviderChangeFromGrid}', + '__disableTmpl' => [ + 'insertDataFromGrid' => false, + 'insertDataFromWizard' => false, + 'changeDataFromGrid' => false + ], ], 'sortOrder' => 20, 'columnsHeader' => false, @@ -445,6 +454,11 @@ protected function getRows() 'thumbnailUrl' => '${$.provider}:${$.parentScope}.thumbnail_image', 'thumbnail' => '${$.provider}:${$.parentScope}.thumbnail', 'smallImage' => '${$.provider}:${$.parentScope}.small_image', + '__disableTmpl' => [ + 'thumbnailUrl' => false, + 'thumbnail' => false, + 'smallImage' => false + ], ], 'uploaderConfig' => [ 'url' => $this->urlBuilder->getUrl( @@ -482,7 +496,10 @@ protected function getRows() 'price', __('Price'), [ - 'imports' => ['addbefore' => '${$.provider}:${$.parentScope}.price_currency'], + 'imports' => [ + 'addbefore' => '${$.provider}:${$.parentScope}.price_currency', + '__disableTmpl' => ['addbefore' => false], + ], 'validation' => ['validate-zero-or-greater' => true] ], ['dataScope' => 'price_string'] @@ -564,7 +581,8 @@ protected function getColumn( 'fit' => true, 'visibleIfCanEdit' => true, 'imports' => [ - 'visible' => '${$.provider}:${$.parentScope}.canEdit' + 'visible' => '${$.provider}:${$.parentScope}.canEdit', + '__disableTmpl' => ['visible' => false], ], ]; $fieldText['arguments']['data']['config'] = [ @@ -576,7 +594,8 @@ protected function getColumn( 'visibleIfCanEdit' => false, 'labelVisible' => false, 'imports' => [ - 'visible' => '!${$.provider}:${$.parentScope}.canEdit' + 'visible' => '!${$.provider}:${$.parentScope}.canEdit', + '__disableTmpl' => ['visible' => false], ], ]; $fieldEdit['arguments']['data']['config'] = array_replace_recursive( diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php index d3129a09d0056..241798a5a0fb2 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php @@ -37,7 +37,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -45,7 +45,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -82,6 +82,7 @@ public function modifyMeta(array $meta) 'imports' => [ 'visible' => 'ns = ${ $.ns }, index = ' . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', + '__disableTmpl' => ['visible' => false], ] ]; $config = $visibilityConfig; diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php index 636d832842f92..d791db2ff78b9 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php @@ -40,7 +40,7 @@ public function __construct(LocatorInterface $locator, ArrayManager $arrayManage } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -48,7 +48,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -92,7 +92,8 @@ private function addPriceTypeWarning(array $meta) ), 'imports' => [ 'updateVisibility' => 'ns = ${ $.ns }, index = ' - . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty' + . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', + '__disableTmpl' => ['updateVisibility' => false], ] ] ); @@ -128,6 +129,7 @@ private function modifyPriceTypeFields(array $meta) 'imports' => [ 'updateOptions' => 'ns = ${ $.ns }, index = ' . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', + '__disableTmpl' => ['updateOptions' => false], ], ] ); diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php index ec69baeb92cb9..7815408321ed4 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php @@ -215,7 +215,6 @@ public function getConfigurableAttributesData() 'code' => $attribute['code'], 'label' => $attribute['label'], 'position' => $attribute['position'], - '__disableTmpl' => true ]; foreach ($attribute['chosen'] as $chosenOption) { @@ -266,7 +265,6 @@ protected function prepareVariations() 'id' => $attribute->getAttributeId(), 'position' => $configurableAttributes[$attribute->getAttributeId()]['position'], 'chosen' => [], - '__disableTmpl' => true ]; $options = $attribute->usesSource() ? $attribute->getSource()->getAllOptions() : []; foreach ($options as $option) { @@ -277,7 +275,6 @@ protected function prepareVariations() 'id' => $option['value'], 'label' => $option['label'], 'value' => $option['value'], - '__disableTmpl' => true ]; } } @@ -289,7 +286,6 @@ protected function prepareVariations() 'id' => $optionId, 'label' => $variation[$attribute->getId()]['label'], 'value' => $optionId, - '__disableTmpl' => true ]; $variationOptions[] = $variationOption; $attributes[$attribute->getAttributeId()]['chosen'][$optionId] = $variationOption; @@ -315,7 +311,6 @@ protected function prepareVariations() 'newProduct' => 0, 'attributes' => $this->getTextAttributes($variationOptions), 'thumbnail_image' => $this->imageHelper->init($product, 'product_thumbnail_image')->getUrl(), - '__disableTmpl' => true ]; $productIds[] = $product->getId(); } diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/StockData.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/StockData.php index db51e7eebefc4..32b92a197f7d9 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/StockData.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/StockData.php @@ -69,6 +69,7 @@ public function modifyMeta(array $meta) 'imports' => [ 'disabled' => '!ns = ${ $.ns }, index = ' . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', + '__disableTmpl' => ['disabled' => false], ], ]; diff --git a/app/code/Magento/Cookie/Test/Mftf/ActionGroup/AdminFillCookieLifetimeActionGroup.xml b/app/code/Magento/Cookie/Test/Mftf/ActionGroup/AdminFillCookieLifetimeActionGroup.xml new file mode 100644 index 0000000000000..28079796926fe --- /dev/null +++ b/app/code/Magento/Cookie/Test/Mftf/ActionGroup/AdminFillCookieLifetimeActionGroup.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="AdminFillCookieLifetimeActionGroup"> + <annotations> + <description>Fills the cookie lifetime field with sample data.</description> + </annotations> + <arguments> + <argument name="cookieLifetime" type="string"/> + </arguments> + <fillField selector="{{AdminDefaultCookieSettingsSection.DefaultCookieLifetime}}" userInput="{{cookieLifetime}}" stepKey="fillFieldCookieLifetime"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cookie/Test/Mftf/ActionGroup/AdminNavigateToDefaultCookieSettingsActionGroup.xml b/app/code/Magento/Cookie/Test/Mftf/ActionGroup/AdminNavigateToDefaultCookieSettingsActionGroup.xml new file mode 100644 index 0000000000000..8d4cd887f09c0 --- /dev/null +++ b/app/code/Magento/Cookie/Test/Mftf/ActionGroup/AdminNavigateToDefaultCookieSettingsActionGroup.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="AdminNavigateToDefaultCookieSettingsActionGroup"> + <amOnPage url="{{AdminConfigurationDefaultCookieSettingsPage.url}}" stepKey="navigateToDefaultCookieSettings"/> + <waitForPageLoad stepKey="waitForWebConfigurationPageLoad"/> + <scrollTo selector="{{AdminDefaultCookieSettingsSection.DefaultCookieSettingsTab}}" x="-150" y="-150" stepKey="scrollToDefaultCookieSettingsSection"/> + <conditionalClick stepKey="expandDefaultCookieSettingsTab" selector="{{AdminDefaultCookieSettingsSection.DefaultCookieSettingsTab}}" dependentSelector="{{AdminDefaultCookieSettingsSection.DefaultCookieLifetime}}" visible="false"/> + <waitForElementVisible selector="{{AdminDefaultCookieSettingsSection.DefaultCookieSettingsTab}}" stepKey="waitForElementsAppeared"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cookie/Test/Mftf/Page/AdminConfigurationDefaultCookieSettingsPage.xml b/app/code/Magento/Cookie/Test/Mftf/Page/AdminConfigurationDefaultCookieSettingsPage.xml new file mode 100644 index 0000000000000..3b1fbe4e14ca8 --- /dev/null +++ b/app/code/Magento/Cookie/Test/Mftf/Page/AdminConfigurationDefaultCookieSettingsPage.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="AdminConfigurationDefaultCookieSettingsPage" url="admin/system_config/edit/section/web/" module="Cookie" area="admin"> + <section name="AdminDefaultCookieSettingsSection"/> + </page> +</pages> diff --git a/app/code/Magento/Cookie/Test/Mftf/Section/AdminDefaultCookieSettingsSection.xml b/app/code/Magento/Cookie/Test/Mftf/Section/AdminDefaultCookieSettingsSection.xml new file mode 100644 index 0000000000000..977db4a8bbf74 --- /dev/null +++ b/app/code/Magento/Cookie/Test/Mftf/Section/AdminDefaultCookieSettingsSection.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="AdminDefaultCookieSettingsSection"> + <element name="DefaultCookieSettingsTab" type="button" selector="#web_cookie-head"/> + <element name="DefaultCookieLifetime" type="input" selector="#web_cookie_cookie_lifetime"/> + </section> +</sections> diff --git a/app/code/Magento/Cookie/Test/Mftf/Test/AdminValidateCookieLifetimeTest.xml b/app/code/Magento/Cookie/Test/Mftf/Test/AdminValidateCookieLifetimeTest.xml new file mode 100644 index 0000000000000..b004eeb17e03f --- /dev/null +++ b/app/code/Magento/Cookie/Test/Mftf/Test/AdminValidateCookieLifetimeTest.xml @@ -0,0 +1,53 @@ +<?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="AdminValidateCookieLifetimeTest"> + <annotations> + <features value="Cookie"/> + <stories value="Validate cookie lifetime field in Magento admin"/> + <title value="Admin should not able to enter text in cookie lifetime filed"/> + <description value="Admin can only be able type numbers in cookie lifetime filed in Magento admin"/> + <group value="Cookie"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginGetFromGeneralFile"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + </after> + <actionGroup ref="AdminNavigateToDefaultCookieSettingsActionGroup" stepKey="navigateToDefaultCookieSettingsPage"/> + <actionGroup ref="AdminUncheckUseSystemValueActionGroup" stepKey="uncheckUseSystemValue"> + <argument name="rowId" value="row_web_cookie_cookie_lifetime"/> + </actionGroup> + <actionGroup ref="AdminFillCookieLifetimeActionGroup" stepKey="fillCookieLifetimeField"> + <argument name="cookieLifetime" value="cookie"/> + </actionGroup> + <actionGroup ref="AdminClickFormActionButtonActionGroup" stepKey="clickSaveButtonWithString"> + <argument name="buttonSelector" value="{{AdminMainActionsSection.save}}"/> + </actionGroup> + <actionGroup ref="AssertAdminValidationErrorActionGroup" stepKey="assertNumberValidation"> + <argument name="inputId" value="web_cookie_cookie_lifetime"/> + <argument name="errorMessage" value="Please enter a valid number in this field."/> + </actionGroup> + <actionGroup ref="AdminFillCookieLifetimeActionGroup" stepKey="fillCookieLifetimeFieldWithNumber"> + <argument name="cookieLifetime" value="3600"/> + </actionGroup> + <actionGroup ref="AdminCheckUseSystemValueActionGroup" stepKey="checkUseSystemValue"> + <argument name="rowId" value="row_web_cookie_cookie_lifetime"/> + </actionGroup> + <actionGroup ref="AdminClickFormActionButtonActionGroup" stepKey="clickSaveButtonWithNumber"> + <argument name="buttonSelector" value="{{AdminMainActionsSection.save}}"/> + </actionGroup> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertSaveCookieLifetimeSuccessMessage"> + <argument name="message" value="You saved the configuration."/> + <argument name="messageType" value="success"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Cookie/etc/adminhtml/system.xml b/app/code/Magento/Cookie/etc/adminhtml/system.xml index d1dcbf45ae5be..3bf9d11e0a462 100644 --- a/app/code/Magento/Cookie/etc/adminhtml/system.xml +++ b/app/code/Magento/Cookie/etc/adminhtml/system.xml @@ -10,17 +10,21 @@ <section id="web"> <group id="cookie" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Default Cookie Settings</label> - <field id="cookie_lifetime" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="cookie_lifetime" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Cookie Lifetime</label> <backend_model>Magento\Cookie\Model\Config\Backend\Lifetime</backend_model> + <comment>Enter the Cookie Lifetime in seconds.</comment> + <validate>validate-number</validate> </field> - <field id="cookie_path" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="cookie_path" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Cookie Path</label> <backend_model>Magento\Cookie\Model\Config\Backend\Path</backend_model> + <comment>Enter a valid cookie path. e.g. /</comment> </field> - <field id="cookie_domain" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="cookie_domain" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Cookie Domain</label> <backend_model>Magento\Cookie\Model\Config\Backend\Domain</backend_model> + <comment>Enter a valid domain name. e.g. "subdomain.yourdomain.com", ".yourdomain.com"</comment> </field> <field id="cookie_httponly" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Use HTTP Only</label> @@ -29,10 +33,11 @@ </comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="cookie_restriction" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> + <field id="cookie_restriction" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Cookie Restriction Mode</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Cookie\Model\Config\Backend\Cookie</backend_model> + <comment>When Cookie Restriction Mode is enabled, visitors to your store are notified that cookies are required for full-featured operations.</comment> </field> </group> </section> diff --git a/app/code/Magento/Cookie/i18n/en_US.csv b/app/code/Magento/Cookie/i18n/en_US.csv index 7fc98c0ad4c58..fcb4988b449cf 100644 --- a/app/code/Magento/Cookie/i18n/en_US.csv +++ b/app/code/Magento/Cookie/i18n/en_US.csv @@ -12,4 +12,7 @@ "Use HTTP Only","Use HTTP Only" "Cookie Restriction Mode","Cookie Restriction Mode" "Cookies are disabled in your browser.","Cookies are disabled in your browser." - +"Enter the Cookie Lifetime in seconds.","Enter the Cookie Lifetime in seconds." +"Enter a valid cookie path. e.g. /","Enter a valid cookie path. e.g. /" +"Enter a valid domain name. e.g. ""subdomain.yourdomain.com"", "".yourdomain.com""","Enter a valid domain name. e.g. ""subdomain.yourdomain.com"", "".yourdomain.com""" +"When Cookie Restriction Mode is enabled, visitors to your store are notified that cookies are required for full-featured operations.","When Cookie Restriction Mode is enabled, visitors to your store are notified that cookies are required for full-featured operations." diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml index 6194287dd058b..d22430e3c0218 100644 --- a/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml @@ -20,6 +20,36 @@ <data key="scope">websites</data> <data key="scope_code">base</data> </entity> + <entity name="SetCurrencyYENBaseConfig"> + <data key="path">currency/options/base</data> + <data key="value">JPY</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetCurrencyCADBaseConfig"> + <data key="path">currency/options/base</data> + <data key="value">CAD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetCurrencyAUDBaseConfig"> + <data key="path">currency/options/base</data> + <data key="value">AUD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetCurrencyHKDBaseConfig"> + <data key="path">currency/options/base</data> + <data key="value">HKD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetCurrencyNZDBaseConfig"> + <data key="path">currency/options/base</data> + <data key="value">NZD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> <entity name="SetAllowedCurrenciesConfigForUSD"> <data key="path">currency/options/allow</data> <data key="value">USD</data> @@ -32,6 +62,36 @@ <data key="scope">websites</data> <data key="scope_code">base</data> </entity> + <entity name="SetAllowedCurrenciesConfigForYEN"> + <data key="path">currency/options/allow</data> + <data key="value">JPY</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetAllowedCurrenciesConfigForCAD"> + <data key="path">currency/options/allow</data> + <data key="value">CAD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetAllowedCurrenciesConfigForAUD"> + <data key="path">currency/options/allow</data> + <data key="value">AUD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetAllowedCurrenciesConfigForHKD"> + <data key="path">currency/options/allow</data> + <data key="value">HKD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetAllowedCurrenciesConfigForNZD"> + <data key="path">currency/options/allow</data> + <data key="value">NZD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> <entity name="SetAllowedCurrenciesConfigForRUB"> <data key="path">currency/options/allow</data> <data key="value">RUB</data> @@ -44,6 +104,36 @@ <data key="scope">websites</data> <data key="scope_code">base</data> </entity> + <entity name="SetDefaultCurrencyYENConfig"> + <data key="path">currency/options/default</data> + <data key="value">JPY</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetDefaultCurrencyCADConfig"> + <data key="path">currency/options/default</data> + <data key="value">CAD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetDefaultCurrencyAUDConfig"> + <data key="path">currency/options/default</data> + <data key="value">AUD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetDefaultCurrencyHKDConfig"> + <data key="path">currency/options/default</data> + <data key="value">HKD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetDefaultCurrencyNZDConfig"> + <data key="path">currency/options/default</data> + <data key="value">NZD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> <entity name="SetDefaultCurrencyUSDConfig"> <data key="path">currency/options/default</data> <data key="value">USD</data> diff --git a/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php b/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php index a8622d98948df..c10ff421b7f92 100644 --- a/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php +++ b/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php @@ -8,6 +8,7 @@ use Magento\Customer\Model\Address\AddressModelInterface; use Magento\Customer\Model\Address\Mapper; use Magento\Customer\Model\Metadata\ElementFactory; +use Magento\Directory\Model\Country\Format; use Magento\Framework\View\Element\AbstractBlock; /** @@ -91,6 +92,8 @@ public function setType(\Magento\Framework\DataObject $type) } /** + * Get the format of the address + * * @param AddressModelInterface|null $address * @return string * All new code should use renderArray based on Metadata service @@ -106,8 +109,11 @@ public function getFormat(AddressModelInterface $address = null) } /** - * {@inheritdoc} + * Render address * + * @param AddressModelInterface $address + * @param string|null $format + * @return mixed * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -118,7 +124,7 @@ public function render(AddressModelInterface $address, $format = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getFormatArray($addressAttributes = null) { @@ -133,8 +139,11 @@ public function getFormatArray($addressAttributes = null) } /** - * {@inheritdoc} + * Render address by attribute array * + * @param array $addressAttributes + * @param Format|null $format + * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -167,7 +176,7 @@ public function renderArray($addressAttributes, $format = null) $addressAttributes['country_id'] )->getName(); } elseif ($attributeCode == 'region' && isset($addressAttributes['region'])) { - $data['region'] = __($addressAttributes['region']); + $data['region'] = (string)__($addressAttributes['region']); } elseif (isset($addressAttributes[$attributeCode])) { $value = $addressAttributes[$attributeCode]; $dataModel = $this->_elementFactory->create($attributeMetadata, $value, 'customer_address'); diff --git a/app/code/Magento/Customer/Controller/Account/Confirm.php b/app/code/Magento/Customer/Controller/Account/Confirm.php index 3aa08cfbf847e..a1ec3164a3c31 100644 --- a/app/code/Magento/Customer/Controller/Account/Confirm.php +++ b/app/code/Magento/Customer/Controller/Account/Confirm.php @@ -24,6 +24,8 @@ /** * Class Confirm * + * Confirm class is responsible for account confirmation flow + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Confirm extends AbstractAccount implements HttpGetActionInterface @@ -168,7 +170,7 @@ public function execute() $metadata->setPath('/'); $this->getCookieManager()->deleteCookie('mage-cache-sessid', $metadata); } - $this->messageManager->addSuccessMessage($this->getSuccessMessage()); + $this->messageManager->addSuccess($this->getSuccessMessage()); $resultRedirect->setUrl($this->getSuccessRedirect()); return $resultRedirect; } catch (StateException $e) { diff --git a/app/code/Magento/Customer/Controller/Account/CreatePost.php b/app/code/Magento/Customer/Controller/Account/CreatePost.php index e2a7c085a0b44..4c1cfa94c5565 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePost.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePost.php @@ -149,6 +149,11 @@ class CreatePost extends AbstractAccount implements CsrfAwareActionInterface, Ht */ private $customerRepository; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param Context $context * @param Session $customerSession @@ -266,9 +271,15 @@ protected function extractAddress() $addressData = []; $regionDataObject = $this->regionDataFactory->create(); + $userDefinedAttr = $this->getRequest()->getParam('address') ?: []; foreach ($allowedAttributes as $attribute) { $attributeCode = $attribute->getAttributeCode(); - $value = $this->getRequest()->getParam($attributeCode); + if ($attribute->isUserDefined()) { + $value = array_key_exists($attributeCode, $userDefinedAttr) ? $userDefinedAttr[$attributeCode] : null; + } else { + $value = $this->getRequest()->getParam($attributeCode); + } + if ($value === null) { continue; } @@ -283,6 +294,9 @@ protected function extractAddress() $addressData[$attributeCode] = $value; } } + $addressData = $addressForm->compactData($addressData); + unset($addressData['region_id'], $addressData['region']); + $addressDataObject = $this->addressDataFactory->create(); $this->dataObjectHelper->populateWithArray( $addressDataObject, diff --git a/app/code/Magento/Customer/Controller/Account/LoginPost.php b/app/code/Magento/Customer/Controller/Account/LoginPost.php index 36d04e949923f..baa08c5f86b45 100644 --- a/app/code/Magento/Customer/Controller/Account/LoginPost.php +++ b/app/code/Magento/Customer/Controller/Account/LoginPost.php @@ -67,6 +67,11 @@ class LoginPost extends AbstractAccount implements CsrfAwareActionInterface, Htt */ private $cookieMetadataManager; + /** + * @var CustomerUrl + */ + private $customerUrl; + /** * @param Context $context * @param Session $customerSession @@ -199,11 +204,11 @@ public function execute() return $resultRedirect; } } catch (EmailNotConfirmedException $e) { - $value = $this->customerUrl->getEmailConfirmationUrl($login['username']); - $message = __( - 'This account is not confirmed. <a href="%1">Click here</a> to resend confirmation email.', - $value + $this->messageManager->addComplexErrorMessage( + 'confirmAccountErrorMessage', + ['url' => $this->customerUrl->getEmailConfirmationUrl($login['username'])] ); + $this->session->setUsername($login['username']); } catch (AuthenticationException $e) { $message = __( 'The account sign-in was incorrect or your account is disabled temporarily. ' diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Address/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Address/Save.php index e4daea6f59fdb..462f5d276ef45 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Address/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Address/Save.php @@ -161,7 +161,7 @@ public function execute(): Json $resultJson = $this->resultJsonFactory->create(); $resultJson->setData( [ - 'message' => $message, + 'messages' => $message, 'error' => $error, 'data' => [ 'entity_id' => $addressId diff --git a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php index 296d2877df8ea..83aa29ea45265 100644 --- a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php +++ b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php @@ -50,13 +50,6 @@ public function getAllOptions($withEmpty = true, $defaultValues = false) $groups = $this->_groupManagement->getLoggedInGroups(); $this->_options = $this->_converter->toOptionArray($groups, 'id', 'code'); - - array_walk( - $this->_options, - function (&$item) { - $item['__disableTmpl'] = true; - } - ); } return $this->_options; diff --git a/app/code/Magento/Customer/Model/Customer/DataProvider.php b/app/code/Magento/Customer/Model/Customer/DataProvider.php index 0b7c618e6a18b..38e597e4e0fe7 100644 --- a/app/code/Magento/Customer/Model/Customer/DataProvider.php +++ b/app/code/Magento/Customer/Model/Customer/DataProvider.php @@ -365,6 +365,7 @@ private function processWebsiteMeta(&$meta) if (isset($meta[AddressInterface::COUNTRY_ID]) && !$this->getShareConfig()->isGlobalScope()) { $meta[AddressInterface::COUNTRY_ID]['arguments']['data']['config']['filterBy'] = [ 'target' => '${ $.provider }:data.customer.website_id', + '__disableTmpl' => ['target' => false], 'field' => 'website_ids' ]; } @@ -375,7 +376,7 @@ private function processWebsiteMeta(&$meta) * * @param AttributeInterface $attribute * @param array $meta - * @return array + * @return void */ private function processFrontendInput(AttributeInterface $attribute, array &$meta) { diff --git a/app/code/Magento/Customer/Model/EmailNotification.php b/app/code/Magento/Customer/Model/EmailNotification.php index b72929a9de3a5..55d82e0d7ccbe 100644 --- a/app/code/Magento/Customer/Model/EmailNotification.php +++ b/app/code/Magento/Customer/Model/EmailNotification.php @@ -170,7 +170,7 @@ public function credentialsChanged( private function emailAndPasswordChanged(CustomerInterface $customer, $email): void { $storeId = $customer->getStoreId(); - if (!$storeId) { + if ($storeId === null) { $storeId = $this->getWebsiteStoreId($customer); } @@ -196,7 +196,7 @@ private function emailAndPasswordChanged(CustomerInterface $customer, $email): v private function emailChanged(CustomerInterface $customer, $email): void { $storeId = $customer->getStoreId(); - if (!$storeId) { + if ($storeId === null) { $storeId = $this->getWebsiteStoreId($customer); } @@ -221,7 +221,7 @@ private function emailChanged(CustomerInterface $customer, $email): void private function passwordReset(CustomerInterface $customer): void { $storeId = $customer->getStoreId(); - if (!$storeId) { + if ($storeId === null) { $storeId = $this->getWebsiteStoreId($customer); } @@ -320,7 +320,7 @@ private function getWebsiteStoreId($customer, $defaultStoreId = null): int public function passwordReminder(CustomerInterface $customer): void { $storeId = $customer->getStoreId(); - if (!$storeId) { + if ($storeId === null) { $storeId = $this->getWebsiteStoreId($customer); } @@ -344,7 +344,7 @@ public function passwordReminder(CustomerInterface $customer): void public function passwordResetConfirmation(CustomerInterface $customer): void { $storeId = $customer->getStoreId(); - if (!$storeId) { + if ($storeId === null) { $storeId = $this->getWebsiteStoreId($customer); } @@ -365,7 +365,7 @@ public function passwordResetConfirmation(CustomerInterface $customer): void * @param CustomerInterface $customer * @param string $type * @param string $backUrl - * @param int $storeId + * @param int|null $storeId * @param string $sendemailStoreId * @return void * @throws LocalizedException @@ -374,7 +374,7 @@ public function newAccount( CustomerInterface $customer, $type = self::NEW_ACCOUNT_EMAIL_REGISTERED, $backUrl = '', - $storeId = 0, + $storeId = null, $sendemailStoreId = null ): void { $types = self::TEMPLATE_TYPES; @@ -385,7 +385,7 @@ public function newAccount( ); } - if (!$storeId) { + if ($storeId === null) { $storeId = $this->getWebsiteStoreId($customer, $sendemailStoreId); } diff --git a/app/code/Magento/Customer/Model/Vat.php b/app/code/Magento/Customer/Model/Vat.php index 123a9eef4b75a..d1144c2e7a662 100644 --- a/app/code/Magento/Customer/Model/Vat.php +++ b/app/code/Magento/Customer/Model/Vat.php @@ -44,7 +44,7 @@ class Vat * WSDL of VAT validation service * */ - const VAT_VALIDATION_WSDL_URL = 'http://ec.europa.eu/taxation_customs/vies/services/checkVatService?wsdl'; + const VAT_VALIDATION_WSDL_URL = 'https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl'; /** * Config path to option that enables/disables automatic group assignment based on VAT diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml index 87c612db08698..6cffb58df75bd 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml @@ -28,6 +28,8 @@ <click stepKey="delete" selector="{{AdminCustomerGridMainActionsSection.delete}}"/> <waitForPageLoad stepKey="waitForConfirmationAlert"/> <click stepKey="accept" selector="{{AdminCustomerGridMainActionsSection.ok}}"/> - <see stepKey="seeSuccessMessage" userInput="were deleted."/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="A total of 1 record(s) were deleted." stepKey="seeSuccessMessage"/> + <waitForPageLoad stepKey="waitForCustomersGridIsLoaded"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml index 56afa8854ce0d..e4b07e44c0306 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml @@ -24,6 +24,7 @@ <fillField stepKey="fillEmail" userInput="{{Customer.email}}" selector="{{StorefrontCustomerCreateFormSection.emailField}}"/> <fillField stepKey="fillPassword" userInput="{{Customer.password}}" selector="{{StorefrontCustomerCreateFormSection.passwordField}}"/> <fillField stepKey="fillConfirmPassword" userInput="{{Customer.password}}" selector="{{StorefrontCustomerCreateFormSection.confirmPasswordField}}"/> + <waitForPageLoad stepKey="waitForCreateAccountButtonIsActive"/> <click stepKey="clickCreateAccountButton" selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}"/> <see stepKey="seeThankYouMessage" userInput="Thank you for registering with Main Website Store."/> <see stepKey="seeFirstName" userInput="{{Customer.firstname}}" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml index 3c82426e81246..f4d30e501fc43 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml @@ -12,7 +12,8 @@ <annotations> <description>Clicks on the Storefront Header 'Create Account' link.</description> </annotations> - + + <waitForPageLoad stepKey="waitForCreateAccountButtonIsActive"/> <click stepKey="clickCreateAccountButton" selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}"/> <waitForPageLoad stepKey="waitForCustomerSaved"/> </actionGroup> diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php index 5565a807b8135..1cbfb194c50d9 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php @@ -23,77 +23,77 @@ class ConfirmTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\RequestInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $requestMock; /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\ResponseInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $responseMock; /** - * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Model\Session|\PHPUnit\Framework\MockObject\MockObject */ protected $customerSessionMock; /** - * @var \Magento\Framework\App\Response\RedirectInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Response\RedirectInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $redirectMock; /** - * @var \Magento\Framework\Url|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Url|\PHPUnit\Framework\MockObject\MockObject */ protected $urlMock; /** - * @var \Magento\Customer\Api\AccountManagementInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Api\AccountManagementInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $customerAccountManagementMock; /** - * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $customerRepositoryMock; /** - * @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $customerDataMock; /** - * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $messageManagerMock; /** - * @var \Magento\Customer\Helper\Address|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Helper\Address|\PHPUnit\Framework\MockObject\MockObject */ protected $addressHelperMock; /** - * @var \Magento\Store\Model\StoreManager|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\StoreManager|\PHPUnit\Framework\MockObject\MockObject */ protected $storeManagerMock; /** - * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\Store|\PHPUnit\Framework\MockObject\MockObject */ protected $storeMock; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit\Framework\MockObject\MockObject */ protected $scopeConfigMock; /** - * @var \Magento\Framework\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Action\Context|\PHPUnit\Framework\MockObject\MockObject */ protected $contextMock; /** - * @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit\Framework\MockObject\MockObject */ protected $redirectResultMock; @@ -282,7 +282,7 @@ public function testSuccessMessage($customerId, $key, $vatValidationEnabled, $ad ->willReturnSelf(); $this->messageManagerMock->expects($this->any()) - ->method('addSuccessMessage') + ->method('addSuccess') ->with($this->stringContains($successMessage)) ->willReturnSelf(); @@ -402,7 +402,7 @@ public function testSuccessRedirect( ->willReturnSelf(); $this->messageManagerMock->expects($this->any()) - ->method('addSuccessMessage') + ->method('addSuccess') ->with($this->stringContains($successMessage)) ->willReturnSelf(); diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php index 05a8b6448af99..c098aba4504c1 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Account/LoginPostTest.php @@ -28,62 +28,62 @@ class LoginPostTest extends \PHPUnit\Framework\TestCase protected $controller; /** - * @var Context | \PHPUnit_Framework_MockObject_MockObject + * @var Context | \PHPUnit\Framework\MockObject\MockObject */ protected $context; /** - * @var Session | \PHPUnit_Framework_MockObject_MockObject + * @var Session | \PHPUnit\Framework\MockObject\MockObject */ protected $session; /** - * @var AccountManagementInterface | \PHPUnit_Framework_MockObject_MockObject + * @var AccountManagementInterface | \PHPUnit\Framework\MockObject\MockObject */ protected $accountManagement; /** - * @var Url | \PHPUnit_Framework_MockObject_MockObject + * @var Url | \PHPUnit\Framework\MockObject\MockObject */ protected $url; /** - * @var \Magento\Framework\Data\Form\FormKey\Validator | \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Data\Form\FormKey\Validator | \PHPUnit\Framework\MockObject\MockObject */ protected $formkeyValidator; /** - * @var AccountRedirect | \PHPUnit_Framework_MockObject_MockObject + * @var AccountRedirect | \PHPUnit\Framework\MockObject\MockObject */ protected $accountRedirect; /** - * @var Http | \PHPUnit_Framework_MockObject_MockObject + * @var Http | \PHPUnit\Framework\MockObject\MockObject */ protected $request; /** - * @var Redirect | \PHPUnit_Framework_MockObject_MockObject + * @var Redirect | \PHPUnit\Framework\MockObject\MockObject */ protected $resultRedirect; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit\Framework\MockObject\MockObject */ protected $redirectFactory; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit\Framework\MockObject\MockObject */ protected $redirect; /** - * @var \Magento\Framework\Message\ManagerInterface | \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Message\ManagerInterface | \PHPUnit\Framework\MockObject\MockObject */ protected $messageManager; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface | \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Config\ScopeConfigInterface | \PHPUnit\Framework\MockObject\MockObject */ protected $scopeConfig; @@ -551,14 +551,12 @@ protected function mockExceptions($exception, $username) ->with($username) ->willReturn($url); - $message = __( - 'This account is not confirmed.' . - ' <a href="%1">Click here</a> to resend confirmation email.', - $url - ); $this->messageManager->expects($this->once()) - ->method('addErrorMessage') - ->with($message) + ->method('addComplexErrorMessage') + ->with( + 'confirmAccountErrorMessage', + ['url' => $url] + ) ->willReturnSelf(); $this->session->expects($this->once()) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Address/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Address/SaveTest.php index 1bf881ff0a933..1ad404416e552 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Address/SaveTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Address/SaveTest.php @@ -1,74 +1,86 @@ <?php -declare(strict_types=1); /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Test\Unit\Controller\Address; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\AddressInterfaceFactory; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Controller\Adminhtml\Address\Save; +use Magento\Customer\Model\Metadata\Form; +use Magento\Customer\Model\Metadata\FormFactory; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\App\RequestInterface; use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class SaveTest extends \PHPUnit\Framework\TestCase +class SaveTest extends TestCase { /** - * @var \Magento\Customer\Controller\Adminhtml\Address\Save + * @var Save */ private $model; /** - * @var \Magento\Customer\Api\AddressRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var AddressRepositoryInterface|MockObject */ private $addressRepositoryMock; /** - * @var \Magento\Customer\Model\Metadata\FormFactory|\PHPUnit_Framework_MockObject_MockObject + * @var FormFactory|MockObject */ private $formFactoryMock; /** - * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CustomerRepositoryInterface|MockObject */ private $customerRepositoryMock; /** - * @var \Magento\Framework\Api\DataObjectHelper|\PHPUnit_Framework_MockObject_MockObject + * @var DataObjectHelper|MockObject */ private $dataObjectHelperMock; /** - * @var \Magento\Customer\Api\Data\AddressInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var AddressInterfaceFactory|MockObject */ private $addressDataFactoryMock; /** - * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var LoggerInterface|MockObject */ private $loggerMock; /** - * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + * @var RequestInterface|MockObject */ private $requestMock; /** - * @var AddressInterface|\PHPUnit_Framework_MockObject_MockObject + * @var AddressInterface|MockObject */ private $address; /** - * @var JsonFactory|\PHPUnit_Framework_MockObject_MockObject + * @var JsonFactory|MockObject */ private $resultJsonFactory; /** - * @var Json|\PHPUnit_Framework_MockObject_MockObject + * @var Json|MockObject */ private $json; @@ -77,13 +89,13 @@ class SaveTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->addressRepositoryMock = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class); - $this->formFactoryMock = $this->createMock(\Magento\Customer\Model\Metadata\FormFactory::class); - $this->customerRepositoryMock = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $this->dataObjectHelperMock = $this->createMock(\Magento\Framework\Api\DataObjectHelper ::class); - $this->addressDataFactoryMock = $this->createMock(\Magento\Customer\Api\Data\AddressInterfaceFactory::class); - $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - $this->requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); + $this->addressRepositoryMock = $this->createMock(AddressRepositoryInterface::class); + $this->formFactoryMock = $this->createMock(FormFactory::class); + $this->customerRepositoryMock = $this->createMock(CustomerRepositoryInterface::class); + $this->dataObjectHelperMock = $this->createMock(DataObjectHelper ::class); + $this->addressDataFactoryMock = $this->createMock(AddressInterfaceFactory::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); + $this->requestMock = $this->createMock(RequestInterface::class); $this->address = $this->getMockBuilder(AddressInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -98,7 +110,7 @@ protected function setUp() $objectManager = new ObjectManagerHelper($this); $this->model = $objectManager->getObject( - \Magento\Customer\Controller\Adminhtml\Address\Save::class, + Save::class, [ 'addressRepository' => $this->addressRepositoryMock, 'formFactory' => $this->formFactoryMock, @@ -153,7 +165,7 @@ public function testExecute(): void ->willReturnOnConsecutiveCalls(22, 1); $customerMock = $this->getMockBuilder( - \Magento\Customer\Api\Data\CustomerInterface::class + CustomerInterface::class )->disableOriginalConstructor()->getMock(); $this->customerRepositoryMock->expects($this->atLeastOnce()) @@ -161,7 +173,7 @@ public function testExecute(): void ->with($customerId) ->willReturn($customerMock); - $customerAddressFormMock = $this->createMock(\Magento\Customer\Model\Metadata\Form::class); + $customerAddressFormMock = $this->createMock(Form::class); $customerAddressFormMock->expects($this->atLeastOnce()) ->method('extractData') ->with($this->requestMock) @@ -200,7 +212,7 @@ public function testExecute(): void ->method('setData') ->with( [ - 'message' => __('Customer address has been updated.'), + 'messages' => __('Customer address has been updated.'), 'error' => false, 'data' => [ 'entity_id' => $addressId diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php index 8ad7363a1c310..f98759d3f067a 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php @@ -26,7 +26,7 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Ui\Component\Form\Field; use Magento\Ui\DataProvider\EavValidationRules; -use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\MockObject\MockObject; /** * Unit tests for \Magento\Customer\Model\Customer\DataProvider class. @@ -243,6 +243,7 @@ public function getAttributesMetaDataProvider() 'componentType' => Field::NAME, 'filterBy' => [ 'target' => '${ $.provider }:data.customer.website_id', + '__disableTmpl' => ['target' => false], 'field' => 'website_ids', ], ], @@ -1280,6 +1281,7 @@ private function getExpectationForVisibleAttributes() 'componentType' => Field::NAME, 'filterBy' => [ 'target' => '${ $.provider }:data.customer.website_id', + '__disableTmpl' => ['target' => false], 'field' => 'website_ids', ], ], diff --git a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php index a8bf94247fd6d..ab90eacbb6032 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php @@ -3,22 +3,29 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Customer\Test\Unit\Model; use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Helper\View; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Customer\Model\Data\CustomerSecure; use Magento\Customer\Model\EmailNotification; use Magento\Framework\App\Area; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Helper\Context; use Magento\Framework\Mail\Template\SenderResolverInterface; +use Magento\Framework\Mail\Template\TransportBuilder; +use Magento\Framework\Mail\TransportInterface; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Store\Model\ScopeInterface; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\MockObject\MockObject; use Magento\Store\Model\Store; -use Magento\Customer\Model\Data\CustomerSecure; +use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Model\Website; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * Unit test for \Magento\Customer\Model\EmailNotification @@ -32,11 +39,6 @@ class EmailNotificationTest extends TestCase */ private const STUB_CUSTOMER_ID = 1; - /** - * @var int - */ - private const STUB_CUSTOMER_STORE_ID = 2; - /** * @var int */ @@ -63,27 +65,27 @@ class EmailNotificationTest extends TestCase private const STUB_SENDER = 'Sender'; /** - * @var \Magento\Customer\Model\CustomerRegistry|MockObject + * @var CustomerRegistry|MockObject */ private $customerRegistryMock; /** - * @var \Magento\Store\Model\StoreManagerInterface|MockObject + * @var StoreManagerInterface|MockObject */ private $storeManagerMock; /** - * @var \Magento\Framework\Mail\Template\TransportBuilder|MockObject + * @var TransportBuilder|MockObject */ private $transportBuilderMock; /** - * @var \Magento\Customer\Helper\View|MockObject + * @var View|MockObject */ private $customerViewHelperMock; /** - * @var \Magento\Framework\Reflection\DataObjectProcessor|MockObject + * @var DataObjectProcessor|MockObject */ private $dataProcessorMock; @@ -93,12 +95,12 @@ class EmailNotificationTest extends TestCase private $customerSecureMock; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|MockObject + * @var ScopeConfigInterface|MockObject */ private $scopeConfigMock; /** - * @var \Magento\Store\Model\Store|MockObject + * @var Store|MockObject */ private $storeMock; @@ -115,22 +117,18 @@ class EmailNotificationTest extends TestCase /** * @inheritdoc */ - public function setUp(): void + public function setUp():void { - $this->customerRegistryMock = $this->createMock(\Magento\Customer\Model\CustomerRegistry::class); - - $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - - $this->transportBuilderMock = $this->createMock(\Magento\Framework\Mail\Template\TransportBuilder::class); - - $this->customerViewHelperMock = $this->createMock(\Magento\Customer\Helper\View::class); - - $this->dataProcessorMock = $this->createMock(\Magento\Framework\Reflection\DataObjectProcessor::class); + $this->customerRegistryMock = $this->createMock(CustomerRegistry::class); + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->transportBuilderMock = $this->createMock(TransportBuilder::class); + $this->customerViewHelperMock = $this->createMock(View::class); + $this->dataProcessorMock = $this->createMock(DataObjectProcessor::class); - $contextMock = $this->createPartialMock(\Magento\Framework\App\Helper\Context::class, ['getScopeConfig']); + $contextMock = $this->createPartialMock(Context::class, ['getScopeConfig']); $this->scopeConfigMock = $this->createPartialMock( - \Magento\Framework\App\Config\ScopeConfigInterface::class, + ScopeConfigInterface::class, ['getValue', 'isSetFlag'] ); @@ -167,15 +165,22 @@ public function setUp(): void * Test email notify when credentials changed * * @param int $testNumber + * @param int $customerStoreId * @param string $oldEmail * @param string $newEmail * @param bool $isPasswordChanged - * * @dataProvider sendNotificationEmailsDataProvider + * + * @return void * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $newEmail, $isPasswordChanged): void - { + public function testEmailNotifyWhenCredentialsChanged( + $testNumber, + $customerStoreId, + $oldEmail, + $newEmail, + $isPasswordChanged + ):void { $customerData = ['key' => 'value']; $senderValues = ['name' => self::STUB_SENDER, 'email' => self::STUB_SENDER]; @@ -199,27 +204,26 @@ public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $n $this->senderResolverMock ->expects($expects) ->method('resolve') - ->with(self::STUB_SENDER, self::STUB_CUSTOMER_STORE_ID) + ->with(self::STUB_SENDER, $customerStoreId) ->willReturn($senderValues); /** - * @var MockObject $origCustomer + * @var MockObject $origCustomerMock */ - $origCustomer = $this->createMock(CustomerInterface::class); - $origCustomer->expects($this->any()) + $origCustomerMock = $this->createMock(CustomerInterface::class); + $origCustomerMock->expects($this->any()) ->method('getStoreId') - ->willReturn(0); - $origCustomer->expects($this->any()) + ->willReturn($customerStoreId); + $origCustomerMock->expects($this->any()) ->method('getId') ->willReturn(self::STUB_CUSTOMER_ID); - $origCustomer->expects($this->any()) - ->method('getWebsiteId') - ->willReturn(self::STUB_CUSTOMER_WEBSITE_ID); + $origCustomerMock->expects($this->never()) + ->method('getWebsiteId'); $storeMock = $this->createMock(Store::class); $storeMock->expects($this->any()) ->method('getId') - ->willReturn(self::STUB_CUSTOMER_STORE_ID); + ->willReturn($customerStoreId); $this->storeManagerMock->expects(clone $expects) ->method('getStore') @@ -228,12 +232,10 @@ public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $n $websiteMock = $this->createPartialMock(Website::class, ['getStoreIds']); $websiteMock->expects($this->any()) ->method('getStoreIds') - ->willReturn([self::STUB_CUSTOMER_STORE_ID]); + ->willReturn([$customerStoreId]); - $this->storeManagerMock->expects(clone $expects) - ->method('getWebsite') - ->with(self::STUB_CUSTOMER_WEBSITE_ID) - ->willReturn($websiteMock); + $this->storeManagerMock->expects($this->never()) + ->method('getWebsite'); $customerSecureMock = $this->createMock(CustomerSecure::class); $this->customerRegistryMock->expects(clone $expects) @@ -243,12 +245,12 @@ public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $n $this->dataProcessorMock->expects(clone $expects) ->method('buildOutputDataArray') - ->with($origCustomer, CustomerInterface::class) + ->with($origCustomerMock, CustomerInterface::class) ->willReturn($customerData); $this->customerViewHelperMock->expects($this->any()) ->method('getCustomerName') - ->with($origCustomer) + ->with($origCustomerMock) ->willReturn(self::STUB_CUSTOMER_NAME); $customerSecureMock->expects(clone $expects) @@ -263,9 +265,9 @@ public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $n /** * @var CustomerInterface|MockObject $savedCustomer */ - $savedCustomer = clone $origCustomer; + $savedCustomer = clone $origCustomerMock; - $origCustomer->expects($this->any()) + $origCustomerMock->expects($this->any()) ->method('getEmail') ->willReturn($oldEmail); @@ -279,22 +281,22 @@ public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $n [ $xmlPathTemplate, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId ], [ EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId ], [ $xmlPathTemplate, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId ], [ EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId ] )->willReturnOnConsecutiveCalls( self::STUB_EMAIL_IDENTIFIER, @@ -309,7 +311,7 @@ public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $n ->willReturnSelf(); $this->transportBuilderMock->expects(clone $expects) ->method('setTemplateOptions') - ->with(['area' => Area::AREA_FRONTEND, 'store' => self::STUB_CUSTOMER_STORE_ID]) + ->with(['area' => Area::AREA_FRONTEND, 'store' => $customerStoreId]) ->willReturnSelf(); $this->transportBuilderMock->expects(clone $expects) ->method('setTemplateVars') @@ -325,7 +327,7 @@ public function testEmailNotifyWhenCredentialsChanged($testNumber, $oldEmail, $n ->withConsecutive([$oldEmail, self::STUB_CUSTOMER_NAME], [$newEmail, self::STUB_CUSTOMER_NAME]) ->willReturnSelf(); - $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class); + $transport = $this->createMock(TransportInterface::class); $this->transportBuilderMock->expects(clone $expects) ->method('getTransport') @@ -348,18 +350,42 @@ public function sendNotificationEmailsDataProvider(): array return [ [ 'test_number' => 1, + 'customerStoreId' => 0, + 'old_email' => 'test@example.com', + 'new_email' => 'test@example.com', + 'is_password_changed' => true + ], + [ + 'test_number' => 1, + 'customerStoreId' => 2, 'old_email' => 'test@example.com', 'new_email' => 'test@example.com', 'is_password_changed' => true ], [ 'test_number' => 2, + 'customerStoreId' => 0, 'old_email' => 'test1@example.com', 'new_email' => 'test2@example.com', 'is_password_changed' => false ], + [ + 'test_number' => 2, + 'customerStoreId' => 2, + 'old_email' => 'test1@example.com', + 'new_email' => 'test2@example.com', + 'is_password_changed' => false + ], + [ + 'test_number' => 3, + 'customerStoreId' => 0, + 'old_email' => 'test1@example.com', + 'new_email' => 'test2@example.com', + 'is_password_changed' => true + ], [ 'test_number' => 3, + 'customerStoreId' => 2, 'old_email' => 'test1@example.com', 'new_email' => 'test2@example.com', 'is_password_changed' => true @@ -370,9 +396,12 @@ public function sendNotificationEmailsDataProvider(): array /** * Test Password Reminder Email Notify * + * @param int $customerStoreId + * @dataProvider customerStoreIdDataProvider + * @return void * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testPasswordReminder(): void + public function testPasswordReminder($customerStoreId):void { $customerData = ['key' => 'value']; $senderValues = ['name' => self::STUB_SENDER, 'email' => self::STUB_SENDER]; @@ -381,29 +410,31 @@ public function testPasswordReminder(): void $this->senderResolverMock ->expects($this->once()) ->method('resolve') - ->with(self::STUB_SENDER, self::STUB_CUSTOMER_STORE_ID) + ->with(self::STUB_SENDER, $customerStoreId) ->willReturn($senderValues); /** - * @var CustomerInterface|MockObject $customer + * @var CustomerInterface|MockObject $customerMock */ - $customer = $this->createMock(CustomerInterface::class); - $customer->expects($this->any()) + $customerMock = $this->createMock(CustomerInterface::class); + $customerMock->expects($this->never()) + ->method('getWebsiteId'); + $customerMock->expects($this->any()) ->method('getWebsiteId') ->willReturn(self::STUB_CUSTOMER_WEBSITE_ID); - $customer->expects($this->any()) + $customerMock->expects($this->any()) ->method('getStoreId') - ->willReturn(self::STUB_CUSTOMER_STORE_ID); - $customer->expects($this->any()) + ->willReturn($customerStoreId); + $customerMock->expects($this->any()) ->method('getId') ->willReturn(self::STUB_CUSTOMER_ID); - $customer->expects($this->any()) + $customerMock->expects($this->any()) ->method('getEmail') ->willReturn(self::STUB_CUSTOMER_EMAIL); $this->storeMock->expects($this->any()) ->method('getId') - ->willReturn(self::STUB_CUSTOMER_STORE_ID); + ->willReturn($customerStoreId); $this->storeManagerMock->expects($this->at(0)) ->method('getStore') @@ -426,12 +457,12 @@ public function testPasswordReminder(): void $this->dataProcessorMock->expects($this->once()) ->method('buildOutputDataArray') - ->with($customer, CustomerInterface::class) + ->with($customerMock, CustomerInterface::class) ->willReturn($customerData); $this->customerViewHelperMock->expects($this->any()) ->method('getCustomerName') - ->with($customer) + ->with($customerMock) ->willReturn(self::STUB_CUSTOMER_NAME); $this->customerSecureMock->expects($this->once()) @@ -448,26 +479,26 @@ public function testPasswordReminder(): void ->with( EmailNotification::XML_PATH_REMIND_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId )->willReturn(self::STUB_EMAIL_IDENTIFIER); $this->scopeConfigMock->expects($this->at(1)) ->method('getValue') ->with( EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId )->willReturn(self::STUB_SENDER); $this->mockDefaultTransportBuilder( self::STUB_EMAIL_IDENTIFIER, - self::STUB_CUSTOMER_STORE_ID, + $customerStoreId, $senderValues, self::STUB_CUSTOMER_EMAIL, self::STUB_CUSTOMER_NAME, ['customer' => $this->customerSecureMock, 'store' => $this->storeMock] ); - $this->model->passwordReminder($customer); + $this->model->passwordReminder($customerMock); } /** @@ -475,7 +506,7 @@ public function testPasswordReminder(): void * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testPasswordReminderCustomerWithoutStoreId(): void + public function testPasswordReminderCustomerWithoutStoreId():void { $customerStoreId = null; $customerData = ['key' => 'value']; @@ -570,9 +601,12 @@ public function testPasswordReminderCustomerWithoutStoreId(): void /** * Test email notify for password reset confirm * + * @dataProvider customerStoreIdDataProvider + * @param int $customerStoreId + * @return void * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testPasswordResetConfirmation(): void + public function testPasswordResetConfirmation($customerStoreId):void { $customerData = ['key' => 'value']; $senderValues = ['name' => self::STUB_SENDER, 'email' => self::STUB_SENDER]; @@ -580,26 +614,30 @@ public function testPasswordResetConfirmation(): void $this->senderResolverMock ->expects($this->once()) ->method('resolve') - ->with(self::STUB_SENDER, self::STUB_CUSTOMER_STORE_ID) + ->with(self::STUB_SENDER, $customerStoreId) ->willReturn($senderValues); /** - * @var CustomerInterface|MockObject $customer + * @var CustomerInterface|MockObject $customerMock */ - $customer = $this->createMock(CustomerInterface::class); - $customer->expects($this->once()) + $customerMock = $this->createMock(CustomerInterface::class); + + $customerMock->expects($this->never()) + ->method('getWebsiteId'); + + $customerMock->expects($this->once()) ->method('getStoreId') - ->willReturn(self::STUB_CUSTOMER_STORE_ID); - $customer->expects($this->any()) + ->willReturn($customerStoreId); + $customerMock->expects($this->any()) ->method('getId') ->willReturn(self::STUB_CUSTOMER_ID); - $customer->expects($this->any()) + $customerMock->expects($this->any()) ->method('getEmail') ->willReturn(self::STUB_CUSTOMER_EMAIL); $this->storeMock->expects($this->any()) ->method('getId') - ->willReturn(self::STUB_CUSTOMER_STORE_ID); + ->willReturn($customerStoreId); $this->storeManagerMock->expects($this->at(0)) ->method('getStore') @@ -612,12 +650,12 @@ public function testPasswordResetConfirmation(): void $this->dataProcessorMock->expects($this->once()) ->method('buildOutputDataArray') - ->with($customer, CustomerInterface::class) + ->with($customerMock, CustomerInterface::class) ->willReturn($customerData); $this->customerViewHelperMock->expects($this->any()) ->method('getCustomerName') - ->with($customer) + ->with($customerMock) ->willReturn(self::STUB_CUSTOMER_NAME); $this->customerSecureMock->expects($this->once()) @@ -634,35 +672,37 @@ public function testPasswordResetConfirmation(): void ->with( EmailNotification::XML_PATH_FORGOT_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId )->willReturn(self::STUB_EMAIL_IDENTIFIER); $this->scopeConfigMock->expects($this->at(1)) ->method('getValue') ->with( EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId )->willReturn(self::STUB_SENDER); $this->mockDefaultTransportBuilder( self::STUB_EMAIL_IDENTIFIER, - self::STUB_CUSTOMER_STORE_ID, + $customerStoreId, $senderValues, self::STUB_CUSTOMER_EMAIL, self::STUB_CUSTOMER_NAME, ['customer' => $this->customerSecureMock, 'store' => $this->storeMock] ); - $this->model->passwordResetConfirmation($customer); + $this->model->passwordResetConfirmation($customerMock); } /** * Test email notify with new account * - * @param void + * @dataProvider customerStoreIdDataProvider + * @param int $customerStoreId + * @return void * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testNewAccount(): void + public function testNewAccount($customerStoreId):void { $customerData = ['key' => 'value']; $senderValues = ['name' => self::STUB_SENDER, 'email' => self::STUB_SENDER]; @@ -670,16 +710,18 @@ public function testNewAccount(): void $this->senderResolverMock ->expects($this->once()) ->method('resolve') - ->with(self::STUB_SENDER, self::STUB_CUSTOMER_STORE_ID) + ->with(self::STUB_SENDER, $customerStoreId) ->willReturn($senderValues); /** * @var CustomerInterface|MockObject $customer */ $customer = $this->createMock(CustomerInterface::class); + $customer->expects($this->never()) + ->method('getWebsiteId'); $customer->expects($this->any()) ->method('getStoreId') - ->willReturn(self::STUB_CUSTOMER_STORE_ID); + ->willReturn($customerStoreId); $customer->expects($this->any()) ->method('getId') ->willReturn(self::STUB_CUSTOMER_ID); @@ -689,11 +731,11 @@ public function testNewAccount(): void $this->storeMock->expects($this->any()) ->method('getId') - ->willReturn(self::STUB_CUSTOMER_STORE_ID); + ->willReturn($customerStoreId); $this->storeManagerMock->expects($this->once()) ->method('getStore') - ->with(self::STUB_CUSTOMER_STORE_ID) + ->with($customerStoreId) ->willReturn($this->storeMock); $this->customerRegistryMock->expects($this->once()) @@ -725,19 +767,19 @@ public function testNewAccount(): void ->with( EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId )->willReturn(self::STUB_EMAIL_IDENTIFIER); $this->scopeConfigMock->expects($this->at(1)) ->method('getValue') ->with( EmailNotification::XML_PATH_REGISTER_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, - self::STUB_CUSTOMER_STORE_ID + $customerStoreId )->willReturn(self::STUB_SENDER); $this->mockDefaultTransportBuilder( self::STUB_EMAIL_IDENTIFIER, - self::STUB_CUSTOMER_STORE_ID, + $customerStoreId, $senderValues, self::STUB_CUSTOMER_EMAIL, self::STUB_CUSTOMER_NAME, @@ -748,10 +790,23 @@ public function testNewAccount(): void $customer, EmailNotification::NEW_ACCOUNT_EMAIL_REGISTERED, '', - self::STUB_CUSTOMER_STORE_ID + $customerStoreId ); } + /** + * DataProvider customer store + * + * @return array + */ + public function customerStoreIdDataProvider():array + { + return [ + ['customerStoreId' => 0], + ['customerStoreId' => 2] + ]; + } + /** * Create default mock for $this->transportBuilderMock. * @@ -771,8 +826,8 @@ private function mockDefaultTransportBuilder( string $customerEmail, string $customerName, array $templateVars = [] - ): void { - $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class); + ):void { + $transportMock = $this->createMock(TransportInterface::class); $this->transportBuilderMock->expects($this->once()) ->method('setTemplateIdentifier') @@ -796,9 +851,9 @@ private function mockDefaultTransportBuilder( ->willReturnSelf(); $this->transportBuilderMock->expects($this->once()) ->method('getTransport') - ->willReturn($transport); + ->willReturn($transportMock); - $transport->expects($this->once()) + $transportMock->expects($this->once()) ->method('sendMessage'); } } diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php index d917cc4908ac8..8b926e8dfdec8 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/ColumnFactoryTest.php @@ -6,28 +6,29 @@ namespace Magento\Customer\Test\Unit\Ui\Component; use Magento\Customer\Ui\Component\ColumnFactory; +use PHPUnit\Framework\MockObject\MockObject; /** * Test ColumnFactory Class */ class ColumnFactoryTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\Customer\Api\Data\OptionInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Customer\Api\Data\OptionInterface|MockObject */ protected $attributeOption; - /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|MockObject */ protected $context; - /** @var \Magento\Framework\View\Element\UiComponentFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponentFactory|MockObject */ protected $componentFactory; - /** @var \Magento\Customer\Api\Data\AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Customer\Api\Data\AttributeMetadataInterface|MockObject */ protected $attributeMetadata; - /** @var \Magento\Ui\Component\Listing\Columns\ColumnInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Ui\Component\Listing\Columns\ColumnInterface|MockObject */ protected $column; - /** @var \Magento\Customer\Ui\Component\Listing\Column\InlineEditUpdater|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Customer\Ui\Component\Listing\Column\InlineEditUpdater|MockObject */ protected $inlineEditUpdater; /** @var ColumnFactory */ @@ -93,7 +94,6 @@ public function testCreate() ] ], 'component' => 'Magento_Ui/js/grid/columns/column', - '__disableTmpl' => 'true' ], ], 'context' => $this->context, diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php index f3c0a56262622..3a23cf66faddc 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/FilterFactoryTest.php @@ -6,25 +6,26 @@ namespace Magento\Customer\Test\Unit\Ui\Component; use Magento\Customer\Ui\Component\FilterFactory; +use PHPUnit\Framework\MockObject\MockObject; /** * Test FilterFactory Class */ class FilterFactoryTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\Customer\Api\Data\OptionInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Customer\Api\Data\OptionInterface|MockObject */ protected $attributeOption; - /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|MockObject */ protected $context; - /** @var \Magento\Framework\View\Element\UiComponentFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponentFactory|MockObject */ protected $componentFactory; - /** @var \Magento\Customer\Api\Data\AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Customer\Api\Data\AttributeMetadataInterface|MockObject */ protected $attributeMetadata; - /** @var \Magento\Ui\Component\Listing\Columns\ColumnInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Ui\Component\Listing\Columns\ColumnInterface|MockObject */ protected $filter; /** @var FilterFactory */ @@ -72,7 +73,6 @@ public function testCreate() 'config' => [ 'dataScope' => $filterName, 'label' => __('Label'), - '__disableTmpl' => 'true', 'options' => [['value' => 'Value', 'label' => 'Label']], 'caption' => __('Select...'), ], diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php index 4a16acd98d827..3792f0d214537 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ActionsTest.php @@ -6,22 +6,23 @@ namespace Magento\Customer\Test\Unit\Ui\Component\Listing\Column; use Magento\Customer\Ui\Component\Listing\Column\Actions; +use PHPUnit\Framework\MockObject\MockObject; /** - * Class ActionsTest + * Class Actions test for Listing Colummn */ class ActionsTest extends \PHPUnit\Framework\TestCase { /** @var Actions */ protected $component; - /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|MockObject */ protected $context; - /** @var \Magento\Framework\View\Element\UiComponentFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponentFactory|MockObject */ protected $uiComponentFactory; - /** @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\UrlInterface|MockObject */ protected $urlBuilder; public function setup() @@ -68,7 +69,6 @@ public function testPrepareDataSource() 'href' => 'http://magento.com/customer/index/edit', 'label' => new \Magento\Framework\Phrase('Edit'), 'hidden' => false, - '__disableTmpl' => true, ] ] ], diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/GroupActionsTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/GroupActionsTest.php index 02cacea5c2601..deec8ab7f7281 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/GroupActionsTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/GroupActionsTest.php @@ -202,7 +202,6 @@ public function testPrepareDataSourceWithDefaultGroup() 'edit' => [ 'href' => static::STUB_GROUP_EDIT_URL, 'label' => __('Edit'), - '__disableTmpl' => true, ] ] ], @@ -213,7 +212,6 @@ public function testPrepareDataSourceWithDefaultGroup() 'edit' => [ 'href' => static::STUB_GROUP_EDIT_URL, 'label' => __('Edit'), - '__disableTmpl' => true, ] ] ] @@ -289,13 +287,11 @@ public function customerGroupsDataProvider(): array 'edit' => [ 'href' => static::STUB_GROUP_EDIT_URL, 'label' => __('Edit'), - '__disableTmpl' => true, ], 'delete' => [ 'href' => static::STUB_GROUP_DELETE_URL, 'label' => __('Delete'), 'post' => true, - '__disableTmpl' => true, 'confirm' => [ 'title' => __('Delete %1', 'General'), 'message' => __( diff --git a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php index a69e84ab41a2c..22f1a3315595b 100644 --- a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php @@ -77,7 +77,6 @@ public function create(array $attributeData, $columnName, $context, array $confi 'component' => $this->getJsComponent( $this->getDataType($attributeData[AttributeMetadata::FRONTEND_INPUT]) ), - '__disableTmpl' => 'true' ], $config ); diff --git a/app/code/Magento/Customer/Ui/Component/FilterFactory.php b/app/code/Magento/Customer/Ui/Component/FilterFactory.php index 3e57db06246d4..7575062dd23db 100644 --- a/app/code/Magento/Customer/Ui/Component/FilterFactory.php +++ b/app/code/Magento/Customer/Ui/Component/FilterFactory.php @@ -48,7 +48,6 @@ public function create(array $attributeData, $context) $config = [ 'dataScope' => $attributeData[AttributeMetadata::ATTRIBUTE_CODE], 'label' => __($attributeData[AttributeMetadata::FRONTEND_LABEL]), - '__disableTmpl' => 'true' ]; if ($attributeData[AttributeMetadata::OPTIONS]) { $config['options'] = $attributeData[AttributeMetadata::OPTIONS]; diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php index 9441beeb7dc61..d6a4067ef3db6 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/Actions.php @@ -60,7 +60,6 @@ public function prepareDataSource(array $dataSource) ), 'label' => __('Edit'), 'hidden' => false, - '__disableTmpl' => true ]; } } diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php index e5a536dc6ecd6..459ac3e29e993 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php @@ -96,7 +96,6 @@ public function prepareDataSource(array $dataSource) ] ), 'label' => __('Edit'), - '__disableTmpl' => true ], ]; @@ -117,7 +116,6 @@ public function prepareDataSource(array $dataSource) ) ], 'post' => true, - '__disableTmpl' => true ]; } } diff --git a/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php b/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php index e5739317bca8d..16caf346c808c 100644 --- a/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php +++ b/app/code/Magento/Customer/Ui/Component/MassAction/Group/Options.php @@ -10,7 +10,9 @@ use Magento\Customer\Model\ResourceModel\Group\CollectionFactory; /** - * Class Options + * Class Options for Mass Action Group + * + * Disable template needed for customers */ class Options implements \JsonSerializable { diff --git a/app/code/Magento/Customer/etc/db_schema.xml b/app/code/Magento/Customer/etc/db_schema.xml index e07d7d8708a43..9f6d75f8ff64f 100644 --- a/app/code/Magento/Customer/etc/db_schema.xml +++ b/app/code/Magento/Customer/etc/db_schema.xml @@ -457,6 +457,9 @@ <constraint xsi:type="foreign" referenceId="CUSTOMER_EAV_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID" table="customer_eav_attribute" column="attribute_id" referenceTable="eav_attribute" referenceColumn="attribute_id" onDelete="CASCADE"/> + <index referenceId="CUSTOMER_EAV_ATTRIBUTE_SORT_ORDER" indexType="btree"> + <column name="sort_order"/> + </index> </table> <table name="customer_form_attribute" resource="default" engine="innodb" comment="Customer Form Attribute"> <column xsi:type="varchar" name="form_code" nullable="false" length="32" comment="Form Code"/> diff --git a/app/code/Magento/Customer/etc/db_schema_whitelist.json b/app/code/Magento/Customer/etc/db_schema_whitelist.json index ec7a53945aba3..1e04a75ab300b 100644 --- a/app/code/Magento/Customer/etc/db_schema_whitelist.json +++ b/app/code/Magento/Customer/etc/db_schema_whitelist.json @@ -284,6 +284,9 @@ "is_filterable_in_grid": true, "is_searchable_in_grid": true }, + "index": { + "CUSTOMER_EAV_ATTRIBUTE_SORT_ORDER": true + }, "constraint": { "PRIMARY": true, "CUSTOMER_EAV_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true @@ -347,4 +350,4 @@ "CUSTOMER_LOG_CUSTOMER_ID": true } } -} \ No newline at end of file +} diff --git a/app/code/Magento/Customer/etc/frontend/di.xml b/app/code/Magento/Customer/etc/frontend/di.xml index 8867042cc6dc9..2a6e36a1ea3d7 100644 --- a/app/code/Magento/Customer/etc/frontend/di.xml +++ b/app/code/Magento/Customer/etc/frontend/di.xml @@ -92,6 +92,12 @@ <item name="template" xsi:type="string">Magento_Customer::messages/confirmAccountSuccessMessage.phtml</item> </item> </item> + <item name="confirmAccountErrorMessage" xsi:type="array"> + <item name="renderer" xsi:type="const">\Magento\Framework\View\Element\Message\Renderer\BlockRenderer::CODE</item> + <item name="data" xsi:type="array"> + <item name="template" xsi:type="string">Magento_Customer::messages/confirmAccountErrorMessage.phtml</item> + </item> + </item> <item name="customerVatShippingAddressSuccessMessage" xsi:type="array"> <item name="renderer" xsi:type="const">\Magento\Framework\View\Element\Message\Renderer\BlockRenderer::CODE</item> <item name="data" xsi:type="array"> diff --git a/app/code/Magento/Customer/view/frontend/email/change_email.html b/app/code/Magento/Customer/view/frontend/email/change_email.html index 4853adf638066..bd961ad99ec40 100644 --- a/app/code/Magento/Customer/view/frontend/email/change_email.html +++ b/app/code/Magento/Customer/view/frontend/email/change_email.html @@ -8,13 +8,12 @@ <!--@vars { "var store.frontend_name":"Store Name", "var store_email":"Store Email", -"var store_phone":"Store Phone" +"var store_phone":"Store Phone", +"var customer.name":"Customer Name" } @--> {{template config_path="design/email/header_template"}} -<p class="greeting">{{trans "Hello,"}}</p> -<br> - +<p class="greeting">{{trans "%name," name=$customer.name}}</p> <p> {{trans "We have received a request to change the following information associated with your account at %store_name: email." store_name=$store.frontend_name}} {{trans 'If you have not authorized this action, please contact us immediately at <a href="mailto:%store_email">%store_email</a>' store_email=$store_email |raw}}{{depend store_phone}} {{trans 'or call us at <a href="tel:%store_phone">%store_phone</a>' store_phone=$store_phone |raw}}{{/depend}}. diff --git a/app/code/Magento/Customer/view/frontend/email/change_email_and_password.html b/app/code/Magento/Customer/view/frontend/email/change_email_and_password.html index 49867bdedc9e0..4f5c85b2381f3 100644 --- a/app/code/Magento/Customer/view/frontend/email/change_email_and_password.html +++ b/app/code/Magento/Customer/view/frontend/email/change_email_and_password.html @@ -8,13 +8,12 @@ <!--@vars { "var store.frontend_name":"Store Name", "var store_email":"Store Email", -"var store_phone":"Store Phone" +"var store_phone":"Store Phone", +"var customer.name":"Customer Name" } @--> {{template config_path="design/email/header_template"}} -<p class="greeting">{{trans "Hello,"}}</p> -<br> - +<p class="greeting">{{trans "%name," name=$customer.name}}</p> <p> {{trans "We have received a request to change the following information associated with your account at %store_name: email, password." store_name=$store.frontend_name}} {{trans 'If you have not authorized this action, please contact us immediately at <a href="mailto:%store_email">%store_email</a>' store_email=$store_email |raw}}{{depend store_phone}} {{trans 'or call us at <a href="tel:%store_phone">%store_phone</a>' store_phone=$store_phone |raw}}{{/depend}}. diff --git a/app/code/Magento/Customer/view/frontend/email/password_reset.html b/app/code/Magento/Customer/view/frontend/email/password_reset.html index 79015117c2280..cab05a89227b6 100644 --- a/app/code/Magento/Customer/view/frontend/email/password_reset.html +++ b/app/code/Magento/Customer/view/frontend/email/password_reset.html @@ -9,13 +9,12 @@ "var customer.name":"Customer Name", "var store.frontend_name":"Store Name", "var store_email":"Store Email", -"var store_phone":"Store Phone" +"var store_phone":"Store Phone", +"var customer.name":"Customer Name" } @--> {{template config_path="design/email/header_template"}} -<p class="greeting">{{trans "Hello,"}}</p> -<br> - +<p class="greeting">{{trans "%name," name=$customer.name}}</p> <p> {{trans "We have received a request to change the following information associated with your account at %store_name: password." store_name=$store.frontend_name}} {{trans 'If you have not authorized this action, please contact us immediately at <a href="mailto:%store_email">%store_email</a>' store_email=$store_email |raw}}{{depend store_phone}} {{trans 'or call us at <a href="tel:%store_phone">%store_phone</a>' store_phone=$store_phone |raw}}{{/depend}}. diff --git a/app/code/Magento/Customer/view/frontend/templates/messages/confirmAccountErrorMessage.phtml b/app/code/Magento/Customer/view/frontend/templates/messages/confirmAccountErrorMessage.phtml new file mode 100644 index 0000000000000..134ff3f142cfb --- /dev/null +++ b/app/code/Magento/Customer/view/frontend/templates/messages/confirmAccountErrorMessage.phtml @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Framework\View\Element\Template $block */ +/** @var \Magento\Framework\Escaper $escaper */ + +?> +<?= $escaper->escapeHtml( + __( + 'This account is not confirmed. <a href="%1">Click here</a> to resend confirmation email.', + $block->getData('url') + ), + ['a'] +); diff --git a/app/code/Magento/Directory/Helper/Data.php b/app/code/Magento/Directory/Helper/Data.php index 3a5558e6d6f3d..3133e3d4c1957 100644 --- a/app/code/Magento/Directory/Helper/Data.php +++ b/app/code/Magento/Directory/Helper/Data.php @@ -6,11 +6,13 @@ namespace Magento\Directory\Helper; +use Magento\Directory\Model\AllowedCountries; use Magento\Directory\Model\Currency; use Magento\Directory\Model\CurrencyFactory; use Magento\Directory\Model\ResourceModel\Country\Collection; use Magento\Directory\Model\ResourceModel\Region\CollectionFactory; use Magento\Framework\App\Cache\Type\Config; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Helper\Context; use Magento\Framework\Json\Helper\Data as JsonData; use Magento\Store\Model\ScopeInterface; @@ -21,6 +23,7 @@ * * @api * @since 100.0.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Data extends \Magento\Framework\App\Helper\AbstractHelper { @@ -156,6 +159,7 @@ public function getRegionCollection() { if (!$this->_regionCollection) { $this->_regionCollection = $this->_regCollectionFactory->create(); + // phpstan:ignore $this->_regionCollection->addCountryFilter($this->getAddress()->getCountryId())->load(); } return $this->_regionCollection; @@ -185,7 +189,9 @@ public function getRegionJson() { \Magento\Framework\Profiler::start('TEST: ' . __METHOD__, ['group' => 'TEST', 'method' => __METHOD__]); if (!$this->_regionJson) { - $cacheKey = 'DIRECTORY_REGIONS_JSON_STORE' . $this->_storeManager->getStore()->getId(); + $scope = $this->getCurrentScope(); + $scopeKey = $scope['value'] ? '_' . implode('_', $scope) : null; + $cacheKey = 'DIRECTORY_REGIONS_JSON_STORE' . $scopeKey; $json = $this->_configCacheType->load($cacheKey); if (empty($json)) { $regions = $this->getRegionData(); @@ -344,10 +350,13 @@ public function getDefaultCountry($store = null) */ public function getRegionData() { - $countryIds = []; - foreach ($this->getCountryCollection() as $country) { - $countryIds[] = $country->getCountryId(); - } + $scope = $this->getCurrentScope(); + $allowedCountries = $this->scopeConfig->getValue( + AllowedCountries::ALLOWED_COUNTRIES_PATH, + $scope['type'], + $scope['value'] + ); + $countryIds = explode(',', $allowedCountries); $collection = $this->_regCollectionFactory->create(); $collection->addCountryFilter($countryIds)->load(); $regions = [ @@ -392,4 +401,31 @@ public function getWeightUnit() { return $this->scopeConfig->getValue(self::XML_PATH_WEIGHT_UNIT, ScopeInterface::SCOPE_STORE); } + + /** + * Get current scope from request + * + * @return array + */ + private function getCurrentScope(): array + { + $scope = [ + 'type' => ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + 'value' => null, + ]; + $request = $this->_getRequest(); + if ($request->getParam(ScopeInterface::SCOPE_WEBSITE)) { + $scope = [ + 'type' => ScopeInterface::SCOPE_WEBSITE, + 'value' => $request->getParam(ScopeInterface::SCOPE_WEBSITE), + ]; + } elseif ($request->getParam(ScopeInterface::SCOPE_STORE)) { + $scope = [ + 'type' => ScopeInterface::SCOPE_STORE, + 'value' => $request->getParam(ScopeInterface::SCOPE_STORE), + ]; + } + + return $scope; + } } diff --git a/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php b/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php index 6ff0f8ea0f30b..f5cc60b5dfb99 100644 --- a/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php @@ -6,59 +6,79 @@ namespace Magento\Directory\Test\Unit\Helper; use Magento\Directory\Helper\Data; +use Magento\Directory\Model\AllowedCountries; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Directory\Model\ResourceModel\Country\Collection as CountryCollection; +use Magento\Directory\Model\ResourceModel\Region\Collection as RegionCollection; +use Magento\Directory\Model\ResourceModel\Region\CollectionFactory; +use Magento\Framework\App\Cache\Type\Config; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Helper\Context; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Json\Helper\Data as JsonDataHelper; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\Constraint\IsIdentical; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class DataTest extends \PHPUnit\Framework\TestCase +class DataTest extends TestCase { /** - * @var \Magento\Directory\Model\ResourceModel\Country\Collection|\PHPUnit_Framework_MockObject_MockObject + * @var CountryCollection|MockObject */ protected $_countryCollection; /** - * @var \Magento\Directory\Model\ResourceModel\Region\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CollectionFactory|MockObject */ protected $_regionCollection; /** - * @var \Magento\Framework\Json\Helper\Data|\PHPUnit_Framework_MockObject_MockObject + * @var JsonDataHelper|MockObject */ protected $jsonHelperMock; /** - * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject + * @var Store|MockObject */ protected $_store; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ScopeConfigInterface|MockObject */ protected $scopeConfigMock; /** - * @var \Magento\Directory\Helper\Data + * @var Data */ protected $_object; protected function setUp() { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $objectManager = new ObjectManager($this); + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); $this->scopeConfigMock->expects($this->any())->method('isSetFlag')->willReturn(false); - $context = $this->createMock(\Magento\Framework\App\Helper\Context::class); + $requestMock = $this->createMock(RequestInterface::class); + $context = $this->createMock(Context::class); + $context->method('getRequest') + ->willReturn($requestMock); $context->expects($this->any()) ->method('getScopeConfig') ->willReturn($this->scopeConfigMock); + $configCacheType = $this->createMock(Config::class); - $configCacheType = $this->createMock(\Magento\Framework\App\Cache\Type\Config::class); + $this->_countryCollection = $this->createMock(CountryCollection::class); - $this->_countryCollection = $this->createMock(\Magento\Directory\Model\ResourceModel\Country\Collection::class); - - $this->_regionCollection = $this->createMock(\Magento\Directory\Model\ResourceModel\Region\Collection::class); + $this->_regionCollection = $this->createMock(RegionCollection::class); $regCollectionFactory = $this->createPartialMock( - \Magento\Directory\Model\ResourceModel\Region\CollectionFactory::class, + CollectionFactory::class, ['create'] ); $regCollectionFactory->expects( @@ -69,13 +89,13 @@ protected function setUp() $this->returnValue($this->_regionCollection) ); - $this->jsonHelperMock = $this->createMock(\Magento\Framework\Json\Helper\Data::class); + $this->jsonHelperMock = $this->createMock(JsonDataHelper::class); - $this->_store = $this->createMock(\Magento\Store\Model\Store::class); - $storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->_store = $this->createMock(Store::class); + $storeManager = $this->createMock(StoreManagerInterface::class); $storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->_store)); - $currencyFactory = $this->createMock(\Magento\Directory\Model\CurrencyFactory::class); + $currencyFactory = $this->createMock(CurrencyFactory::class); $arguments = [ 'context' => $context, @@ -86,32 +106,31 @@ protected function setUp() 'storeManager' => $storeManager, 'currencyFactory' => $currencyFactory, ]; - $this->_object = $objectManager->getObject(\Magento\Directory\Helper\Data::class, $arguments); + $this->_object = $objectManager->getObject(Data::class, $arguments); } public function testGetRegionJson() { - $countries = [ - new \Magento\Framework\DataObject(['country_id' => 'Country1']), - new \Magento\Framework\DataObject(['country_id' => 'Country2']) - ]; - $countryIterator = new \ArrayIterator($countries); - $this->_countryCollection->expects( - $this->atLeastOnce() - )->method( - 'getIterator' - )->will( - $this->returnValue($countryIterator) - ); - + $this->scopeConfigMock->method('getValue') + ->willReturnMap( + [ + [ + AllowedCountries::ALLOWED_COUNTRIES_PATH, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + null, + 'Country1,Country2' + ], + [Data::XML_PATH_STATES_REQUIRED, ScopeInterface::SCOPE_STORE, null, ''] + ] + ); $regions = [ - new \Magento\Framework\DataObject( + new DataObject( ['country_id' => 'Country1', 'region_id' => 'r1', 'code' => 'r1-code', 'name' => 'r1-name'] ), - new \Magento\Framework\DataObject( + new DataObject( ['country_id' => 'Country1', 'region_id' => 'r2', 'code' => 'r2-code', 'name' => 'r2-name'] ), - new \Magento\Framework\DataObject( + new DataObject( ['country_id' => 'Country2', 'region_id' => 'r3', 'code' => 'r3-code', 'name' => 'r3-name'] ) ]; @@ -148,7 +167,7 @@ public function testGetRegionJson() )->method( 'jsonEncode' )->with( - new \PHPUnit\Framework\Constraint\IsIdentical($expectedDataToEncode) + new IsIdentical($expectedDataToEncode) )->will( $this->returnValue('encoded_json') ); @@ -220,7 +239,7 @@ public function testGetDefaultCountry() ->method('getValue') ->with( Data::XML_PATH_DEFAULT_COUNTRY, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $storeId )->will($this->returnValue($country)); @@ -237,7 +256,7 @@ public function testGetCountryCollection() $this->returnValue(0) ); - $store = $this->createMock(\Magento\Store\Model\Store::class); + $store = $this->createMock(Store::class); $this->_countryCollection->expects( $this->once() )->method( @@ -257,7 +276,7 @@ public function testGetCountryCollection() public function testGetTopCountryCodesReturnsParsedConfigurationValue($topCountriesValue, $expectedResult) { $this->scopeConfigMock->expects($this->once()) - ->method('getValue')->with(\Magento\Directory\Helper\Data::XML_PATH_TOP_COUNTRIES) + ->method('getValue')->with(Data::XML_PATH_TOP_COUNTRIES) ->willReturn($topCountriesValue); $this->assertEquals($expectedResult, $this->_object->getTopCountryCodes()); diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml deleted file mode 100644 index ddc11fa6420ec..0000000000000 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml +++ /dev/null @@ -1,58 +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="AdminAddDefaultVideoDownloadableProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> - <annotations> - <features value="Downloadable"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to add default video for a Downloadable Product"/> - <description value="Admin should be able to add default video for a Downloadable Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-114"/> - <group value="Downloadable"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add static.magento.com" before="enableAdminAccountSharing"/> - </before> - <after> - <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove static.magento.com" before="setStoreDefaultConfig"/> - </after> - <!-- Create a downloadable product --> - <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="DownloadableProduct"/> - </actionGroup> - <actionGroup ref="FillMainProductFormNoWeightActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="DownloadableProduct"/> - </actionGroup> - - <!-- Add downloadable links --> - <scrollTo selector="{{AdminProductDownloadableSection.sectionHeader}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> - <conditionalClick selector="{{AdminProductDownloadableSection.sectionHeader}}" dependentSelector="{{AdminProductDownloadableSection.isDownloadableProduct}}" visible="false" stepKey="openDownloadableSection" after="scrollToSection"/> - <checkOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> - <fillField userInput="{{downloadableData.link_title}}" selector="{{AdminProductDownloadableSection.linksTitleInput}}" stepKey="fillLinkTitle" after="checkOptionIsDownloadable"/> - <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately" after="fillLinkTitle"/> - <fillField userInput="{{downloadableData.sample_title}}" selector="{{AdminProductDownloadableSection.samplesTitleInput}}" stepKey="fillSampleTitle" after="checkOptionPurchaseSeparately"/> - <actionGroup ref="AddDownloadableProductLinkWithMaxDownloadsActionGroup" stepKey="addDownloadableProductLinkWithMaxDownloads" after="fillSampleTitle"> - <argument name="link" value="downloadableLinkWithMaxDownloads"/> - </actionGroup> - <actionGroup ref="AddDownloadableProductLinkActionGroup" stepKey="addDownloadableProductLink" before="saveProductForm"> - <argument name="link" value="downloadableLink"/> - </actionGroup> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="DownloadableProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml index 9ae046210181b..317f2abdf2f23 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml @@ -97,8 +97,7 @@ </actionGroup> <!-- Assert product price in cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="openShoppingCartPage"/> - <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openShoppingCartPage"/> <see selector="{{CheckoutCartProductSection.ProductPriceByName(DownloadableProduct.name)}}" userInput="$51.99" stepKey="assertProductPriceInCart"/> </test> </tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml deleted file mode 100644 index bd2e7615ac252..0000000000000 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml +++ /dev/null @@ -1,59 +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="AdminRemoveDefaultVideoDownloadableProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> - <annotations> - <features value="Downloadable"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to remove default video from a Downloadable Product"/> - <description value="Admin should be able to remove default video from a Downloadable Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-207"/> - <group value="Downloadable"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add static.magento.com" before="enableAdminAccountSharing"/> - </before> - <after> - <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove static.magento.com" before="setStoreDefaultConfig"/> - </after> - - <!-- Create a downloadable product --> - <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="DownloadableProduct"/> - </actionGroup> - <actionGroup ref="FillMainProductFormNoWeightActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="DownloadableProduct"/> - </actionGroup> - - <!-- Add downloadable links --> - <scrollTo selector="{{AdminProductDownloadableSection.sectionHeader}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> - <conditionalClick selector="{{AdminProductDownloadableSection.sectionHeader}}" dependentSelector="{{AdminProductDownloadableSection.isDownloadableProduct}}" visible="false" stepKey="openDownloadableSection" after="scrollToSection"/> - <checkOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> - <fillField userInput="{{downloadableData.link_title}}" selector="{{AdminProductDownloadableSection.linksTitleInput}}" stepKey="fillLinkTitle" after="checkOptionIsDownloadable"/> - <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately" after="fillLinkTitle"/> - <fillField userInput="{{downloadableData.sample_title}}" selector="{{AdminProductDownloadableSection.samplesTitleInput}}" stepKey="fillSampleTitle" after="checkOptionPurchaseSeparately"/> - <actionGroup ref="AddDownloadableProductLinkWithMaxDownloadsActionGroup" stepKey="addDownloadableProductLinkWithMaxDownloads" after="fillSampleTitle"> - <argument name="link" value="downloadableLinkWithMaxDownloads"/> - </actionGroup> - <actionGroup ref="AddDownloadableProductLinkActionGroup" stepKey="addDownloadableProductLink" before="saveProductForm"> - <argument name="link" value="downloadableLink"/> - </actionGroup> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="DownloadableProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml index 30e31be6c8ec4..0ac2dc9b04825 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml @@ -95,8 +95,7 @@ </actionGroup> <!-- Step 4: Open cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="openShoppingCartPage"/> - <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openShoppingCartPage"/> <see selector="{{CheckoutCartProductSection.ProductPriceByName(DownloadableProduct.name)}}" userInput="$51.99" stepKey="assertProductPriceInCart"/> diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/DownloadablePanel.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/DownloadablePanel.php index a34ef79d70d30..813fcaf863412 100644 --- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/DownloadablePanel.php +++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/DownloadablePanel.php @@ -45,7 +45,7 @@ public function __construct(LocatorInterface $locator, ArrayManager $arrayManage } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -58,7 +58,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -97,7 +97,8 @@ protected function addMessageBox() 'visible' => false, 'imports' => [ 'visible' => '${$.provider}:' . self::DATA_SCOPE_PRODUCT . '.' - . ProductAttributeInterface::CODE_HAS_WEIGHT + . ProductAttributeInterface::CODE_HAS_WEIGHT, + '__disableTmpl' => ['visible' => false], ], ]; @@ -122,7 +123,8 @@ protected function addCheckboxIsDownloadable() 'sortOrder' => 10, 'imports' => [ 'disabled' => '${$.provider}:' . self::DATA_SCOPE_PRODUCT . '.' - . ProductAttributeInterface::CODE_HAS_WEIGHT + . ProductAttributeInterface::CODE_HAS_WEIGHT, + '__disableTmpl' => ['disabled' => false], ], 'valueMap' => [ 'false' => '0', @@ -130,6 +132,7 @@ protected function addCheckboxIsDownloadable() ], 'samplesFieldset' => 'ns = ${ $.ns }, index=' . Composite::CONTAINER_SAMPLES, 'linksFieldset' => 'ns = ${ $.ns }, index=' . Composite::CONTAINER_LINKS, + '__disableTmpl' => ['samplesFieldset' => false, 'linksFieldset' => false], ]; $this->meta = $this->arrayManager->set($checkboxPath, $this->meta, $checkboxConfig); diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php index 8c98d871a12d2..d4c81763c7d11 100644 --- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php +++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php @@ -287,7 +287,8 @@ protected function getPriceColumn() 'imports' => [ 'linksPurchasedSeparately' => '${$.provider}:data.product' . '.links_purchased_separately', - 'useDefaultPrice' => '${$.parentName}.use_default_price:checked' + 'useDefaultPrice' => '${$.parentName}.use_default_price:checked', + '__disableTmpl' => ['linksPurchasedSeparately' => false, 'useDefaultPrice' => false], ], ]; @@ -485,6 +486,7 @@ protected function getMaxDownloadsColumn() ], 'exports' => [ 'checked' => '${$.parentName}.number_of_downloads:disabled', + '__disableTmpl' => ['checked' => false], ], ]; diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/UsedDefault.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/UsedDefault.php index d0e24ce7c4aca..b00dfef81a705 100644 --- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/UsedDefault.php +++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/UsedDefault.php @@ -11,6 +11,9 @@ use Magento\Framework\Stdlib\ArrayManager; use Magento\Ui\Component\Form; +/** + * Class for Product Form Modifier User Default + */ class UsedDefault extends AbstractModifier { /** @@ -49,7 +52,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -57,7 +60,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -124,6 +127,7 @@ protected function priceUsedDefault() ], 'imports' => [ 'linksPurchasedSeparately' => '${$.provider}:data.product.links_purchased_separately', + '__disableTmpl' => ['linksPurchasedSeparately' => false], ], ]; $this->meta = $this->arrayManager->set($checkboxPath, $this->meta, $useDefaultConfig); @@ -159,6 +163,7 @@ protected function titleUsedDefaultInGrid($indexTitle) ], 'exports' => [ 'checked' => '${$.parentName}.' . $indexTitle . ':disabled', + '__disableTmpl' => ['checked' => false], ], ]; $this->meta = $this->arrayManager->set($checkboxPath, $this->meta, $useDefaultConfig); diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 9e4ad6fb53a33..c8780341271ac 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -6,14 +6,21 @@ namespace Magento\Eav\Model\ResourceModel\Entity; +use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Config; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Eav\Model\Entity\Attribute as EntityAttribute; +use Magento\Eav\Model\Entity\Attribute\FrontendLabel; +use Magento\Eav\Model\Entity\Attribute\Source\Table; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject; use Magento\Framework\DB\Select; use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Context; +use Magento\Store\Model\StoreManagerInterface; /** * EAV attribute resource model @@ -32,7 +39,7 @@ class Attribute extends AbstractDb protected static $_entityAttributes = []; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $_storeManager; @@ -49,15 +56,15 @@ class Attribute extends AbstractDb /** * Class constructor * - * @param \Magento\Framework\Model\ResourceModel\Db\Context $context - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param Context $context + * @param StoreManagerInterface $storeManager * @param Type $eavEntityType * @param string $connectionName * @codeCoverageIgnore */ public function __construct( - \Magento\Framework\Model\ResourceModel\Db\Context $context, - \Magento\Store\Model\StoreManagerInterface $storeManager, + Context $context, + StoreManagerInterface $storeManager, Type $eavEntityType, $connectionName = null ) { @@ -94,14 +101,14 @@ protected function _initUniqueFields() /** * Load attribute data by attribute code * - * @param EntityAttribute|\Magento\Framework\Model\AbstractModel $object + * @param EntityAttribute|AbstractModel $object * @param int $entityTypeId * @param string $code * @return bool */ public function loadByCode(AbstractModel $object, $entityTypeId, $code) { - $bind = [':entity_type_id' => $entityTypeId]; + $bind = [':entity_type_id' => (int) $entityTypeId]; $select = $this->_getLoadSelect('attribute_code', $code, $object)->where('entity_type_id = :entity_type_id'); $data = $this->getConnection()->fetchRow($select, $bind); @@ -145,10 +152,10 @@ private function _getMaxSortOrder(AbstractModel $object) /** * Delete entity * - * @param \Magento\Framework\Model\AbstractMode $object + * @param AbstractModel $object * @return $this */ - public function deleteEntity(\Magento\Framework\Model\AbstractModel $object) + public function deleteEntity(AbstractModel $object) { if (!$object->getEntityAttributeId()) { return $this; @@ -167,7 +174,7 @@ public function deleteEntity(\Magento\Framework\Model\AbstractModel $object) * * @param EntityAttribute|AbstractModel $object * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ protected function _beforeSave(AbstractModel $object) { @@ -184,7 +191,7 @@ protected function _beforeSave(AbstractModel $object) */ if (!$object->getId()) { if ($object->getFrontendInput() == 'select') { - $object->setSourceModel(\Magento\Eav\Model\Entity\Attribute\Source\Table::class); + $object->setSourceModel(Table::class); } } @@ -200,7 +207,7 @@ protected function _beforeSave(AbstractModel $object) */ protected function _beforeDelete(AbstractModel $attribute) { - /** @var $attribute \Magento\Eav\Api\Data\AttributeInterface */ + /** @var $attribute AttributeInterface */ if ($attribute->getId() && !$attribute->getIsUserDefined()) { throw new CouldNotDeleteException(__("The system attribute can't be deleted.")); } @@ -232,12 +239,12 @@ protected function _afterSave(AbstractModel $object) /** * Perform actions after object delete * - * @param \Magento\Framework\Model\AbstractModel|\Magento\Framework\DataObject $object + * @param AbstractModel|DataObject $object * @return $this * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @since 100.0.7 */ - protected function _afterDelete(\Magento\Framework\Model\AbstractModel $object) + protected function _afterDelete(AbstractModel $object) { $this->getConfig()->clear(); return $this; @@ -260,7 +267,7 @@ private function getConfig() /** * Save store labels * - * @param EntityAttribute|\Magento\Framework\Model\AbstractModel $object + * @param EntityAttribute|AbstractModel $object * @return $this */ protected function _saveStoreLabels(AbstractModel $object) @@ -287,7 +294,7 @@ protected function _saveStoreLabels(AbstractModel $object) /** * Save additional data of attribute * - * @param EntityAttribute|\Magento\Framework\Model\AbstractModel $object + * @param EntityAttribute|AbstractModel $object * @return $this */ protected function _saveAdditionalAttributeData(AbstractModel $object) @@ -296,7 +303,7 @@ protected function _saveAdditionalAttributeData(AbstractModel $object) if ($additionalTable) { $connection = $this->getConnection(); $data = $this->_prepareDataForTable($object, $this->getTable($additionalTable)); - $bind = [':attribute_id' => $object->getId()]; + $bind = [':attribute_id' => (int) $object->getId()]; $select = $connection->select()->from( $this->getTable($additionalTable), ['attribute_id'] @@ -410,12 +417,12 @@ protected function _processAttributeOptions($object, $option) * * @param array $values * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ protected function _checkDefaultOptionValue($values) { if (!isset($values[0])) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __("The default option isn't defined. Set the option and try again.") ); } @@ -797,8 +804,8 @@ public function __sleep() public function __wakeup() { parent::__wakeup(); - $this->_storeManager = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Store\Model\StoreManagerInterface::class); + $this->_storeManager = ObjectManager::getInstance() + ->get(StoreManagerInterface::class); } /** @@ -807,14 +814,14 @@ public function __wakeup() * @param AbstractModel $object * @param string|null $frontendLabel * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ private function setStoreLabels(AbstractModel $object, $frontendLabel) { $resultLabel = []; $frontendLabels = $object->getFrontendLabels(); if (isset($frontendLabels[0]) - && $frontendLabels[0] instanceof \Magento\Eav\Model\Entity\Attribute\FrontendLabel + && $frontendLabels[0] instanceof FrontendLabel ) { foreach ($frontendLabels as $label) { $resultLabel[$label->getStoreId()] = $label->getLabel(); @@ -830,13 +837,13 @@ private function setStoreLabels(AbstractModel $object, $frontendLabel) * @param array|string|null $frontendLabel * @param array $resultLabels * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ private function checkDefaultFrontendLabelExists($frontendLabel, $resultLabels) { $isAdminStoreLabel = (isset($resultLabels[0]) && !empty($resultLabels[0])); if (empty($frontendLabel) && !$isAdminStoreLabel) { - throw new \Magento\Framework\Exception\LocalizedException(__('The storefront label is not defined.')); + throw new LocalizedException(__('The storefront label is not defined.')); } } } diff --git a/app/code/Magento/Eav/etc/db_schema.xml b/app/code/Magento/Eav/etc/db_schema.xml index 8dc917b22d09f..0e0b599290ca2 100644 --- a/app/code/Magento/Eav/etc/db_schema.xml +++ b/app/code/Magento/Eav/etc/db_schema.xml @@ -305,6 +305,11 @@ <column name="entity_type_id"/> <column name="attribute_code"/> </constraint> + <index referenceId="EAV_ATTRIBUTE_FRONTEND_INPUT_ENTITY_TYPE_ID_IS_USER_DEFINED" indexType="btree"> + <column name="frontend_input"/> + <column name="entity_type_id"/> + <column name="is_user_defined"/> + </index> </table> <table name="eav_entity_store" resource="default" engine="innodb" comment="Eav Entity Store"> <column xsi:type="int" name="entity_store_id" padding="10" unsigned="true" nullable="false" identity="true" diff --git a/app/code/Magento/Eav/etc/db_schema_whitelist.json b/app/code/Magento/Eav/etc/db_schema_whitelist.json index fbcba0741eadf..756c584d0e4d7 100644 --- a/app/code/Magento/Eav/etc/db_schema_whitelist.json +++ b/app/code/Magento/Eav/etc/db_schema_whitelist.json @@ -177,6 +177,9 @@ "is_unique": true, "note": true }, + "index": { + "EAV_ATTRIBUTE_FRONTEND_INPUT_ENTITY_TYPE_ID_IS_USER_DEFINED": true + }, "constraint": { "PRIMARY": true, "EAV_ATTRIBUTE_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml index 03999a70322b1..e8a0df9b9dc87 100644 --- a/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml +++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml @@ -30,7 +30,7 @@ <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="categoryFirst" stepKey="deleteCategory"/> <comment userInput="The test was moved to elasticsearch suite" stepKey="resetCatalogSearchConfiguration"/> - <actionGroup ref="updateIndexerOnSave" stepKey="resetIndexerBackToOriginalState"> + <actionGroup ref="AdminIndexerSetUpdateOnSaveActionGroup" stepKey="resetIndexerBackToOriginalState"> <argument name="indexerName" value="catalogsearch_fulltext"/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> diff --git a/app/code/Magento/Email/Model/AbstractTemplate.php b/app/code/Magento/Email/Model/AbstractTemplate.php index 3d4fc252b57ff..c697734b9df0f 100644 --- a/app/code/Magento/Email/Model/AbstractTemplate.php +++ b/app/code/Magento/Email/Model/AbstractTemplate.php @@ -546,7 +546,9 @@ protected function applyDesignConfig() protected function cancelDesignConfig() { $this->appEmulation->stopEnvironmentEmulation(); + $this->urlModel->setScope(null); $this->hasDesignBeenApplied = false; + return $this; } diff --git a/app/code/Magento/Email/etc/di.xml b/app/code/Magento/Email/etc/di.xml index 73ba21ed7537b..5f6e30d591b79 100644 --- a/app/code/Magento/Email/etc/di.xml +++ b/app/code/Magento/Email/etc/di.xml @@ -18,7 +18,7 @@ </type> <type name="Magento\Email\Model\Template"> <arguments> - <argument name="urlModel" xsi:type="object" shared="false">Magento\Framework\Url</argument> + <argument name="urlModel" xsi:type="object">Magento\Framework\Url</argument> </arguments> </type> <type name="Magento\Theme\Model\Design\Config\MetadataProvider"> diff --git a/app/code/Magento/Fedex/Model/Carrier.php b/app/code/Magento/Fedex/Model/Carrier.php index 0cbd90150734b..e19b632fc268c 100644 --- a/app/code/Magento/Fedex/Model/Carrier.php +++ b/app/code/Magento/Fedex/Model/Carrier.php @@ -8,6 +8,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Module\Dir; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Webapi\Soap\ClientFactory; @@ -21,6 +22,7 @@ * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) */ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\Carrier\CarrierInterface { @@ -149,6 +151,16 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C */ private $soapClientFactory; + /** + * @var array + */ + private $baseCurrencyRate; + + /** + * @var DataObject + */ + private $_rawTrackingRequest; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory @@ -629,12 +641,13 @@ protected function _prepareRateResponse($response) protected function _getRateAmountOriginBased($rate) { $amount = null; + $currencyCode = ''; $rateTypeAmounts = []; - if (is_object($rate)) { // The "RATED..." rates are expressed in the currency of the origin country foreach ($rate->RatedShipmentDetails as $ratedShipmentDetail) { $netAmount = (string)$ratedShipmentDetail->ShipmentRateDetail->TotalNetCharge->Amount; + $currencyCode = (string)$ratedShipmentDetail->ShipmentRateDetail->TotalNetCharge->Currency; $rateType = (string)$ratedShipmentDetail->ShipmentRateDetail->RateType; $rateTypeAmounts[$rateType] = $netAmount; } @@ -649,11 +662,42 @@ protected function _getRateAmountOriginBased($rate) if ($amount === null) { $amount = (string)$rate->RatedShipmentDetails[0]->ShipmentRateDetail->TotalNetCharge->Amount; } + + $amount = (float)$amount * $this->getBaseCurrencyRate($currencyCode); } return $amount; } + /** + * Returns base currency rate. + * + * @param string $currencyCode + * @return float + * @throws LocalizedException + */ + private function getBaseCurrencyRate(string $currencyCode): float + { + if (!isset($this->baseCurrencyRate[$currencyCode])) { + $baseCurrencyCode = $this->_request->getBaseCurrency()->getCode(); + $rate = $this->_currencyFactory->create() + ->load($currencyCode) + ->getAnyRate($baseCurrencyCode); + if ($rate === false) { + $errorMessage = __( + 'Can\'t convert a shipping cost from "%1-%2" for FedEx carrier.', + $currencyCode, + $baseCurrencyCode + ); + $this->_logger->critical($errorMessage); + throw new LocalizedException($errorMessage); + } + $this->baseCurrencyRate[$currencyCode] = (float)$rate; + } + + return $this->baseCurrencyRate[$currencyCode]; + } + /** * Set free method request * @@ -726,9 +770,8 @@ protected function _getXmlQuotes() $debugData = ['request' => $this->filterDebugData($request)]; try { $url = $this->getConfigData('gateway_url'); - if (!$url) { - $url = $this->_defaultGatewayUrl; - } + + // phpcs:disable Magento2.Functions.DiscouragedFunction $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); @@ -737,6 +780,7 @@ protected function _getXmlQuotes() curl_setopt($ch, CURLOPT_POSTFIELDS, $request); $responseBody = curl_exec($ch); curl_close($ch); + // phpcs:enable $debugData['result'] = $this->filterDebugData($responseBody); $this->_setCachedQuotes($request, $responseBody); diff --git a/app/code/Magento/Fedex/Test/Unit/Model/CarrierTest.php b/app/code/Magento/Fedex/Test/Unit/Model/CarrierTest.php index 65aae284ea444..a5d72d1e3ad4c 100644 --- a/app/code/Magento/Fedex/Test/Unit/Model/CarrierTest.php +++ b/app/code/Magento/Fedex/Test/Unit/Model/CarrierTest.php @@ -10,10 +10,12 @@ use Magento\Directory\Helper\Data; use Magento\Directory\Model\Country; use Magento\Directory\Model\CountryFactory; +use Magento\Directory\Model\Currency; use Magento\Directory\Model\CurrencyFactory; use Magento\Directory\Model\RegionFactory; use Magento\Fedex\Model\Carrier; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Module\Dir\Reader; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Serialize\Serializer\Json; @@ -35,7 +37,7 @@ use Magento\Shipping\Model\Tracking\ResultFactory; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; -use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\MockObject\MockObject as MockObject; use Psr\Log\LoggerInterface; /** @@ -100,6 +102,11 @@ class CarrierTest extends \PHPUnit\Framework\TestCase */ private $logger; + /** + * @var CurrencyFactory|MockObject + */ + private $currencyFactory; + protected function setUp() { $this->helper = new ObjectManager($this); @@ -141,7 +148,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $currencyFactory = $this->getMockBuilder(CurrencyFactory::class) + $this->currencyFactory = $this->getMockBuilder(CurrencyFactory::class) ->disableOriginalConstructor() ->getMock(); @@ -179,7 +186,7 @@ protected function setUp() 'trackStatusFactory' => $this->statusFactory, 'regionFactory' => $regionFactory, 'countryFactory' => $countryFactory, - 'currencyFactory' => $currencyFactory, + 'currencyFactory' => $this->currencyFactory, 'directoryData' => $data, 'stockRegistry' => $stockRegistry, 'storeManager' => $storeManager, @@ -240,13 +247,21 @@ public function scopeConfigGetValue(string $path) /** * @param float $amount + * @param string $currencyCode + * @param string $baseCurrencyCode * @param string $rateType * @param float $expected * @param int $callNum * @dataProvider collectRatesDataProvider */ - public function testCollectRatesRateAmountOriginBased($amount, $rateType, $expected, $callNum = 1) - { + public function testCollectRatesRateAmountOriginBased( + $amount, + $currencyCode, + $baseCurrencyCode, + $rateType, + $expected, + $callNum = 1 + ) { $this->scope->expects($this->any()) ->method('isSetFlag') ->willReturn(true); @@ -254,6 +269,7 @@ public function testCollectRatesRateAmountOriginBased($amount, $rateType, $expec // @codingStandardsIgnoreStart $netAmount = new \stdClass(); $netAmount->Amount = $amount; + $netAmount->Currency = $currencyCode; $totalNetCharge = new \stdClass(); $totalNetCharge->TotalNetCharge = $netAmount; @@ -274,9 +290,39 @@ public function testCollectRatesRateAmountOriginBased($amount, $rateType, $expec $this->serializer->method('serialize') ->willReturn('CollectRateString' . $amount); + $rateCurrency = $this->getMockBuilder(Currency::class) + ->disableOriginalConstructor() + ->getMock(); + $rateCurrency->method('load') + ->willReturnSelf(); + $rateCurrency->method('getAnyRate') + ->willReturnMap( + [ + ['USD', 1], + ['EUR', 0.75], + ['UNKNOWN', false] + ] + ); + + if ($baseCurrencyCode === 'UNKNOWN') { + $this->expectException(LocalizedException::class); + } + + $this->currencyFactory->method('create') + ->willReturn($rateCurrency); + + $baseCurrency = $this->getMockBuilder(Currency::class) + ->disableOriginalConstructor() + ->getMock(); + $baseCurrency->method('getCode') + ->willReturn($baseCurrencyCode); + $request = $this->getMockBuilder(RateRequest::class) + ->setMethods(['getBaseCurrency']) ->disableOriginalConstructor() ->getMock(); + $request->method('getBaseCurrency') + ->willReturn($baseCurrency); $this->soapClient->expects($this->exactly($callNum)) ->method('getRates') @@ -295,22 +341,23 @@ public function testCollectRatesRateAmountOriginBased($amount, $rateType, $expec public function collectRatesDataProvider() { return [ - [10.0, 'RATED_ACCOUNT_PACKAGE', 10], - [10.0, 'RATED_ACCOUNT_PACKAGE', 10, 0], - [11.50, 'PAYOR_ACCOUNT_PACKAGE', 11.5], - [11.50, 'PAYOR_ACCOUNT_PACKAGE', 11.5, 0], - [100.01, 'RATED_ACCOUNT_SHIPMENT', 100.01], - [100.01, 'RATED_ACCOUNT_SHIPMENT', 100.01, 0], - [32.2, 'PAYOR_ACCOUNT_SHIPMENT', 32.2], - [32.2, 'PAYOR_ACCOUNT_SHIPMENT', 32.2, 0], - [15.0, 'RATED_LIST_PACKAGE', 15], - [15.0, 'RATED_LIST_PACKAGE', 15, 0], - [123.25, 'PAYOR_LIST_PACKAGE', 123.25], - [123.25, 'PAYOR_LIST_PACKAGE', 123.25, 0], - [12.12, 'RATED_LIST_SHIPMENT', 12.12], - [12.12, 'RATED_LIST_SHIPMENT', 12.12, 0], - [38.9, 'PAYOR_LIST_SHIPMENT', 38.9], - [38.9, 'PAYOR_LIST_SHIPMENT', 38.9, 0], + [10.0, 'USD', 'EUR', 'RATED_ACCOUNT_PACKAGE', 7.5], + [10.0, 'USD', 'UNKNOWN', 'RATED_ACCOUNT_PACKAGE', null, 0], + [10.0, 'USD', 'USD', 'RATED_ACCOUNT_PACKAGE', 10, 0], + [11.50, 'USD', 'USD', 'PAYOR_ACCOUNT_PACKAGE', 11.5], + [11.50, 'USD', 'USD', 'PAYOR_ACCOUNT_PACKAGE', 11.5, 0], + [100.01, 'USD', 'USD', 'RATED_ACCOUNT_SHIPMENT', 100.01], + [100.01, 'USD', 'USD', 'RATED_ACCOUNT_SHIPMENT', 100.01, 0], + [32.2, 'USD', 'USD', 'PAYOR_ACCOUNT_SHIPMENT', 32.2], + [32.2, 'USD', 'USD', 'PAYOR_ACCOUNT_SHIPMENT', 32.2, 0], + [15.0, 'USD', 'USD', 'RATED_LIST_PACKAGE', 15], + [15.0, 'USD', 'USD', 'RATED_LIST_PACKAGE', 15, 0], + [123.25, 'USD', 'USD', 'PAYOR_LIST_PACKAGE', 123.25], + [123.25, 'USD', 'USD', 'PAYOR_LIST_PACKAGE', 123.25, 0], + [12.12, 'USD', 'USD', 'RATED_LIST_SHIPMENT', 12.12], + [12.12, 'USD', 'USD', 'RATED_LIST_SHIPMENT', 12.12, 0], + [38.9, 'USD', 'USD', 'PAYOR_LIST_SHIPMENT', 38.9], + [38.9, 'USD', 'USD', 'PAYOR_LIST_SHIPMENT', 38.9, 0], ]; } diff --git a/app/code/Magento/Fedex/i18n/en_US.csv b/app/code/Magento/Fedex/i18n/en_US.csv index 778c324c8dfd3..d1509d42730bc 100644 --- a/app/code/Magento/Fedex/i18n/en_US.csv +++ b/app/code/Magento/Fedex/i18n/en_US.csv @@ -77,3 +77,4 @@ Dropoff,Dropoff Debug,Debug "Show Method if Not Applicable","Show Method if Not Applicable" "Sort Order","Sort Order" +"Can't convert a shipping cost from ""%1-%2"" for FedEx carrier.","Can't convert a shipping cost from ""%1-%2"" for FedEx carrier." diff --git a/app/code/Magento/GiftMessage/Ui/DataProvider/Product/Modifier/GiftMessage.php b/app/code/Magento/GiftMessage/Ui/DataProvider/Product/Modifier/GiftMessage.php index fe2479d778992..63bf6705854f3 100644 --- a/app/code/Magento/GiftMessage/Ui/DataProvider/Product/Modifier/GiftMessage.php +++ b/app/code/Magento/GiftMessage/Ui/DataProvider/Product/Modifier/GiftMessage.php @@ -161,10 +161,12 @@ protected function customizeAllowGiftMessageField(array $meta) 'exports' => [ 'checked' => '${$.parentName}.' . static::FIELD_MESSAGE_AVAILABLE . ':isUseConfig', + '__disableTmpl' => ['checked' => false], ], 'imports' => [ 'disabled' => '${$.parentName}.' . static::FIELD_MESSAGE_AVAILABLE . ':isUseDefault', + '__disableTmpl' => ['disabled' => false], ] ], ], diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml deleted file mode 100644 index 5eee2d77befab..0000000000000 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml +++ /dev/null @@ -1,62 +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="AdminAddDefaultVideoGroupedProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> - <annotations> - <features value="GroupedProduct"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to add default video for a Grouped Product"/> - <description value="Admin should be able to add default video for a Grouped Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-108"/> - <group value="GroupedProduct"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> - <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> - </before> - <after> - <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> - </after> - - <!-- Create a grouped product --> - <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="GroupedProduct"/> - </actionGroup> - <actionGroup ref="FillGroupedProductFormActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="GroupedProduct"/> - </actionGroup> - - <!-- Add two simple products to grouped product --> - <scrollTo selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> - <conditionalClick selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" dependentSelector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" visible="false" stepKey="openGroupedProductSection" after="scrollToSection"/> - <click selector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" stepKey="clickAddProductsToGroup" after="openGroupedProductSection"/> - <waitForElementVisible selector="{{AdminAddProductsToGroupPanel.filters}}" stepKey="waitForFilter" after="clickAddProductsToGroup"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku1" after="waitForFilter"> - <argument name="product" value="$$simpleProduct1$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku2" after="checkOption1"> - <argument name="product" value="$$simpleProduct2$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> - <click selector="{{AdminAddProductsToGroupPanel.addSelectedProducts}}" stepKey="addSelectedProducts" before="saveProductForm"/> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="GroupedProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml deleted file mode 100644 index 4486bc66ffb98..0000000000000 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml +++ /dev/null @@ -1,62 +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="AdminRemoveDefaultVideoGroupedProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> - <annotations> - <features value="GroupedProduct"/> - <stories value="Add/remove images and videos for all product types and category"/> - <title value="Admin should be able to remove default video from a Grouped Product"/> - <description value="Admin should be able to remove default video from a Grouped Product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-203"/> - <group value="GroupedProduct"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> - <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> - </before> - <after> - <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> - </after> - - <!-- Create a grouped product --> - <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProductPage"> - <argument name="product" value="GroupedProduct"/> - </actionGroup> - <actionGroup ref="FillGroupedProductFormActionGroup" stepKey="fillMainProductForm"> - <argument name="product" value="GroupedProduct"/> - </actionGroup> - - <!-- Add two simple products to grouped product --> - <scrollTo selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> - <conditionalClick selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" dependentSelector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" visible="false" stepKey="openGroupedProductSection" after="scrollToSection"/> - <click selector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" stepKey="clickAddProductsToGroup" after="openGroupedProductSection"/> - <waitForElementVisible selector="{{AdminAddProductsToGroupPanel.filters}}" stepKey="waitForFilter" after="clickAddProductsToGroup"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku1" after="waitForFilter"> - <argument name="product" value="$$simpleProduct1$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku2" after="checkOption1"> - <argument name="product" value="$$simpleProduct2$$"/> - </actionGroup> - <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> - <click selector="{{AdminAddProductsToGroupPanel.addSelectedProducts}}" stepKey="addSelectedProducts" before="saveProductForm"/> - - <!-- Assert product in storefront product page --> - <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageActionGroup" stepKey="AssertProductInStorefrontProductPage"> - <argument name="product" value="GroupedProduct"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php index 2ea622c1c2b8f..3ea8c6eb3c2b9 100644 --- a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php +++ b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php @@ -394,9 +394,11 @@ protected function getListing() 'externalFilterMode' => true, 'imports' => [ 'storeId' => '${ $.provider }:data.product.current_store_id', + '__disableTmpl' => ['storeId' => false], ], 'exports' => [ 'storeId' => '${ $.externalProvider }:params.current_store_id', + '__disableTmpl' => ['storeId' => false], ], ], ], @@ -499,7 +501,10 @@ protected function getGrid() 'attribute_set' => 'attribute_set_text', 'thumbnail' => 'thumbnail_src', ], - 'links' => ['insertData' => '${ $.provider }:${ $.dataProvider }'], + 'links' => [ + 'insertData' => '${ $.provider }:${ $.dataProvider }', + '__disableTmpl' => ['insertData' => false], + ], 'sortOrder' => 20, 'columnsHeader' => false, 'columnsHeaderAfterRender' => true, diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index c7207c853b95e..d760303cbd65f 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -132,11 +132,11 @@ protected function getIndexers(InputInterface $input) $dependentIndexers[] = $this->getDependentIndexerIds($indexer->getId()); } - $relatedIndexers = array_merge(...$relatedIndexers); - $dependentIndexers = array_merge(...$dependentIndexers); + $relatedIndexers = $relatedIndexers ? array_unique(array_merge(...$relatedIndexers)) : []; + $dependentIndexers = $dependentIndexers ? array_merge(...$dependentIndexers) : []; $invalidRelatedIndexers = []; - foreach (array_unique($relatedIndexers) as $relatedIndexer) { + foreach ($relatedIndexers as $relatedIndexer) { if ($allIndexers[$relatedIndexer]->isInvalid()) { $invalidRelatedIndexers[] = $relatedIndexer; } @@ -169,8 +169,9 @@ private function getRelatedIndexerIds(string $indexerId): array $relatedIndexerIds[] = [$relatedIndexerId]; $relatedIndexerIds[] = $this->getRelatedIndexerIds($relatedIndexerId); } + $relatedIndexerIds = $relatedIndexerIds ? array_unique(array_merge(...$relatedIndexerIds)) : []; - return array_unique(array_merge(...$relatedIndexerIds)); + return $relatedIndexerIds; } /** @@ -189,8 +190,9 @@ private function getDependentIndexerIds(string $indexerId): array $dependentIndexerIds[] = $this->getDependentIndexerIds($id); } } + $dependentIndexerIds = $dependentIndexerIds ? array_unique(array_merge(...$dependentIndexerIds)) : []; - return array_unique(array_merge(...$dependentIndexerIds)); + return $dependentIndexerIds; } /** @@ -251,6 +253,8 @@ private function validateSharedIndex($sharedIndex) $indexer = $this->getIndexerRegistry()->get($indexerId); /** @var \Magento\Indexer\Model\Indexer\State $state */ $state = $indexer->getState(); + $state->setStatus(StateInterface::STATUS_WORKING); + $state->save(); $state->setStatus(StateInterface::STATUS_VALID); $state->save(); } diff --git a/app/code/Magento/Indexer/Model/Processor.php b/app/code/Magento/Indexer/Model/Processor.php index ee82c8282f0a9..29a9f3a1f416f 100644 --- a/app/code/Magento/Indexer/Model/Processor.php +++ b/app/code/Magento/Indexer/Model/Processor.php @@ -10,6 +10,9 @@ use Magento\Framework\Indexer\IndexerInterfaceFactory; use Magento\Framework\Indexer\StateInterface; +/** + * Indexer processor + */ class Processor { /** @@ -70,6 +73,8 @@ public function reindexAllInvalid() } else { /** @var \Magento\Indexer\Model\Indexer\State $state */ $state = $indexer->getState(); + $state->setStatus(StateInterface::STATUS_WORKING); + $state->save(); $state->setStatus(StateInterface::STATUS_VALID); $state->save(); } diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/AdminIndexerSetUpdateOnSaveActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/AdminIndexerSetUpdateOnSaveActionGroup.xml new file mode 100644 index 0000000000000..bc6bf62172612 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/AdminIndexerSetUpdateOnSaveActionGroup.xml @@ -0,0 +1,26 @@ +<?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="AdminIndexerSetUpdateOnSaveActionGroup"> + <annotations> + <description>Goes to the Index Management page. Checks the provided Indexer Name. Selects 'Update on Save'. Clicks on Submit.</description> + </annotations> + <arguments> + <argument name="indexerName" type="string"/> + </arguments> + + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/indexer/indexer/list/" stepKey="amOnIndexManagementPage2"/> + <waitForPageLoad stepKey="waitForIndexManagementPageToLoad2"/> + <click selector="{{AdminIndexManagementSection.indexerCheckbox(indexerName)}}" stepKey="selectIndexer2"/> + <selectOption selector="{{AdminIndexManagementSection.massActionSelect}}" userInput="change_mode_onthefly" stepKey="selectUpdateOnSave"/> + <click selector="{{AdminIndexManagementSection.massActionSubmit}}" stepKey="submitIndexerForm2"/> + <!-- No re-indexing is done as part of this actionGroup since the test required no re-indexing --> + <waitForPageLoad stepKey="waitForSave2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/AdminReindexAndFlushCacheActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/AdminReindexAndFlushCacheActionGroup.xml deleted file mode 100644 index 42b6b047ae73e..0000000000000 --- a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/AdminReindexAndFlushCacheActionGroup.xml +++ /dev/null @@ -1,18 +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="AdminReindexAndFlushCache"> - <annotations> - <description>Run reindex and flush cache.</description> - </annotations> - - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerByScheduleActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerByScheduleActionGroup.xml index 580f288aff10d..697ca8e610d9e 100644 --- a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerByScheduleActionGroup.xml +++ b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerByScheduleActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="updateIndexerBySchedule"> + <actionGroup name="UpdateIndexerBySchedule"> <annotations> <description>Goes to the Index Management page. Checks the provided Indexer Name. Selects 'Update by Schedule'. Clicks on Submit.</description> </annotations> diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerOnSaveActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerOnSaveActionGroup.xml index 53064e33a5657..efa6291d5de63 100644 --- a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerOnSaveActionGroup.xml +++ b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/IndexerActionGroup/UpdateIndexerOnSaveActionGroup.xml @@ -7,20 +7,5 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="updateIndexerOnSave"> - <annotations> - <description>Goes to the Index Management page. Checks the provided Indexer Name. Selects 'Update on Save'. Clicks on Submit.</description> - </annotations> - <arguments> - <argument name="indexerName" type="string"/> - </arguments> - - <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/indexer/indexer/list/" stepKey="amOnIndexManagementPage2"/> - <waitForPageLoad stepKey="waitForIndexManagementPageToLoad2"/> - <click selector="{{AdminIndexManagementSection.indexerCheckbox(indexerName)}}" stepKey="selectIndexer2"/> - <selectOption selector="{{AdminIndexManagementSection.massActionSelect}}" userInput="change_mode_onthefly" stepKey="selectUpdateOnSave"/> - <click selector="{{AdminIndexManagementSection.massActionSubmit}}" stepKey="submitIndexerForm2"/> - <!-- No re-indexing is done as part of this actionGroup since the test required no re-indexing --> - <waitForPageLoad stepKey="waitForSave2"/> - </actionGroup> + <actionGroup name="updateIndexerOnSave" extends="AdminIndexerSetUpdateOnSaveActionGroup" deprecated="Use AdminIndexerSetUpdateOnSaveActionGroup"/> </actionGroups> diff --git a/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml index c60f976538028..84619a5213128 100644 --- a/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml +++ b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml @@ -13,6 +13,7 @@ <stories value="Menu Navigation"/> <title value="Admin system index management grid change test"/> <description value="Verify changes in 'Schedule column' on system index management"/> + <severity value="CRITICAL"/> </annotations> <before> <createData entity="_defaultCategory" stepKey="createCategory"/> diff --git a/app/code/Magento/LoginAsCustomer/LICENSE.txt b/app/code/Magento/LoginAsCustomer/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/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/LoginAsCustomer/LICENSE_AFL.txt b/app/code/Magento/LoginAsCustomer/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/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/LoginAsCustomer/Model/AuthenticateCustomer.php b/app/code/Magento/LoginAsCustomer/Model/AuthenticateCustomer.php new file mode 100644 index 0000000000000..df51a1e43f525 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/Model/AuthenticateCustomer.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomer\Model; + +use Magento\Customer\Model\Session; +use Magento\Framework\Exception\LocalizedException; +use Magento\LoginAsCustomerApi\Api\AuthenticateCustomerInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; + +/** + * @inheritdoc + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class AuthenticateCustomer implements AuthenticateCustomerInterface +{ + /** + * @var Session + */ + private $customerSession; + + /** + * @param Session $customerSession + */ + public function __construct( + Session $customerSession + ) { + $this->customerSession = $customerSession; + } + + /** + * @inheritdoc + */ + public function execute(AuthenticationDataInterface $authenticationData): void + { + if ($this->customerSession->getId()) { + $this->customerSession->logout(); + } + + $result = $this->customerSession->loginById($authenticationData->getCustomerId()); + if (false === $result) { + throw new LocalizedException(__('Login was not successful.')); + } + + $this->customerSession->regenerateId(); + $this->customerSession->setLoggedAsCustomerAdmindId($authenticationData->getAdminId()); + } +} diff --git a/app/code/Magento/LoginAsCustomer/Model/AuthenticationData.php b/app/code/Magento/LoginAsCustomer/Model/AuthenticationData.php new file mode 100644 index 0000000000000..17d85b970a2ff --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/Model/AuthenticationData.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomer\Model; + +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataExtensionInterface; + +/** + * @inheritdoc + */ +class AuthenticationData implements AuthenticationDataInterface +{ + /** + * @var int + */ + private $customerId; + + /** + * @var int + */ + private $adminId; + + /** + * @var AuthenticationDataExtensionInterface|null + */ + private $extensionAttributes; + + /** + * @param int $customerId + * @param int $adminId + * @param AuthenticationDataExtensionInterface|null $extensionAttributes + */ + public function __construct( + int $customerId, + int $adminId, + AuthenticationDataExtensionInterface $extensionAttributes = null + ) { + $this->customerId = $customerId; + $this->adminId = $adminId; + $this->extensionAttributes = $extensionAttributes; + } + + /** + * @inheritdoc + */ + public function getCustomerId(): int + { + return $this->customerId; + } + + /** + * @inheritdoc + */ + public function getAdminId(): int + { + return $this->adminId; + } + + /** + * @inheritdoc + */ + public function getExtensionAttributes(): ?AuthenticationDataExtensionInterface + { + return $this->extensionAttributes; + } +} diff --git a/app/code/Magento/LoginAsCustomer/Model/Config.php b/app/code/Magento/LoginAsCustomer/Model/Config.php new file mode 100644 index 0000000000000..2cfafa6ac09a3 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/Model/Config.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomer\Model; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; + +/** + * @inheritdoc + */ +class Config implements ConfigInterface +{ + /** + * Extension config path + */ + private const XML_PATH_ENABLED + = 'login_as_customer/general/enabled'; + private const XML_PATH_STORE_VIEW_MANUAL_CHOICE_ENABLED + = 'login_as_customer/general/store_view_manual_choice_enabled'; + private const XML_PATH_AUTHENTICATION_EXPIRATION_TIME + = 'login_as_customer/general/authentication_data_expiration_time'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + ScopeConfigInterface $scopeConfig + ) { + $this->scopeConfig = $scopeConfig; + } + + /** + * @inheritdoc + */ + public function isEnabled(): bool + { + return (bool)$this->scopeConfig->getValue(self::XML_PATH_ENABLED); + } + + /** + * @inheritdoc + */ + public function isStoreManualChoiceEnabled(): bool + { + return (bool)$this->scopeConfig->getValue(self::XML_PATH_STORE_VIEW_MANUAL_CHOICE_ENABLED); + } + + /** + * @inheritdoc + */ + public function getAuthenticationDataExpirationTime(): int + { + return (int)$this->scopeConfig->getValue(self::XML_PATH_AUTHENTICATION_EXPIRATION_TIME); + } +} diff --git a/app/code/Magento/LoginAsCustomer/Model/ResourceModel/DeleteExpiredAuthenticationData.php b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/DeleteExpiredAuthenticationData.php new file mode 100644 index 0000000000000..8778c20ab9257 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/DeleteExpiredAuthenticationData.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomer\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; +use Magento\LoginAsCustomerApi\Api\DeleteExpiredAuthenticationDataInterface; + +/** + * @inheritdoc + */ +class DeleteExpiredAuthenticationData implements DeleteExpiredAuthenticationDataInterface +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var DateTime + */ + private $dateTime; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * @param ResourceConnection $resourceConnection + * @param DateTime $dateTime + * @param ConfigInterface $config + */ + public function __construct( + ResourceConnection $resourceConnection, + DateTime $dateTime, + ConfigInterface $config + ) { + $this->resourceConnection = $resourceConnection; + $this->dateTime = $dateTime; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function execute(int $userId): void + { + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName('login_as_customer'); + + $connection->delete( + $tableName, + [ + 'admin_id = ?' => $userId + ] + ); + } +} diff --git a/app/code/Magento/LoginAsCustomer/Model/ResourceModel/GetAuthenticationDataBySecret.php b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/GetAuthenticationDataBySecret.php new file mode 100644 index 0000000000000..8951ae8f70939 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/GetAuthenticationDataBySecret.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomer\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Framework\Exception\LocalizedException; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterfaceFactory; +use Magento\LoginAsCustomerApi\Api\GetAuthenticationDataBySecretInterface; + +/** + * @inheritdoc + */ +class GetAuthenticationDataBySecret implements GetAuthenticationDataBySecretInterface +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var DateTime + */ + private $dateTime; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var AuthenticationDataInterfaceFactory + */ + private $authenticationDataFactory; + + /** + * @param ResourceConnection $resourceConnection + * @param DateTime $dateTime + * @param ConfigInterface $config + * @param AuthenticationDataInterfaceFactory $authenticationDataFactory + */ + public function __construct( + ResourceConnection $resourceConnection, + DateTime $dateTime, + ConfigInterface $config, + AuthenticationDataInterfaceFactory $authenticationDataFactory + ) { + $this->resourceConnection = $resourceConnection; + $this->dateTime = $dateTime; + $this->config = $config; + $this->authenticationDataFactory = $authenticationDataFactory; + } + + /** + * @inheritdoc + */ + public function execute(string $secretKey): AuthenticationDataInterface + { + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName('login_as_customer'); + + $timePoint = date( + 'Y-m-d H:i:s', + $this->dateTime->gmtTimestamp() - $this->config->getAuthenticationDataExpirationTime() + ); + + $select = $connection->select() + ->from(['main_table' => $tableName]) + ->where('main_table.secret = ?', $secretKey) + ->where('main_table.created_at > ?', $timePoint); + + $data = $connection->fetchRow($select); + + if (!$data) { + throw new LocalizedException(__('Secret key is not found or was expired.')); + } + + /** @var AuthenticationDataInterface $authenticationData */ + $authenticationData = $this->authenticationDataFactory->create( + [ + 'customerId' => (int)$data['customer_id'], + 'adminId' => (int)$data['admin_id'], + 'extensionAttributes' => null, + ] + ); + return $authenticationData; + } +} diff --git a/app/code/Magento/LoginAsCustomer/Model/ResourceModel/IsLoginAsCustomerSessionActive.php b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/IsLoginAsCustomerSessionActive.php new file mode 100644 index 0000000000000..3bbba4b5e6ec4 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/IsLoginAsCustomerSessionActive.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomer\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\LoginAsCustomerApi\Api\IsLoginAsCustomerSessionActiveInterface; + +/** + * @inheritdoc + */ +class IsLoginAsCustomerSessionActive implements IsLoginAsCustomerSessionActiveInterface +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param ResourceConnection $resourceConnection + */ + public function __construct( + ResourceConnection $resourceConnection + ) { + $this->resourceConnection = $resourceConnection; + } + + /** + * @inheritdoc + */ + public function execute(int $customerId, int $userId): bool + { + $tableName = $this->resourceConnection->getTableName('login_as_customer'); + $connection = $this->resourceConnection->getConnection(); + + $query = $connection->select() + ->from($tableName) + ->where('customer_id = ?', $customerId) + ->where('admin_id = ?', $userId); + + $result = $connection->fetchRow($query); + + return false !== $result; + } +} diff --git a/app/code/Magento/LoginAsCustomer/Model/ResourceModel/SaveAuthenticationData.php b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/SaveAuthenticationData.php new file mode 100644 index 0000000000000..d120b0eae392e --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/Model/ResourceModel/SaveAuthenticationData.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomer\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Framework\Math\Random; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; +use Magento\LoginAsCustomerApi\Api\SaveAuthenticationDataInterface; + +/** + * @inheritdoc + */ +class SaveAuthenticationData implements SaveAuthenticationDataInterface +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var DateTime + */ + private $dateTime; + + /** + * @var Random + */ + private $random; + + /** + * @param ResourceConnection $resourceConnection + * @param DateTime $dateTime + * @param Random $random + */ + public function __construct( + ResourceConnection $resourceConnection, + DateTime $dateTime, + Random $random + ) { + $this->resourceConnection = $resourceConnection; + $this->dateTime = $dateTime; + $this->random = $random; + } + + /** + * @inheritdoc + */ + public function execute(AuthenticationDataInterface $authenticationData): string + { + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName('login_as_customer'); + + $secret = $this->random->getRandomString(64); + + $connection->insert( + $tableName, + [ + 'customer_id' => $authenticationData->getCustomerId(), + 'admin_id' => $authenticationData->getAdminId(), + 'secret' => $secret, + 'created_at' => $this->dateTime->gmtDate(), + ] + ); + return $secret; + } +} diff --git a/app/code/Magento/LoginAsCustomer/README.md b/app/code/Magento/LoginAsCustomer/README.md new file mode 100644 index 0000000000000..17d37bf553278 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/README.md @@ -0,0 +1,3 @@ +# Magento_LoginAsCustomer module + +The Magento_LoginAsCustomer module is responsible for ability to login into customer account using the admin panel. diff --git a/app/code/Magento/LoginAsCustomer/composer.json b/app/code/Magento/LoginAsCustomer/composer.json new file mode 100755 index 0000000000000..eeef8604feff2 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/composer.json @@ -0,0 +1,21 @@ +{ + "name": "magento/module-login-as-customer", + "description": "Allow for admin to enter a customer account", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-customer": "*", + "magento/module-login-as-customer-api": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ "registration.php" ], + "psr-4": { + "Magento\\LoginAsCustomer\\": "" + } + } +} diff --git a/app/code/Magento/LoginAsCustomer/etc/config.xml b/app/code/Magento/LoginAsCustomer/etc/config.xml new file mode 100644 index 0000000000000..936ae1ff2f05d --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/etc/config.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:module:Magento_Store:etc/config.xsd"> + <default> + <login_as_customer> + <general> + <enabled>0</enabled> + <store_view_manual_choice_enabled>0</store_view_manual_choice_enabled> + <authentication_data_expiration_time>60</authentication_data_expiration_time> + </general> + </login_as_customer> + </default> +</config> diff --git a/app/code/Magento/LoginAsCustomer/etc/db_schema.xml b/app/code/Magento/LoginAsCustomer/etc/db_schema.xml new file mode 100644 index 0000000000000..a693946f0c3ba --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/etc/db_schema.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="login_as_customer" resource="default" engine="innodb" comment="Magento Login As Customer Table"> + <column xsi:type="varchar" name="secret" nullable="false" length="64" comment="Login Secret"/> + <column xsi:type="int" name="customer_id" nullable="false" comment="Customer ID"/> + <column xsi:type="int" name="admin_id" nullable="false" comment="Admin ID"/> + <column xsi:type="timestamp" name="created_at" comment="Creation Time"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="secret"/> + </constraint> + <index referenceId="LOGIN_AS_CUSTOMER_CREATED_AT" indexType="btree"> + <column name="created_at"/> + </index> + </table> +</schema> diff --git a/app/code/Magento/LoginAsCustomer/etc/db_schema_whitelist.json b/app/code/Magento/LoginAsCustomer/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..397ea4525aee2 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/etc/db_schema_whitelist.json @@ -0,0 +1,16 @@ +{ + "login_as_customer": { + "column": { + "secret": true, + "customer_id": true, + "admin_id": true, + "created_at": true + }, + "index": { + "LOGIN_AS_CUSTOMER_CREATED_AT": true + }, + "constraint": { + "PRIMARY": true + } + } +} diff --git a/app/code/Magento/LoginAsCustomer/etc/di.xml b/app/code/Magento/LoginAsCustomer/etc/di.xml new file mode 100755 index 0000000000000..76602534d31e8 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/etc/di.xml @@ -0,0 +1,17 @@ +<?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"> + <preference for="Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface" type="Magento\LoginAsCustomer\Model\AuthenticationData"/> + <preference for="Magento\LoginAsCustomerApi\Api\SaveAuthenticationDataInterface" type="Magento\LoginAsCustomer\Model\ResourceModel\SaveAuthenticationData"/> + <preference for="Magento\LoginAsCustomerApi\Api\GetAuthenticationDataBySecretInterface" type="Magento\LoginAsCustomer\Model\ResourceModel\GetAuthenticationDataBySecret"/> + <preference for="Magento\LoginAsCustomerApi\Api\AuthenticateCustomerInterface" type="Magento\LoginAsCustomer\Model\AuthenticateCustomer"/> + <preference for="Magento\LoginAsCustomerApi\Api\DeleteAuthenticationDataBySecretInterface" type="Magento\LoginAsCustomer\Model\ResourceModel\DeleteAuthenticationDataBySecret"/> + <preference for="Magento\LoginAsCustomerApi\Api\DeleteExpiredAuthenticationDataInterface" type="Magento\LoginAsCustomer\Model\ResourceModel\DeleteExpiredAuthenticationData"/> + <preference for="Magento\LoginAsCustomerApi\Api\ConfigInterface" type="Magento\LoginAsCustomer\Model\Config"/> + <preference for="Magento\LoginAsCustomerApi\Api\IsLoginAsCustomerSessionActiveInterface" type="Magento\LoginAsCustomer\Model\ResourceModel\IsLoginAsCustomerSessionActive"/> +</config> diff --git a/app/code/Magento/LoginAsCustomer/etc/module.xml b/app/code/Magento/LoginAsCustomer/etc/module.xml new file mode 100755 index 0000000000000..d577dc0da5e4f --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/etc/module.xml @@ -0,0 +1,11 @@ +<?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_LoginAsCustomer"/> +</config> diff --git a/app/code/Magento/LoginAsCustomer/i18n/en_US.csv b/app/code/Magento/LoginAsCustomer/i18n/en_US.csv new file mode 100644 index 0000000000000..6ce6cfc532221 --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/i18n/en_US.csv @@ -0,0 +1,2 @@ +"Close Session","Close Session" +"You are connected as <strong>%1</strong> on %2","You are connected as <strong>%1</strong> on %2" diff --git a/app/code/Magento/LoginAsCustomer/registration.php b/app/code/Magento/LoginAsCustomer/registration.php new file mode 100755 index 0000000000000..ac43a6d812e9b --- /dev/null +++ b/app/code/Magento/LoginAsCustomer/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_LoginAsCustomer', + __DIR__ +); diff --git a/app/code/Magento/LoginAsCustomerApi/Api/AuthenticateCustomerInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/AuthenticateCustomerInterface.php new file mode 100644 index 0000000000000..81cc11bd738fd --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/AuthenticateCustomerInterface.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api; + +use Magento\Framework\Exception\LocalizedException; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; + +/** + * Authenticate a customer + * + * @api + */ +interface AuthenticateCustomerInterface +{ + /** + * Authenticate a customer + * + * @param AuthenticationDataInterface $authenticationData + * @return void + * @throws LocalizedException + */ + public function execute(AuthenticationDataInterface $authenticationData): void; +} diff --git a/app/code/Magento/LoginAsCustomerApi/Api/ConfigInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/ConfigInterface.php new file mode 100644 index 0000000000000..b3aafa6c8a51b --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/ConfigInterface.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api; + +/** + * LoginAsCustomer config + * + * @api + */ +interface ConfigInterface +{ + /** + * Check if Login As Customer extension is enabled + * + * @return bool + */ + public function isEnabled(): bool; + + /** + * Check if store view manual choice is enabled + * + * @return bool + */ + public function isStoreManualChoiceEnabled(): bool; + + /** + * Get authentication data expiration time (in seconds) + * + * @return int + */ + public function getAuthenticationDataExpirationTime(): int; +} diff --git a/app/code/Magento/LoginAsCustomerApi/Api/Data/AuthenticationDataInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/Data/AuthenticationDataInterface.php new file mode 100644 index 0000000000000..f74f63c39f7ba --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/Data/AuthenticationDataInterface.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Authentication data + * + * @api + */ +interface AuthenticationDataInterface extends ExtensibleDataInterface +{ + /** + * Get Customer Id + * + * @return int + */ + public function getCustomerId(): int; + + /** + * Get Admin Id + * + * @return int + */ + public function getAdminId(): int; + + /** + * Get extension attributes + * + * Fully qualified namespaces is needed for proper work of ccode generation + * + * @return \Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataExtensionInterface|null + */ + public function getExtensionAttributes(): ?AuthenticationDataExtensionInterface; +} diff --git a/app/code/Magento/LoginAsCustomerApi/Api/DeleteAuthenticationDataBySecretInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/DeleteAuthenticationDataBySecretInterface.php new file mode 100644 index 0000000000000..aba63bb9acb20 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/DeleteAuthenticationDataBySecretInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api; + +/** + * Delete authentication data by secret + * + * @api + */ +interface DeleteAuthenticationDataBySecretInterface +{ + /** + * Delete authentication data by secret + * + * @param string $secret + * @return void + */ + public function execute(string $secret): void; +} diff --git a/app/code/Magento/LoginAsCustomerApi/Api/DeleteExpiredAuthenticationDataInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/DeleteExpiredAuthenticationDataInterface.php new file mode 100644 index 0000000000000..3b2e756f40e83 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/DeleteExpiredAuthenticationDataInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api; + +/** + * Delete expired authentication data + * + * @api + */ +interface DeleteExpiredAuthenticationDataInterface +{ + /** + * Delete expired authentication data by user id. + * + * @param int $userId + * @return void + */ + public function execute(int $userId): void; +} diff --git a/app/code/Magento/LoginAsCustomerApi/Api/GetAuthenticationDataBySecretInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/GetAuthenticationDataBySecretInterface.php new file mode 100644 index 0000000000000..2ca8d66453739 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/GetAuthenticationDataBySecretInterface.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api; + +use Magento\Framework\Exception\LocalizedException; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; + +/** + * Get authentication data by secret + * + * @api + */ +interface GetAuthenticationDataBySecretInterface +{ + /** + * Load login details based on secret key + * + * @param string $secretKey + * @return AuthenticationDataInterface + * @throws LocalizedException + */ + public function execute(string $secretKey): AuthenticationDataInterface; +} diff --git a/app/code/Magento/LoginAsCustomerApi/Api/IsLoginAsCustomerSessionActiveInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/IsLoginAsCustomerSessionActiveInterface.php new file mode 100644 index 0000000000000..30674375ed021 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/IsLoginAsCustomerSessionActiveInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api; + +/** + * Check if Login as Customer session is still active. + * + * @api + */ +interface IsLoginAsCustomerSessionActiveInterface +{ + /** + * Check if Login as Customer session is still active. + * + * @param int $customerId + * @param int $userId + * @return bool + */ + public function execute(int $customerId, int $userId): bool; +} diff --git a/app/code/Magento/LoginAsCustomerApi/Api/SaveAuthenticationDataInterface.php b/app/code/Magento/LoginAsCustomerApi/Api/SaveAuthenticationDataInterface.php new file mode 100644 index 0000000000000..88d4cb8056cf6 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/Api/SaveAuthenticationDataInterface.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerApi\Api; + +use Magento\Framework\Exception\LocalizedException; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; + +/** + * Save authentication data. Return secret key + * + * @api + */ +interface SaveAuthenticationDataInterface +{ + /** + * Save authentication data. Return secret key + * + * @param Data\AuthenticationDataInterface $authenticationData + * @return string + * @throws LocalizedException + */ + public function execute(AuthenticationDataInterface $authenticationData): string; +} diff --git a/app/code/Magento/LoginAsCustomerApi/LICENSE.txt b/app/code/Magento/LoginAsCustomerApi/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/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/LoginAsCustomerApi/LICENSE_AFL.txt b/app/code/Magento/LoginAsCustomerApi/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/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/LoginAsCustomerApi/README.md b/app/code/Magento/LoginAsCustomerApi/README.md new file mode 100644 index 0000000000000..caf2f23b49f43 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/README.md @@ -0,0 +1,3 @@ +# Magento_LoginAsCustomer module + +The Magento_LoginAsCustomerApi module provides API for ability to login into customer account for an admin user. diff --git a/app/code/Magento/LoginAsCustomerApi/composer.json b/app/code/Magento/LoginAsCustomerApi/composer.json new file mode 100644 index 0000000000000..c99c117bda7c3 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/composer.json @@ -0,0 +1,19 @@ +{ + "name": "magento/module-login-as-customer-api", + "description": "Allow for admin to enter a customer account", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ "registration.php" ], + "psr-4": { + "Magento\\LoginAsCustomerApi\\": "" + } + } +} diff --git a/app/code/Magento/LoginAsCustomerApi/etc/module.xml b/app/code/Magento/LoginAsCustomerApi/etc/module.xml new file mode 100644 index 0000000000000..cdfefb80e9b3b --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/etc/module.xml @@ -0,0 +1,11 @@ +<?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_LoginAsCustomerApi"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerApi/registration.php b/app/code/Magento/LoginAsCustomerApi/registration.php new file mode 100644 index 0000000000000..344d110e46f43 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerApi/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_LoginAsCustomerApi', + __DIR__ +); diff --git a/app/code/Magento/LoginAsCustomerLog/Api/Data/LogInterface.php b/app/code/Magento/LoginAsCustomerLog/Api/Data/LogInterface.php new file mode 100644 index 0000000000000..10d793d6c4c0b --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Api/Data/LogInterface.php @@ -0,0 +1,130 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Data interface for login as customer log. + * + * @api + */ +interface LogInterface extends ExtensibleDataInterface +{ + const LOG_ID = 'log_id'; + const TIME = 'time'; + const CUSTOMER_ID = 'customer_id'; + const CUSTOMER_EMAIL = 'customer_email'; + const USER_ID = 'user_id'; + const USERNAME = 'user_name'; + + /** + * Set login as customer log id. + * + * @param int $logId + * @return void + */ + public function setLogId(int $logId): void; + + /** + * Retrieve login as customer log id. + * + * @return null|int + */ + public function getLogId(): ?int; + + /** + * Set login as customer log time. + * + * @param string $time + * @return void + */ + public function setTime(string $time): void; + + /** + * Retrieve login as customer log time. + * + * @return null|string + */ + public function getTime(): ?string; + + /** + * Set login as customer log user id. + * + * @param int $userId + * @return void + */ + public function setUserId(int $userId): void; + + /** + * Retrieve login as customer log user id. + * + * @return null|int + */ + public function getUserId(): ?int; + + /** + * Set login as customer log user name. + * + * @param string $userName + * @return void + */ + public function setUserName(string $userName): void; + + /** + * Retrieve login as customer log user name. + * + * @return null|string + */ + public function getUserName(): ?string; + + /** + * Set login as customer log customer id. + * + * @param int $customerId + * @return void + */ + public function setCustomerId(int $customerId): void; + + /** + * Retrieve login as customer log customer id. + * + * @return null|int + */ + public function getCustomerId(): ?int; + + /** + * Set login as customer log customer email. + * + * @param string $customerEmail + * @return void + */ + public function setCustomerEmail(string $customerEmail): void; + + /** + * Retrieve login as customer log customer email. + * + * @return null|string + */ + public function getCustomerEmail(): ?string; + + /** + * Set log extension attributes. + * + * @param \Magento\LoginAsCustomerLog\Api\Data\LogExtensionInterface $extensionAttributes + * @return void + */ + public function setExtensionAttributes(LogExtensionInterface $extensionAttributes): void; + + /** + * Retrieve log extension attributes. + * + * @return \Magento\LoginAsCustomerLog\Api\Data\LogExtensionInterface + */ + public function getExtensionAttributes(): LogExtensionInterface; +} diff --git a/app/code/Magento/LoginAsCustomerLog/Api/Data/LogSearchResultsInterface.php b/app/code/Magento/LoginAsCustomerLog/Api/Data/LogSearchResultsInterface.php new file mode 100644 index 0000000000000..5b08d28af6335 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Api/Data/LogSearchResultsInterface.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Api\Data; + +use \Magento\Framework\Api\SearchResultsInterface; + +/** + * Login as customer log entity search results interface. + * + * @api + */ +interface LogSearchResultsInterface extends SearchResultsInterface +{ + /** + * Get log list. + * + * @return \Magento\LoginAsCustomerLog\Api\Data\LogInterface[] + */ + public function getItems(); + + /** + * Set log list. + * + * @param \Magento\LoginAsCustomerLog\Api\Data\LogInterface[] $items + * @return void + */ + public function setItems(array $items); +} diff --git a/app/code/Magento/LoginAsCustomerLog/Api/GetLogsListInterface.php b/app/code/Magento/LoginAsCustomerLog/Api/GetLogsListInterface.php new file mode 100644 index 0000000000000..4b5ee382c908a --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Api/GetLogsListInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Api; + +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\LoginAsCustomerLog\Api\Data\LogSearchResultsInterface; + +/** + * Get login as customer log list considering search criteria. + * + * @api + */ +interface GetLogsListInterface +{ + /** + * Retrieve list of log entities. + * + * @param SearchCriteriaInterface $searchCriteria + * @return LogSearchResultsInterface + */ + public function execute(SearchCriteriaInterface $searchCriteria): LogSearchResultsInterface; +} diff --git a/app/code/Magento/LoginAsCustomerLog/Api/SaveLogsInterface.php b/app/code/Magento/LoginAsCustomerLog/Api/SaveLogsInterface.php new file mode 100644 index 0000000000000..67e1ece477727 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Api/SaveLogsInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Api; + +/** + * Save login as custom logs entities. + * + * @api + */ +interface SaveLogsInterface +{ + /** + * Save logs. + * + * @param \Magento\LoginAsCustomerLog\Api\Data\LogInterface[] $logs + * @return void + */ + public function execute(array $logs): void; +} diff --git a/app/code/Magento/LoginAsCustomerLog/Controller/Adminhtml/Log/Index.php b/app/code/Magento/LoginAsCustomerLog/Controller/Adminhtml/Log/Index.php new file mode 100644 index 0000000000000..ce0c50bf347fc --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Controller/Adminhtml/Log/Index.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Controller\Adminhtml\Log; + +use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; + +/** + * Login As Customer log grid controller. + */ +class Index extends Action implements HttpGetActionInterface +{ + const ADMIN_RESOURCE = 'Magento_LoginAsCustomerLog::login_log'; + + /** + * @inheritdoc + */ + public function execute(): ResultInterface + { + $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); + $resultPage->setActiveMenu('Magento_LoginAsCustomerLog::login_log') + ->addBreadcrumb(__('Login as Customer Log'), __('List')); + $resultPage->getConfig()->getTitle()->prepend(__('Login as Customer Log')); + + return $resultPage; + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/Model/GetLogList.php b/app/code/Magento/LoginAsCustomerLog/Model/GetLogList.php new file mode 100644 index 0000000000000..f01b9419144a5 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Model/GetLogList.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Model; + +use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\LoginAsCustomerLog\Api\Data\LogSearchResultsInterface; +use Magento\LoginAsCustomerLog\Api\Data\LogSearchResultsInterfaceFactory; +use Magento\LoginAsCustomerLog\Api\GetLogsListInterface; +use Magento\LoginAsCustomerLog\Model\ResourceModel\Log\CollectionFactory; + +/** + * @inheritDoc + */ +class GetLogList implements GetLogsListInterface +{ + /** + * @var CollectionFactory + */ + private $logCollectionFactory; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * @var CollectionProcessorInterface + */ + private $collectionProcessor; + + /** + * @var LogSearchResultsInterfaceFactory + */ + private $logSearchResultsFactory; + + /** + * @param CollectionFactory $logCollectionFactory + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param CollectionProcessorInterface $collectionProcessor + * @param LogSearchResultsInterfaceFactory $logSearchResultsFactory + */ + public function __construct( + CollectionFactory $logCollectionFactory, + SearchCriteriaBuilder $searchCriteriaBuilder, + CollectionProcessorInterface $collectionProcessor, + LogSearchResultsInterfaceFactory $logSearchResultsFactory + ) { + $this->logCollectionFactory = $logCollectionFactory; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->collectionProcessor = $collectionProcessor; + $this->logSearchResultsFactory = $logSearchResultsFactory; + } + + /** + * @inheritDoc + */ + public function execute(SearchCriteriaInterface $searchCriteria): LogSearchResultsInterface + { + $collection = $this->logCollectionFactory->create(); + $searchCriteria = $searchCriteria ?: $this->searchCriteriaBuilder->create(); + $this->collectionProcessor->process($searchCriteria, $collection); + + $searchResult = $this->logSearchResultsFactory->create(); + $searchResult->setItems($collection->getItems()); + $searchResult->setTotalCount($collection->getSize()); + $searchResult->setSearchCriteria($searchCriteria); + + return $searchResult; + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/Model/Log.php b/app/code/Magento/LoginAsCustomerLog/Model/Log.php new file mode 100644 index 0000000000000..cdf6789659b3f --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Model/Log.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Model; + +use Magento\Framework\Model\AbstractExtensibleModel; +use Magento\LoginAsCustomerLog\Api\Data\LogExtensionInterface; +use Magento\LoginAsCustomerLog\Api\Data\LogInterface; + +/** + * @inheritDoc + */ +class Log extends AbstractExtensibleModel implements LogInterface +{ + /** + * @inheritDoc + */ + public function setLogId(int $logId): void + { + $this->setData(LogInterface::LOG_ID, $logId); + } + + /** + * @inheritDoc + */ + public function getLogId(): ?int + { + return $this->getData(LogInterface::LOG_ID) ? (int)$this->getData(LogInterface::LOG_ID) : null; + } + + /** + * @inheritDoc + */ + public function setTime(string $time): void + { + $this->setData(LogInterface::TIME, $time); + } + + /** + * @inheritDoc + */ + public function getTime(): ?string + { + return $this->getData(LogInterface::TIME); + } + + /** + * @inheritDoc + */ + public function setUserId(int $userId): void + { + $this->setData(LogInterface::USER_ID, $userId); + } + + /** + * @inheritDoc + */ + public function getUserId(): ?int + { + return $this->getData(LogInterface::USER_ID) ? (int)$this->getData(LogInterface::USER_ID) : null; + } + + /** + * @inheritDoc + */ + public function setUserName(string $userName): void + { + $this->setData(LogInterface::USERNAME, $userName); + } + + /** + * @inheritDoc + */ + public function getUserName(): ?string + { + return $this->getData(LogInterface::USERNAME); + } + + /** + * @inheritDoc + */ + public function setCustomerId(int $customerId): void + { + $this->setData(LogInterface::CUSTOMER_ID, $customerId); + } + + /** + * @inheritDoc + */ + public function getCustomerId(): ?int + { + return $this->getData(LogInterface::CUSTOMER_ID) ? + (int)$this->getData(LogInterface::CUSTOMER_ID) + : null; + } + + /** + * @inheritDoc + */ + public function setCustomerEmail(string $customerEmail): void + { + $this->setData(LogInterface::CUSTOMER_EMAIL, $customerEmail); + } + + /** + * @inheritDoc + */ + public function getCustomerEmail(): ?string + { + return $this->getData(LogInterface::CUSTOMER_EMAIL); + } + + /** + * @inheritdoc + */ + public function setExtensionAttributes(LogExtensionInterface $extensionAttributes): void + { + $this->_setExtensionAttributes($extensionAttributes); + } + + /** + * @inheritDoc + */ + public function getExtensionAttributes(): LogExtensionInterface + { + return $this->_getExtensionAttributes(); + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/Model/LogSearchResults.php b/app/code/Magento/LoginAsCustomerLog/Model/LogSearchResults.php new file mode 100644 index 0000000000000..2def7b0e09c6c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Model/LogSearchResults.php @@ -0,0 +1,18 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Model; + +use Magento\Framework\Api\SearchResults; +use Magento\LoginAsCustomerLog\Api\Data\LogSearchResultsInterface; + +/** + * @inheritDoc + */ +class LogSearchResults extends SearchResults implements LogSearchResultsInterface +{ +} diff --git a/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/Log.php b/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/Log.php new file mode 100644 index 0000000000000..11c142c078322 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/Log.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Model\ResourceModel; + +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\LoginAsCustomerLog\Api\Data\LogInterface; + +/** + * Login as customer log resource model. + */ +class Log extends AbstractDb +{ + const TABLE_NAME_LOG = 'magento_login_as_customer_log'; + + /** + * @inheritdoc + */ + protected function _construct() + { + $this->_init(self::TABLE_NAME_LOG, LogInterface::LOG_ID); + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/Log/Collection.php b/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/Log/Collection.php new file mode 100644 index 0000000000000..a5df9a0067ad1 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/Log/Collection.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Model\ResourceModel\Log; + +use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; +use Magento\LoginAsCustomerLog\Model\Log; +use Magento\LoginAsCustomerLog\Model\ResourceModel\Log as LogResource; + +/** + * Login as customer log entities collection. + */ +class Collection extends AbstractCollection +{ + /** + * @inheritdoc + */ + protected function _construct() + { + $this->_init(Log::class, LogResource::class); + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/SaveLogs.php b/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/SaveLogs.php new file mode 100644 index 0000000000000..f793f75e678cc --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Model/ResourceModel/SaveLogs.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\LoginAsCustomerLog\Api\SaveLogsInterface; + +/** + * @inheritDoc + */ +class SaveLogs implements SaveLogsInterface +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var DateTime + */ + private $dateTime; + + /** + * @param ResourceConnection $resourceConnection + * @param DateTime $dateTime + */ + public function __construct(ResourceConnection $resourceConnection, DateTime $dateTime) + { + $this->resourceConnection = $resourceConnection; + $this->dateTime = $dateTime; + } + + /** + * @inheritDoc + */ + public function execute(array $logs): void + { + $logsData = []; + foreach ($logs as $log) { + if (!$log->getTime()) { + $log->setTime($this->dateTime->gmtDate()); + } + $logsData[] = $log->getData(); + } + $logTable = $this->resourceConnection->getTableName(Log::TABLE_NAME_LOG); + $connection = $this->resourceConnection->getConnection(); + $connection->insertMultiple($logTable, $logsData); + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/Plugin/LoginAsCustomerApi/Api/AuthenticateCustomerInterface/LogAuthenticationPlugin.php b/app/code/Magento/LoginAsCustomerLog/Plugin/LoginAsCustomerApi/Api/AuthenticateCustomerInterface/LogAuthenticationPlugin.php new file mode 100644 index 0000000000000..c1d999b552821 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Plugin/LoginAsCustomerApi/Api/AuthenticateCustomerInterface/LogAuthenticationPlugin.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Plugin\LoginAsCustomerApi\Api\AuthenticateCustomerInterface; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\LoginAsCustomerApi\Api\AuthenticateCustomerInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; +use Magento\LoginAsCustomerLog\Api\Data\LogInterfaceFactory; +use Magento\LoginAsCustomerLog\Api\SaveLogsInterface; +use Magento\User\Api\Data\UserInterfaceFactory; +use Magento\User\Model\ResourceModel\User; + +/** + * Log user logged in as customer plugin. + */ +class LogAuthenticationPlugin +{ + /** + * @var LogInterfaceFactory + */ + private $logFactory; + + /** + * @var SaveLogsInterface + */ + private $saveLogs; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var UserInterfaceFactory + */ + private $userFactory; + + /** + * @var User + */ + private $userResource; + + /** + * @param LogInterfaceFactory $logFactory + * @param SaveLogsInterface $saveLogs + * @param CustomerRepositoryInterface $customerRepository + * @param UserInterfaceFactory $userFactory + * @param User $userResource + */ + public function __construct( + LogInterfaceFactory $logFactory, + SaveLogsInterface $saveLogs, + CustomerRepositoryInterface $customerRepository, + UserInterfaceFactory $userFactory, + User $userResource + ) { + $this->logFactory = $logFactory; + $this->saveLogs = $saveLogs; + $this->customerRepository = $customerRepository; + $this->userFactory = $userFactory; + $this->userResource = $userResource; + } + + /** + * Log user authentication as customer. + * + * @param AuthenticateCustomerInterface $subject + * @param void $result + * @param AuthenticationDataInterface $data + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute( + AuthenticateCustomerInterface $subject, + $result, + AuthenticationDataInterface $data + ): void { + $customerId = $data->getCustomerId(); + $customerEmail = $this->customerRepository->getById($customerId)->getEmail(); + $userId = $data->getAdminId(); + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + $log = $this->logFactory->create( + [ + 'data' => [ + 'customer_id' => $customerId, + 'user_id' => $userId, + 'customer_email' => $customerEmail, + 'user_name' => $user->getUserName(), + ], + ] + ); + $this->saveLogs->execute([$log]); + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/README.md b/app/code/Magento/LoginAsCustomerLog/README.md new file mode 100644 index 0000000000000..a44ae014f2c83 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/README.md @@ -0,0 +1,3 @@ +# Magento_LoginAsCustomerLog module + +The Magento_LoginAsCustomerLog module provides log for Login As Customer functionality diff --git a/app/code/Magento/LoginAsCustomerLog/Ui/DataProvider/LogDataProvider.php b/app/code/Magento/LoginAsCustomerLog/Ui/DataProvider/LogDataProvider.php new file mode 100644 index 0000000000000..53a76b08432d9 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/Ui/DataProvider/LogDataProvider.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerLog\Ui\DataProvider; + +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\Search\ReportingInterface; +use Magento\Framework\Api\Search\SearchCriteriaBuilder; +use Magento\Framework\Api\SortOrderBuilder; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider; +use Magento\LoginAsCustomerLog\Api\Data\LogInterface; +use Magento\LoginAsCustomerLog\Api\GetLogsListInterface; +use Magento\Ui\DataProvider\SearchResultFactory; + +/** + * @inheritDoc + */ +class LogDataProvider extends DataProvider +{ + /** + * @var GetLogsListInterface + */ + private $getLogsList; + + /** + * @var SearchResultFactory + */ + private $searchResultFactory; + + /** + * @var SortOrderBuilder + */ + private $sortOrderBuilder; + + /** + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param ReportingInterface $reporting + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param RequestInterface $request + * @param FilterBuilder $filterBuilder + * @param GetLogsListInterface $getLogsList + * @param SearchResultFactory $searchResultFactory + * @param SortOrderBuilder $sortOrderBuilder + * @param array $meta + * @param array $data + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + $name, + $primaryFieldName, + $requestFieldName, + ReportingInterface $reporting, + SearchCriteriaBuilder $searchCriteriaBuilder, + RequestInterface $request, + FilterBuilder $filterBuilder, + GetLogsListInterface $getLogsList, + SearchResultFactory $searchResultFactory, + SortOrderBuilder $sortOrderBuilder, + array $meta = [], + array $data = [] + ) { + parent::__construct( + $name, + $primaryFieldName, + $requestFieldName, + $reporting, + $searchCriteriaBuilder, + $request, + $filterBuilder, + $meta, + $data + ); + $this->getLogsList = $getLogsList; + $this->searchResultFactory = $searchResultFactory; + $this->sortOrderBuilder = $sortOrderBuilder; + } + + /** + * @inheritdoc + */ + public function getSearchResult() + { + $searchCriteria = $this->getSearchCriteria(); + $sortOrders = $searchCriteria->getSortOrders(); + $sortOrder = current($sortOrders); + if (!$sortOrder->getField()) { + $sortOrder = $this->sortOrderBuilder->setDescendingDirection()->setField(LogInterface::TIME)->create(); + $searchCriteria->setSortOrders([$sortOrder]); + } + $result = $this->getLogsList->execute($searchCriteria); + + return $this->searchResultFactory->create( + $result->getItems(), + $result->getTotalCount(), + $searchCriteria, + LogInterface::LOG_ID + ); + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/composer.json b/app/code/Magento/LoginAsCustomerLog/composer.json new file mode 100644 index 0000000000000..3ee07ca7565a0 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/composer.json @@ -0,0 +1,27 @@ +{ + "name": "magento/module-login-as-customer-log", + "description": "", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-backend": "*", + "magento/module-customer": "*", + "magento/module-login-as-customer-api": "*", + "magento/module-ui": "*", + "magento/module-user": "*" + }, + "suggest": { + "magento/module-login-as-customer": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ "registration.php" ], + "psr-4": { + "Magento\\LoginAsCustomerLog\\": "" + } + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/etc/acl.xml b/app/code/Magento/LoginAsCustomerLog/etc/acl.xml new file mode 100644 index 0000000000000..0a46616b4ad7b --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/acl.xml @@ -0,0 +1,20 @@ +<?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:Acl/etc/acl.xsd"> + <acl> + <resources> + <resource id="Magento_Backend::admin"> + <resource id="Magento_Customer::customer"> + <resource id="Magento_LoginAsCustomer::login" title="Login as Customer" sortOrder="50"> + <resource id="Magento_LoginAsCustomerLog::login_log" title="View Login as Customer Log" sortOrder="20"/> + </resource> + </resource> + </resource> + </resources> + </acl> +</config> diff --git a/app/code/Magento/LoginAsCustomerLog/etc/adminhtml/menu.xml b/app/code/Magento/LoginAsCustomerLog/etc/adminhtml/menu.xml new file mode 100644 index 0000000000000..143e0ad4b5a6c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/adminhtml/menu.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:module:Magento_Backend:etc/menu.xsd"> + <menu> + <add id="Magento_LoginAsCustomerLog::login_log" + title="Login As Customer Log" + module="Magento_LoginAsCustomerLog" + parent="Magento_Customer::customer" + sortOrder="40" + resource="Magento_LoginAsCustomerLog::login_log" + action="loginascustomer_log/log/index"/> + </menu> +</config> diff --git a/app/code/Magento/LoginAsCustomerLog/etc/adminhtml/routes.xml b/app/code/Magento/LoginAsCustomerLog/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000..9201e5e7ac91f --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/adminhtml/routes.xml @@ -0,0 +1,15 @@ +<?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:App/etc/routes.xsd"> + <router id="admin"> + <route id="loginascustomer_log" frontName="loginascustomer_log"> + <module name="Magento_LoginAsCustomerLog"/> + </route> + </router> +</config> diff --git a/app/code/Magento/LoginAsCustomerLog/etc/db_schema.xml b/app/code/Magento/LoginAsCustomerLog/etc/db_schema.xml new file mode 100644 index 0000000000000..a1f40b4e5bbf5 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/db_schema.xml @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="magento_login_as_customer_log" resource="default" engine="innodb" comment="Login as Customer Logging"> + <column xsi:type="int" name="log_id" padding="11" unsigned="false" nullable="false" identity="true" comment="Log Id"/> + <column xsi:type="timestamp" name="time" on_update="false" nullable="true" comment="Event Date"/> + <column xsi:type="int" name="user_id" padding="10" unsigned="true" nullable="true" identity="false" comment="User Id"/> + <column xsi:type="varchar" name="user_name" nullable="true" length="40" comment="User Name"/> + <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer Id"/> + <column xsi:type="varchar" name="customer_email" nullable="true" length="40" comment="Customer email"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="log_id"/> + </constraint> + <index referenceId="MAGENTO_LOGIN_AS_CUSTOMER_LOG_USER_ID" indexType="btree"> + <column name="user_id"/> + </index> + </table> +</schema> diff --git a/app/code/Magento/LoginAsCustomerLog/etc/db_schema_whitelist.json b/app/code/Magento/LoginAsCustomerLog/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..6523a283edd0f --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/db_schema_whitelist.json @@ -0,0 +1,17 @@ +{ + "magento_login_as_customer_log": { + "column": { + "log_id": true, + "user_name": true, + "user_id": true, + "customer_id": true, + "customer_email": true + }, + "constraint": { + "PRIMARY": true + }, + "index": { + "MAGENTO_LOGIN_AS_CUSTOMER_LOG_USER_ID": true + } + } +} diff --git a/app/code/Magento/LoginAsCustomerLog/etc/di.xml b/app/code/Magento/LoginAsCustomerLog/etc/di.xml new file mode 100755 index 0000000000000..49d19d85f0d65 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/di.xml @@ -0,0 +1,13 @@ +<?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"> + <preference for="Magento\LoginAsCustomerLog\Api\Data\LogInterface" type="Magento\LoginAsCustomerLog\Model\Log"/> + <preference for="Magento\LoginAsCustomerLog\Api\Data\LogSearchResultsInterface" type="Magento\LoginAsCustomerLog\Model\LogSearchResults"/> + <preference for="Magento\LoginAsCustomerLog\Api\GetLogsListInterface" type="Magento\LoginAsCustomerLog\Model\GetLogList"/> + <preference for="Magento\LoginAsCustomerLog\Api\SaveLogsInterface" type="Magento\LoginAsCustomerLog\Model\ResourceModel\SaveLogs"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerLog/etc/frontend/di.xml b/app/code/Magento/LoginAsCustomerLog/etc/frontend/di.xml new file mode 100755 index 0000000000000..284b2db56e258 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/frontend/di.xml @@ -0,0 +1,13 @@ +<?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\LoginAsCustomerApi\Api\AuthenticateCustomerInterface"> + <plugin name="log_authentication_plugin" + type="Magento\LoginAsCustomerLog\Plugin\LoginAsCustomerApi\Api\AuthenticateCustomerInterface\LogAuthenticationPlugin"/> + </type> +</config> diff --git a/app/code/Magento/LoginAsCustomerLog/etc/module.xml b/app/code/Magento/LoginAsCustomerLog/etc/module.xml new file mode 100644 index 0000000000000..aef4e21780c4e --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/etc/module.xml @@ -0,0 +1,11 @@ +<?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_LoginAsCustomerLog"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerLog/registration.php b/app/code/Magento/LoginAsCustomerLog/registration.php new file mode 100644 index 0000000000000..2a4594d2218f6 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/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_LoginAsCustomerLog', + __DIR__ +); diff --git a/app/code/Magento/LoginAsCustomerLog/view/adminhtml/layout/loginascustomer_log_log_index.xml b/app/code/Magento/LoginAsCustomerLog/view/adminhtml/layout/loginascustomer_log_log_index.xml new file mode 100644 index 0000000000000..4ec4853877419 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/view/adminhtml/layout/loginascustomer_log_log_index.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="content"> + <uiComponent name="login_as_customer_log_listing"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/LoginAsCustomerLog/view/adminhtml/ui_component/login_as_customer_log_listing.xml b/app/code/Magento/LoginAsCustomerLog/view/adminhtml/ui_component/login_as_customer_log_listing.xml new file mode 100644 index 0000000000000..077fd6e18db7c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerLog/view/adminhtml/ui_component/login_as_customer_log_listing.xml @@ -0,0 +1,94 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">login_as_customer_log_listing.login_as_customer_log_listing_data_source</item> + </item> + </argument> + <settings> + <spinner>login_as_customer_log_listing_columns</spinner> + <deps> + <dep>login_as_customer_log_listing.login_as_customer_log_listing_data_source</dep> + </deps> + </settings> + <dataSource name="login_as_customer_log_listing_data_source" component="Magento_Ui/js/grid/provider"> + <settings> + <storageConfig> + <param name="indexField" xsi:type="string">log_id</param> + </storageConfig> + <updateUrl path="mui/index/render"/> + </settings> + <aclResource>Magento_LoginAsCustomerLog::login_log</aclResource> + <dataProvider class="Magento\LoginAsCustomerLog\Ui\DataProvider\LogDataProvider" name="login_as_customer_log_listing_data_source"> + <settings> + <requestFieldName>log_id</requestFieldName> + <primaryFieldName>Id</primaryFieldName> + </settings> + </dataProvider> + </dataSource> + <listingToolbar name="listing_top"> + <settings> + <sticky>true</sticky> + </settings> + <bookmark name="bookmarks"/> + <columnsControls name="columns_controls"/> + <filterSearch name="name"/> + <filters name="listing_filters"> + <settings> + <templates> + <filters> + <select> + <param name="template" xsi:type="string">ui/grid/filters/elements/ui-select</param> + <param name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</param> + </select> + </filters> + </templates> + </settings> + </filters> + <paging name="listing_paging"/> + </listingToolbar> + <columns name="login_as_customer_log_listing_columns"> + <column name="log_id" sortOrder="10"> + <settings> + <filter>text</filter> + <label translate="true">ID</label> + </settings> + </column> + <column name="customer_id" sortOrder="20"> + <settings> + <filter>text</filter> + <label translate="true">Customer ID</label> + </settings> + </column> + <column name="customer_email" sortOrder="30"> + <settings> + <filter>text</filter> + <label translate="true">Customer Email</label> + </settings> + </column> + <column name="user_id" sortOrder="40"> + <settings> + <filter>text</filter> + <label translate="true">Admin ID</label> + </settings> + </column> + <column name="user_name" sortOrder="50"> + <settings> + <filter>text</filter> + <label translate="true">Admin Name</label> + </settings> + </column> + <column name="time" sortOrder="60"> + <settings> + <filter>text</filter> + <label translate="true">Logged In</label> + </settings> + </column> + </columns> +</listing> diff --git a/app/code/Magento/LoginAsCustomerPageCache/Plugin/PageCache/Model/Config/DisablePageCacheIfNeededPlugin.php b/app/code/Magento/LoginAsCustomerPageCache/Plugin/PageCache/Model/Config/DisablePageCacheIfNeededPlugin.php new file mode 100644 index 0000000000000..6b36a0720ecb3 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/Plugin/PageCache/Model/Config/DisablePageCacheIfNeededPlugin.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerPageCache\Plugin\PageCache\Model\Config; + +use Magento\Customer\Model\Session; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\PageCache\Model\Config; +use Magento\Store\Model\ScopeInterface; + +/** + * Disable PageCache if enabled corresponding option in configuration + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class DisablePageCacheIfNeededPlugin +{ + /** + * Core store config + * + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var Session + */ + private $customerSession; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param Session $customerSession + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + Session $customerSession + ) { + $this->scopeConfig = $scopeConfig; + $this->customerSession = $customerSession; + } + + /** + * Disable page cache if needed when admin is logged as customer + * + * @param Config $subject + * @param bool $isEnabled + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterIsEnabled(Config $subject, $isEnabled): bool + { + if ($isEnabled) { + $disable = $this->scopeConfig->getValue( + 'login_as_customer/general/disable_page_cache', + ScopeInterface::SCOPE_STORE + ); + $adminId = $this->customerSession->getLoggedAsCustomerAdmindId(); + if ($disable && $adminId) { + $isEnabled = false; + } + } + return $isEnabled; + } +} diff --git a/app/code/Magento/LoginAsCustomerPageCache/README.md b/app/code/Magento/LoginAsCustomerPageCache/README.md new file mode 100644 index 0000000000000..91fa43fb4e833 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/README.md @@ -0,0 +1,3 @@ +# LoginAsCustomerPageCache module + +The Magento_LoginAsCustomerPageCache module provides adaptation to PageCache functionality diff --git a/app/code/Magento/LoginAsCustomerPageCache/composer.json b/app/code/Magento/LoginAsCustomerPageCache/composer.json new file mode 100644 index 0000000000000..3412c9fcb611c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-login-as-customer-page-cache", + "description": "", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-customer": "*", + "magento/module-store": "*" + }, + "suggest": { + "magento/module-page-cache": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ "registration.php" ], + "psr-4": { + "Magento\\LoginAsCustomerPageCache\\": "" + } + } +} diff --git a/app/code/Magento/LoginAsCustomerPageCache/etc/adminhtml/system.xml b/app/code/Magento/LoginAsCustomerPageCache/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..a586033af05db --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/etc/adminhtml/system.xml @@ -0,0 +1,20 @@ +<?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:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="login_as_customer"> + <group id="general"> + <field id="disable_page_cache" translate="label comment" type="select" sortOrder="20" showInDefault="1" canRestore="1"> + <label>Disable Page Cache For Admin User</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <comment><![CDATA[If set to "Yes", page caching is disabled when the Admin user is logged in as a customer.]]></comment> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/LoginAsCustomerPageCache/etc/config.xml b/app/code/Magento/LoginAsCustomerPageCache/etc/config.xml new file mode 100644 index 0000000000000..ba3f954f4980b --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/etc/config.xml @@ -0,0 +1,17 @@ +<?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:module:Magento_Store:etc/config.xsd"> + <default> + <login_as_customer> + <general> + <disable_page_cache>1</disable_page_cache> + </general> + </login_as_customer> + </default> +</config> diff --git a/app/code/Magento/LoginAsCustomerPageCache/etc/frontend/di.xml b/app/code/Magento/LoginAsCustomerPageCache/etc/frontend/di.xml new file mode 100644 index 0000000000000..1419c80d918dc --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/etc/frontend/di.xml @@ -0,0 +1,14 @@ +<?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\PageCache\Model\Config"> + <plugin name="login-as-customer-disable-page-cache" + type="Magento\LoginAsCustomerPageCache\Plugin\PageCache\Model\Config\DisablePageCacheIfNeededPlugin"/> + </type> +</config> \ No newline at end of file diff --git a/app/code/Magento/LoginAsCustomerPageCache/etc/module.xml b/app/code/Magento/LoginAsCustomerPageCache/etc/module.xml new file mode 100644 index 0000000000000..15c845a995794 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/etc/module.xml @@ -0,0 +1,11 @@ +<?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_LoginAsCustomerPageCache"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerPageCache/registration.php b/app/code/Magento/LoginAsCustomerPageCache/registration.php new file mode 100644 index 0000000000000..587c3d56050d7 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerPageCache/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_LoginAsCustomerPageCache', + __DIR__ +); diff --git a/app/code/Magento/LoginAsCustomerSales/Plugin/AdminAddCommentOnOrderPlacementPlugin.php b/app/code/Magento/LoginAsCustomerSales/Plugin/AdminAddCommentOnOrderPlacementPlugin.php new file mode 100644 index 0000000000000..2ae982e536f49 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/Plugin/AdminAddCommentOnOrderPlacementPlugin.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerSales\Plugin; + +use Magento\Backend\Model\Auth\Session; +use Magento\Sales\Model\Order; + +/** + * Add comment after order placed by admin using admin panel. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class AdminAddCommentOnOrderPlacementPlugin +{ + /** + * @var Session + */ + private $userSession; + + /** + * @param Session $session + */ + public function __construct( + Session $session + ) { + $this->userSession = $session; + } + + /** + * Add comment after order placed by admin using admin panel. + * + * @param Order $subject + * @param Order $result + * @return Order + */ + public function afterPlace(Order $subject, Order $result): Order + { + $adminUser = $this->userSession->getUser(); + if ($adminUser) { + $subject->addCommentToStatusHistory( + 'Order Placed by Store Administrator', + false, + true + )->setIsCustomerNotified(false); + $subject->addCommentToStatusHistory( + "Order Placed by {$adminUser->getFirstName()} {$adminUser->getLastName()} using Admin Panel", + false, + false + )->setIsCustomerNotified(false); + } + + return $result; + } +} diff --git a/app/code/Magento/LoginAsCustomerSales/Plugin/AuthenticateCustomerPlugin.php b/app/code/Magento/LoginAsCustomerSales/Plugin/AuthenticateCustomerPlugin.php new file mode 100644 index 0000000000000..5d8541d4dd440 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/Plugin/AuthenticateCustomerPlugin.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerSales\Plugin; + +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\Exception\LocalizedException; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\LoginAsCustomerApi\Api\AuthenticateCustomerInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; + +/** + * \Magento\LoginAsCustomerApi\Api\AuthenticateCustomerInterface Plugin + * + * Remove all items from guest shopping cart before execute. Mark customer cart as not-guest after execute + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class AuthenticateCustomerPlugin +{ + /** + * @var CustomerSession + */ + private $customerSession; + + /** + * @var CheckoutSession + */ + private $checkoutSession; + + /** + * @var CartRepositoryInterface + */ + private $quoteRepository; + + /** + * @param CustomerSession $customerSession + * @param CheckoutSession $checkoutSession + * @param CartRepositoryInterface $quoteRepository + */ + public function __construct( + CustomerSession $customerSession, + CheckoutSession $checkoutSession, + CartRepositoryInterface $quoteRepository + ) { + $this->customerSession = $customerSession; + $this->checkoutSession = $checkoutSession; + $this->quoteRepository = $quoteRepository; + } + + /** + * Remove all items from guest shopping cart + * + * @param AuthenticateCustomerInterface $subject + * @param AuthenticationDataInterface $authenticationData + * @return null + * @throws LocalizedException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeExecute( + AuthenticateCustomerInterface $subject, + AuthenticationDataInterface $authenticationData + ) { + if (!$this->customerSession->getId()) { + $quote = $this->checkoutSession->getQuote(); + /* Remove items from guest cart */ + $quote->removeAllItems(); + $this->quoteRepository->save($quote); + } + return null; + } + + /** + * Mark customer cart as not-guest + * + * @param AuthenticateCustomerInterface $subject + * @param void $result + * @param AuthenticationDataInterface $authenticationData + * @return void + * @throws LocalizedException + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute( + AuthenticateCustomerInterface $subject, + $result, + AuthenticationDataInterface $authenticationData + ) { + $this->checkoutSession->loadCustomerQuote(); + $quote = $this->checkoutSession->getQuote(); + + $quote->setCustomerIsGuest(0); + $this->quoteRepository->save($quote); + } +} diff --git a/app/code/Magento/LoginAsCustomerSales/Plugin/FrontAddCommentOnOrderPlacementPlugin.php b/app/code/Magento/LoginAsCustomerSales/Plugin/FrontAddCommentOnOrderPlacementPlugin.php new file mode 100644 index 0000000000000..dc7b295f61c4d --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/Plugin/FrontAddCommentOnOrderPlacementPlugin.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerSales\Plugin; + +use Magento\Customer\Model\Session; +use Magento\Sales\Model\Order; +use Magento\User\Model\UserFactory; + +/** + * Add comment after order placed by admin using Login as Customer. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class FrontAddCommentOnOrderPlacementPlugin +{ + /** + * @var Session + */ + private $customerSession; + + /** + * @var UserFactory + */ + private $userFactory; + + /** + * @param Session $session + * @param UserFactory $userFactory + */ + public function __construct( + Session $session, + UserFactory $userFactory + ) { + $this->customerSession = $session; + $this->userFactory = $userFactory; + } + + /** + * Add comment after order placed by admin using Login as Customer. + * + * @param Order $subject + * @param Order $result + * @return Order + */ + public function afterPlace(Order $subject, Order $result): Order + { + $adminId = $this->customerSession->getLoggedAsCustomerAdmindId(); + if ($adminId) { + $adminUser = $this->userFactory->create()->load($adminId); + $subject->addCommentToStatusHistory( + 'Order Placed by Store Administrator', + false, + true + )->setIsCustomerNotified(false); + $subject->addCommentToStatusHistory( + "Order Placed by {$adminUser->getFirstName()} {$adminUser->getLastName()} using Login as Customer", + false, + false + )->setIsCustomerNotified(false); + } + + return $result; + } +} diff --git a/app/code/Magento/LoginAsCustomerSales/README.md b/app/code/Magento/LoginAsCustomerSales/README.md new file mode 100644 index 0000000000000..d5e38d8ec5909 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/README.md @@ -0,0 +1,3 @@ +# Magento_LoginAsCustomerSales module + +The Magento_LoginAsCustomerSales module is responsible for comunication between Magento_LoginAsCustomer and shopping cart state. diff --git a/app/code/Magento/LoginAsCustomerSales/composer.json b/app/code/Magento/LoginAsCustomerSales/composer.json new file mode 100644 index 0000000000000..32c800561784c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/composer.json @@ -0,0 +1,30 @@ +{ + "name": "magento/module-login-as-customer-sales", + "description": "", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-backend": "*", + "magento/module-checkout": "*", + "magento/module-customer": "*", + "magento/module-quote": "*", + "magento/module-user": "*" + }, + "suggest": { + "magento/module-sales": "*", + "magento/module-login-as-customer-api": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\LoginAsCustomerSales\\": "" + } + } +} diff --git a/app/code/Magento/LoginAsCustomerSales/etc/adminhtml/di.xml b/app/code/Magento/LoginAsCustomerSales/etc/adminhtml/di.xml new file mode 100644 index 0000000000000..225688a8c7bab --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/etc/adminhtml/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\Sales\Model\Order"> + <plugin name="admin-order-placement-comment" type="Magento\LoginAsCustomerSales\Plugin\AdminAddCommentOnOrderPlacementPlugin"/> + </type> +</config> diff --git a/app/code/Magento/LoginAsCustomerSales/etc/di.xml b/app/code/Magento/LoginAsCustomerSales/etc/di.xml new file mode 100644 index 0000000000000..f267fc3850234 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/etc/di.xml @@ -0,0 +1,14 @@ +<?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\LoginAsCustomerApi\Api\AuthenticateCustomerInterface"> + <plugin name="login_as_customer_sales_authenticate_customer" + type="Magento\LoginAsCustomerSales\Plugin\AuthenticateCustomerPlugin"/> + </type> +</config> \ No newline at end of file diff --git a/app/code/Magento/LoginAsCustomerSales/etc/module.xml b/app/code/Magento/LoginAsCustomerSales/etc/module.xml new file mode 100644 index 0000000000000..91f62000c58f5 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/etc/module.xml @@ -0,0 +1,11 @@ +<?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_LoginAsCustomerSales"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerSales/etc/webapi_rest/di.xml b/app/code/Magento/LoginAsCustomerSales/etc/webapi_rest/di.xml new file mode 100644 index 0000000000000..1a010fcdead85 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/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\Sales\Model\Order"> + <plugin name="front-order-placement-comment" type="Magento\LoginAsCustomerSales\Plugin\FrontAddCommentOnOrderPlacementPlugin"/> + </type> +</config> diff --git a/app/code/Magento/LoginAsCustomerSales/registration.php b/app/code/Magento/LoginAsCustomerSales/registration.php new file mode 100644 index 0000000000000..4ee5647f5e837 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerSales/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_LoginAsCustomerSales', + __DIR__ +); diff --git a/app/code/Magento/LoginAsCustomerUi/Block/Adminhtml/ConfirmationPopup.php b/app/code/Magento/LoginAsCustomerUi/Block/Adminhtml/ConfirmationPopup.php new file mode 100644 index 0000000000000..6655e0a3a8fc4 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Block/Adminhtml/ConfirmationPopup.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Block\Adminhtml; + +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Store\Ui\Component\Listing\Column\Store\Options as StoreOptions; +use Magento\Backend\Block\Template; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; + +/** + * Admin blog post + * + * @api + */ +class ConfirmationPopup extends Template +{ + /** + * Store Options + * + * @var StoreOptions + */ + private $storeOptions; + + /** + * Config + * + * @var ConfigInterface + */ + private $config; + + /** + * Json Serializer + * + * @var Json + */ + private $json; + + /** + * @param Template\Context $context + * @param StoreOptions $storeOptions + * @param ConfigInterface $config + * @param Json $json + * @param array $data + */ + public function __construct( + Template\Context $context, + StoreOptions $storeOptions, + ConfigInterface $config, + Json $json, + array $data = [] + ) { + parent::__construct($context, $data); + $this->storeOptions = $storeOptions; + $this->config = $config; + $this->json = $json; + } + + /** + * @inheritdoc + */ + public function getJsLayout() + { + $layout = $this->json->unserialize(parent::getJsLayout()); + $showStoreViewOptions = $this->config->isStoreManualChoiceEnabled(); + + $layout['components']['lac-confirmation-popup']['title'] = $showStoreViewOptions + ? __('Login as Customer: Select Store View') + : __('You are about to Login as Customer'); + $layout['components']['lac-confirmation-popup']['content'] = + __('Actions taken while in "Login as Customer" will affect actual customer data.'); + + $layout['components']['lac-confirmation-popup']['showStoreViewOptions'] = $showStoreViewOptions; + $layout['components']['lac-confirmation-popup']['storeViewOptions'] = $showStoreViewOptions + ? $this->storeOptions->toOptionArray() + : []; + + return $this->json->serialize($layout); + } + + /** + * Render block HTML + * + * @return string + */ + protected function _toHtml() + { + if (!$this->config->isEnabled()) { + return ''; + } + return parent::_toHtml(); + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/Controller/Adminhtml/Login/Login.php b/app/code/Magento/LoginAsCustomerUi/Controller/Adminhtml/Login/Login.php new file mode 100755 index 0000000000000..c56b3c8e65fb0 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Controller/Adminhtml/Login/Login.php @@ -0,0 +1,195 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Controller\Adminhtml\Login; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\Auth\Session; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Url; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterface; +use Magento\LoginAsCustomerApi\Api\Data\AuthenticationDataInterfaceFactory; +use Magento\LoginAsCustomerApi\Api\DeleteExpiredAuthenticationDataInterface; +use Magento\LoginAsCustomerApi\Api\SaveAuthenticationDataInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Login as customer action + * Generate secret key and forward to the storefront action + * + * This action can be executed via GET request when "Store View To Login In" is disabled, and POST when it is enabled + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Login extends Action implements HttpGetActionInterface, HttpPostActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_LoginAsCustomer::login'; + + /** + * @var Session + */ + private $authSession; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var AuthenticationDataInterfaceFactory + */ + private $authenticationDataFactory; + + /** + * @var SaveAuthenticationDataInterface + */ + private $saveAuthenticationData; + + /** + * @var DeleteExpiredAuthenticationDataInterface + */ + private $deleteExpiredAuthenticationData; + + /** + * @var Url + */ + private $url; + + /** + * @param Context $context + * @param Session $authSession + * @param StoreManagerInterface $storeManager + * @param CustomerRepositoryInterface $customerRepository + * @param ConfigInterface $config + * @param AuthenticationDataInterfaceFactory $authenticationDataFactory + * @param SaveAuthenticationDataInterface $saveAuthenticationData , + * @param DeleteExpiredAuthenticationDataInterface $deleteExpiredAuthenticationData + * @param Url $url + */ + public function __construct( + Context $context, + Session $authSession, + StoreManagerInterface $storeManager, + CustomerRepositoryInterface $customerRepository, + ConfigInterface $config, + AuthenticationDataInterfaceFactory $authenticationDataFactory, + SaveAuthenticationDataInterface $saveAuthenticationData, + DeleteExpiredAuthenticationDataInterface $deleteExpiredAuthenticationData, + Url $url + ) { + parent::__construct($context); + + $this->authSession = $authSession; + $this->storeManager = $storeManager; + $this->customerRepository = $customerRepository; + $this->config = $config; + $this->authenticationDataFactory = $authenticationDataFactory; + $this->saveAuthenticationData = $saveAuthenticationData; + $this->deleteExpiredAuthenticationData = $deleteExpiredAuthenticationData; + $this->url = $url; + } + + /** + * Login as customer + * + * @return ResultInterface + * @throws NoSuchEntityException + * @throws LocalizedException + */ + public function execute(): ResultInterface + { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + + if (!$this->config->isEnabled()) { + $this->messageManager->addErrorMessage(__('Login As Customer is disabled.')); + return $resultRedirect->setPath('customer/index/index'); + } + + $customerId = (int)$this->_request->getParam('customer_id'); + if (!$customerId) { + $customerId = (int)$this->_request->getParam('entity_id'); + } + + try { + $this->customerRepository->getById($customerId); + } catch (NoSuchEntityException $e) { + $this->messageManager->addErrorMessage(__('Customer with this ID are no longer exist.')); + return $resultRedirect->setPath('customer/index/index'); + } + + $storeId = (int)$this->_request->getParam('store_id'); + if (empty($storeId) && $this->config->isStoreManualChoiceEnabled()) { + $this->messageManager->addNoticeMessage(__('Please select a Store View to login in.')); + return $resultRedirect->setPath('customer/index/edit', ['id' => $customerId]); + } + + $adminUser = $this->authSession->getUser(); + $userId = (int)$adminUser->getId(); + + /** @var AuthenticationDataInterface $authenticationData */ + $authenticationData = $this->authenticationDataFactory->create( + [ + 'customerId' => $customerId, + 'adminId' => $userId, + 'extensionAttributes' => null, + ] + ); + + $this->deleteExpiredAuthenticationData->execute($userId); + $secret = $this->saveAuthenticationData->execute($authenticationData); + + $redirectUrl = $this->getLoginProceedRedirectUrl($secret, $storeId); + $resultRedirect->setUrl($redirectUrl); + return $resultRedirect; + } + + /** + * Get login proceed redirect url + * + * @param string $secret + * @param int|null $storeId + * @return string + * @throws NoSuchEntityException + */ + private function getLoginProceedRedirectUrl(string $secret, ?int $storeId): string + { + if (null === $storeId) { + $store = $this->storeManager->getDefaultStoreView(); + } else { + $store = $this->storeManager->getStore($storeId); + } + + return $this->url + ->setScope($store) + ->getUrl('loginascustomer/login/index', ['secret' => $secret, '_nosid' => true]); + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/Controller/Login/Index.php b/app/code/Magento/LoginAsCustomerUi/Controller/Login/Index.php new file mode 100755 index 0000000000000..424473c7301ef --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Controller/Login/Index.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Controller\Login; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Message\ManagerInterface; +use Magento\LoginAsCustomerApi\Api\GetAuthenticationDataBySecretInterface; +use Magento\LoginAsCustomerApi\Api\AuthenticateCustomerInterface; +use Psr\Log\LoggerInterface; + +/** + * Login As Customer storefront login action + */ +class Index implements HttpGetActionInterface +{ + /** + * @var ResultFactory + */ + private $resultFactory; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var GetAuthenticationDataBySecretInterface + */ + private $getAuthenticationDataBySecret; + + /** + * @var AuthenticateCustomerInterface + */ + private $authenticateCustomer; + + /** + * @var ManagerInterface + */ + private $messageManager; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ResultFactory $resultFactory + * @param RequestInterface $request + * @param CustomerRepositoryInterface $customerRepository + * @param GetAuthenticationDataBySecretInterface $getAuthenticateDataProcessor + * @param AuthenticateCustomerInterface $authenticateCustomerProcessor + * @param ManagerInterface $messageManager + * @param LoggerInterface $logger + */ + public function __construct( + ResultFactory $resultFactory, + RequestInterface $request, + CustomerRepositoryInterface $customerRepository, + GetAuthenticationDataBySecretInterface $getAuthenticateDataProcessor, + AuthenticateCustomerInterface $authenticateCustomerProcessor, + ManagerInterface $messageManager, + LoggerInterface $logger + ) { + $this->resultFactory = $resultFactory; + $this->request = $request; + $this->customerRepository = $customerRepository; + $this->getAuthenticationDataBySecret = $getAuthenticateDataProcessor; + $this->authenticateCustomer = $authenticateCustomerProcessor; + $this->messageManager = $messageManager; + $this->logger = $logger; + } + + /** + * Login As Customer storefront login + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + + try { + $secret = $this->request->getParam('secret'); + if (empty($secret) || !is_string($secret)) { + throw new LocalizedException(__('Cannot login to account. No secret key provided.')); + } + + $authenticateData = $this->getAuthenticationDataBySecret->execute($secret); + + try { + $customer = $this->customerRepository->getById($authenticateData->getCustomerId()); + } catch (NoSuchEntityException $e) { + throw new LocalizedException(__('Customer are no longer exist.')); + } + + $this->authenticateCustomer->execute($authenticateData); + + $this->messageManager->addSuccessMessage( + __('You are logged in as customer: %1', $customer->getFirstname() . ' ' . $customer->getLastname()) + ); + $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); + $resultPage->getConfig()->getTitle()->set(__('You are logged in')); + return $this->resultFactory->create(ResultFactory::TYPE_PAGE); + + } catch (LocalizedException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $resultRedirect->setPath('/'); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + + $this->messageManager->addErrorMessage(__('Cannot login to account.')); + $resultRedirect->setPath('/'); + } + return $resultRedirect; + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/CustomerData/LoginAsCustomerUi.php b/app/code/Magento/LoginAsCustomerUi/CustomerData/LoginAsCustomerUi.php new file mode 100644 index 0000000000000..be30ee332f621 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/CustomerData/LoginAsCustomerUi.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\CustomerData; + +use Magento\Customer\CustomerData\SectionSourceInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\Exception\LocalizedException; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Customer data for the logged_as_customer section + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class LoginAsCustomerUi implements SectionSourceInterface +{ + /** + * @var Session + */ + private $customerSession; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param Session $customerSession + * @param StoreManagerInterface $storeManager + */ + public function __construct( + Session $customerSession, + StoreManagerInterface $storeManager + ) { + $this->customerSession = $customerSession; + $this->storeManager = $storeManager; + } + + /** + * Retrieve private customer data for the logged_as_customer section + * + * @return array + * @throws LocalizedException + */ + public function getSectionData(): array + { + if (!$this->customerSession->getCustomerId()) { + return []; + } + + return [ + 'adminUserId' => $this->customerSession->getLoggedAsCustomerAdmindId(), + 'websiteName' => $this->storeManager->getWebsite()->getName() + ]; + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/Model/Config/Source/StoreViewLogin.php b/app/code/Magento/LoginAsCustomerUi/Model/Config/Source/StoreViewLogin.php new file mode 100644 index 0000000000000..cf77f695287fd --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Model/Config/Source/StoreViewLogin.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Model\Config\Source; + +/** + * @inheritdoc + */ +class StoreViewLogin implements \Magento\Framework\Data\OptionSourceInterface +{ + /** + * @const int + */ + private const AUTODETECT = 0; + + /** + * @const int + */ + private const MANUAL = 1; + + /** + * @inheritdoc + */ + public function toOptionArray(): array + { + return [ + ['value' => self::AUTODETECT, 'label' => __('Auto-Detection (default)')], + ['value' => self::MANUAL, 'label' => __('Manual Selection')], + ]; + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/Plugin/AdminLogoutPlugin.php b/app/code/Magento/LoginAsCustomerUi/Plugin/AdminLogoutPlugin.php new file mode 100644 index 0000000000000..ff61b9482ac17 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Plugin/AdminLogoutPlugin.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Plugin; + +use Magento\Backend\Model\Auth; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; +use Magento\LoginAsCustomerApi\Api\DeleteExpiredAuthenticationDataInterface; + +/** + * Delete all Login as Customer sessions for logging out admin. + */ +class AdminLogoutPlugin +{ + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var DeleteExpiredAuthenticationDataInterface + */ + private $deleteExpiredAuthenticationData; + + /** + * @param ConfigInterface $config + * @param DeleteExpiredAuthenticationDataInterface $deleteExpiredAuthenticationData + */ + public function __construct( + ConfigInterface $config, + DeleteExpiredAuthenticationDataInterface $deleteExpiredAuthenticationData + ) { + $this->config = $config; + $this->deleteExpiredAuthenticationData = $deleteExpiredAuthenticationData; + } + + /** + * Delete all Login as Customer sessions for logging out admin. + * + * @param Auth $subject + */ + public function beforeLogout(Auth $subject): void + { + if ($this->config->isEnabled()) { + $userId = (int)$subject->getUser()->getId(); + $this->deleteExpiredAuthenticationData->execute($userId); + } + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/Plugin/Button/ToolbarPlugin.php b/app/code/Magento/LoginAsCustomerUi/Plugin/Button/ToolbarPlugin.php new file mode 100644 index 0000000000000..ec2c4dd5dcb65 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Plugin/Button/ToolbarPlugin.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Plugin\Button; + +use Magento\Backend\Block\Widget\Button\ButtonList; +use Magento\Backend\Block\Widget\Button\Toolbar; +use Magento\Framework\View\Element\AbstractBlock; +use Magento\Framework\Escaper; +use Magento\Framework\AuthorizationInterface; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; + +/** + * Plugin for \Magento\Backend\Block\Widget\Button\Toolbar. + */ +class ToolbarPlugin +{ + /** + * @var AuthorizationInterface + */ + private $authorization; + + /** + * @var Escaper + */ + private $escaper; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * ToolbarPlugin constructor. + * @param AuthorizationInterface $authorization + * @param ConfigInterface $config + * @param Escaper $escaper + */ + public function __construct( + AuthorizationInterface $authorization, + ConfigInterface $config, + Escaper $escaper + ) { + $this->authorization = $authorization; + $this->config = $config; + $this->escaper = $escaper; + } + + /** + * Add Login As Customer button. + * + * @param \Magento\Backend\Block\Widget\Button\Toolbar $subject + * @param \Magento\Framework\View\Element\AbstractBlock $context + * @param \Magento\Backend\Block\Widget\Button\ButtonList $buttonList + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforePushButtons( + Toolbar $subject, + AbstractBlock $context, + ButtonList $buttonList + ):void { + $order = false; + $nameInLayout = $context->getNameInLayout(); + + if ('sales_order_edit' == $nameInLayout) { + $order = $context->getOrder(); + } elseif ('sales_invoice_view' == $nameInLayout) { + $order = $context->getInvoice()->getOrder(); + } elseif ('sales_shipment_view' == $nameInLayout) { + $order = $context->getShipment()->getOrder(); + } elseif ('sales_creditmemo_view' == $nameInLayout) { + $order = $context->getCreditmemo()->getOrder(); + } + if ($order) { + + $isAllowed = $this->authorization->isAllowed('Magento_LoginAsCustomer::login_button'); + $isEnabled = $this->config->isEnabled(); + if ($isAllowed && $isEnabled) { + if (!empty($order['customer_id'])) { + $buttonUrl = $context->getUrl('loginascustomer/login/login', [ + 'customer_id' => $order['customer_id'] + ]); + $buttonList->add( + 'guest_to_customer', + [ + 'label' => __('Login As Customer'), + 'onclick' => 'window.lacConfirmationPopup("' + . $this->escaper->escapeHtml($this->escaper->escapeJs($buttonUrl)) + . '")', + 'class' => 'reset' + ], + -1 + ); + } + } + } + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/Plugin/InvalidateExpiredSessionPlugin.php b/app/code/Magento/LoginAsCustomerUi/Plugin/InvalidateExpiredSessionPlugin.php new file mode 100644 index 0000000000000..1f80292ed2738 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Plugin/InvalidateExpiredSessionPlugin.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\LoginAsCustomerUi\Plugin; + +use Magento\Customer\Model\Session; +use Magento\Framework\App\ActionInterface; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; +use Magento\LoginAsCustomerApi\Api\IsLoginAsCustomerSessionActiveInterface; + +/** + * Invalidate expired and not active Login as Customer sessions. + */ +class InvalidateExpiredSessionPlugin +{ + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var Session + */ + private $session; + + /** + * @var IsLoginAsCustomerSessionActiveInterface + */ + private $isLoginAsCustomerSessionActive; + + /** + * @param ConfigInterface $config + * @param Session $session + * @param IsLoginAsCustomerSessionActiveInterface $isLoginAsCustomerSessionActive + */ + public function __construct( + ConfigInterface $config, + Session $session, + IsLoginAsCustomerSessionActiveInterface $isLoginAsCustomerSessionActive + ) { + $this->session = $session; + $this->isLoginAsCustomerSessionActive = $isLoginAsCustomerSessionActive; + $this->config = $config; + } + + /** + * Invalidate expired and not active Login as Customer sessions. + * + * @param ActionInterface $subject + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeExecute(ActionInterface $subject) + { + if ($this->config->isEnabled()) { + $adminId = (int)$this->session->getLoggedAsCustomerAdmindId(); + $customerId = (int)$this->session->getCustomerId(); + if ($adminId && $customerId) { + if (!$this->isLoginAsCustomerSessionActive->execute($customerId, $adminId)) { + $this->session->destroy(); + } + } + } + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/README.md b/app/code/Magento/LoginAsCustomerUi/README.md new file mode 100644 index 0000000000000..11e8da9310920 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/README.md @@ -0,0 +1,3 @@ +# Magento_LoginAsCustomerSales module + +The Magento_LoginAsCustomerUi module provides UI for Magento_LoginAsCustomerUi diff --git a/app/code/Magento/LoginAsCustomerUi/Ui/Customer/Component/Control/LoginAsCustomerButton.php b/app/code/Magento/LoginAsCustomerUi/Ui/Customer/Component/Control/LoginAsCustomerButton.php new file mode 100644 index 0000000000000..88add39eb1efb --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Ui/Customer/Component/Control/LoginAsCustomerButton.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Ui\Customer\Component\Control; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; +use Magento\Framework\AuthorizationInterface; +use Magento\Framework\Escaper; +use Magento\Framework\Registry; +use Magento\Backend\Block\Widget\Context; +use Magento\Customer\Block\Adminhtml\Edit\GenericButton; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; + +/** + * Login As Customer button UI component. + */ +class LoginAsCustomerButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @var AuthorizationInterface + */ + private $authorization; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * Escaper + * + * @var Escaper + */ + private $escaper; + + /** + * @param Context $context + * @param Registry $registry + * @param ConfigInterface $config + */ + public function __construct( + Context $context, + Registry $registry, + ConfigInterface $config + ) { + parent::__construct($context, $registry); + $this->authorization = $context->getAuthorization(); + $this->config = $config; + $this->escaper = $context->getEscaper(); + } + + /** + * @inheritdoc + */ + public function getButtonData(): array + { + $customerId = $this->getCustomerId(); + $data = []; + $isAllowed = $customerId && $this->authorization->isAllowed('Magento_LoginAsCustomer::login_button'); + $isEnabled = $this->config->isEnabled(); + if ($isAllowed && $isEnabled) { + $data = [ + 'label' => __('Login As Customer'), + 'class' => 'login login-button', + 'on_click' => 'window.lacConfirmationPopup("' + . $this->escaper->escapeHtml($this->escaper->escapeJs($this->getLoginUrl())) + . '")', + 'sort_order' => 70, + ]; + } + + return $data; + } + + /** + * Get Login As Customer login url. + * + * @return string + */ + public function getLoginUrl(): string + { + return $this->getUrl('loginascustomer/login/login', ['customer_id' => $this->getCustomerId()]); + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/Ui/Store/Component/Control/LoginAsCustomerButton.php b/app/code/Magento/LoginAsCustomerUi/Ui/Store/Component/Control/LoginAsCustomerButton.php new file mode 100644 index 0000000000000..265e1addc48ab --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/Ui/Store/Component/Control/LoginAsCustomerButton.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\Ui\Store\Component\Control; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; + +/** + * Login As Customer button UI component. + */ +class LoginAsCustomerButton implements ButtonProviderInterface +{ + /** + * Get button data + * + * @return array + */ + public function getButtonData(): array + { + return [ + 'label' => __('Login As Customer'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 90, + ]; + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/ViewModel/Configuration.php b/app/code/Magento/LoginAsCustomerUi/ViewModel/Configuration.php new file mode 100644 index 0000000000000..7cbe30b116194 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/ViewModel/Configuration.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerUi\ViewModel; + +use Magento\Customer\Model\Context; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; + +/** + * View model to get extension configuration in the template + */ +class Configuration implements \Magento\Framework\View\Element\Block\ArgumentInterface +{ + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var \Magento\Framework\App\Http\Context + */ + private $httpContext; + + /** + * @param ConfigInterface $config + * @param \Magento\Framework\App\Http\Context $httpContext + */ + public function __construct( + ConfigInterface $config, + \Magento\Framework\App\Http\Context $httpContext + ) { + $this->config = $config; + $this->httpContext = $httpContext; + } + + /** + * Retrieve true if login as a customer is enabled + * + * @return bool + */ + public function isEnabled(): bool + { + return $this->config->isEnabled() && $this->isLoggedIn(); + } + + /** + * Is logged in + * + * @return bool + */ + private function isLoggedIn(): bool + { + return (bool)$this->httpContext->getValue(Context::CONTEXT_AUTH); + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/composer.json b/app/code/Magento/LoginAsCustomerUi/composer.json new file mode 100644 index 0000000000000..ba18098ee13f9 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-login-as-customer-ui", + "description": "", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-login-as-customer-api": "*", + "magento/module-backend": "*", + "magento/module-customer": "*", + "magento/module-store": "*" + }, + "suggest": { + "magento/module-login-as-customer": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ "registration.php" ], + "psr-4": { + "Magento\\LoginAsCustomerUi\\": "" + } + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/etc/acl.xml b/app/code/Magento/LoginAsCustomerUi/etc/acl.xml new file mode 100755 index 0000000000000..f49526f6bbb04 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/etc/acl.xml @@ -0,0 +1,27 @@ +<?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:Acl/etc/acl.xsd"> + <acl> + <resources> + <resource id="Magento_Backend::admin"> + <resource id="Magento_Customer::customer"> + <resource id="Magento_LoginAsCustomer::login" title="Login as Customer" sortOrder="50"> + <resource id="Magento_LoginAsCustomer::login_button" title="Allow Login as Customer Button" sortOrder="10" /> + </resource> + </resource> + <resource id="Magento_Backend::stores"> + <resource id="Magento_Backend::stores_settings"> + <resource id="Magento_Config::config"> + <resource id="Magento_LoginAsCustomer::config_section" title="Login as Customer Section" /> + </resource> + </resource> + </resource> + </resource> + </resources> + </acl> +</config> diff --git a/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/di.xml b/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/di.xml new file mode 100644 index 0000000000000..2ee0b83be573e --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/di.xml @@ -0,0 +1,15 @@ +<?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\Backend\Block\Widget\Button\Toolbar"> + <plugin name="login_as_customer_button_toolbar" type="Magento\LoginAsCustomerUi\Plugin\Button\ToolbarPlugin"/> + </type> + <type name="Magento\Backend\Model\Auth"> + <plugin name="login_as_customer_admin_logout" type="Magento\LoginAsCustomerUi\Plugin\AdminLogoutPlugin"/> + </type> +</config> diff --git a/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/routes.xml b/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/routes.xml new file mode 100755 index 0000000000000..1122e490db306 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ +<?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:App/etc/routes.xsd"> + <router id="admin"> + <route id="loginascustomer" frontName="loginascustomer"> + <module name="Magento_LoginAsCustomerUi"/> + </route> + </router> +</config> diff --git a/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/system.xml b/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/system.xml new file mode 100755 index 0000000000000..2a5614d44d00c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/etc/adminhtml/system.xml @@ -0,0 +1,32 @@ +<?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:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="login_as_customer" translate="label" type="text" sortOrder="1" showInDefault="1"> + <class>separator-top</class> + <label>Login as Customer</label> + <tab>customer</tab> + <resource>Magento_LoginAsCustomer::config_section</resource> + <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" canRestore="1"> + <label>Login as Customer Settings</label> + <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" canRestore="1"> + <label>Enable Extension</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + <field id="store_view_manual_choice_enabled" translate="label comment" type="select" sortOrder="40" showInDefault="1" canRestore="1"> + <label>Store View To Login To</label> + <source_model>Magento\LoginAsCustomerUi\Model\Config\Source\StoreViewLogin</source_model> + <comment><![CDATA[ + Use the "Manual Selection" option on a multi-website setup that has "Share Customer Accounts" enabled globally. + If set to "Manual Selection", the "Login as Customer" admin can select a store view after logging in. + ]]></comment> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/LoginAsCustomerUi/etc/frontend/di.xml b/app/code/Magento/LoginAsCustomerUi/etc/frontend/di.xml new file mode 100755 index 0000000000000..746e908234edc --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/etc/frontend/di.xml @@ -0,0 +1,20 @@ +<?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\Customer\CustomerData\SectionPoolInterface"> + <arguments> + <argument name="sectionSourceMap" xsi:type="array"> + <item name="loggedAsCustomer" xsi:type="string">Magento\LoginAsCustomerUi\CustomerData\LoginAsCustomerUi</item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\App\ActionInterface"> + <plugin name="invalidate_expired_session_plugin" + type="Magento\LoginAsCustomerUi\Plugin\InvalidateExpiredSessionPlugin"/> + </type> +</config> diff --git a/app/code/Magento/LoginAsCustomerUi/etc/frontend/routes.xml b/app/code/Magento/LoginAsCustomerUi/etc/frontend/routes.xml new file mode 100755 index 0000000000000..7336b032b21c6 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/etc/frontend/routes.xml @@ -0,0 +1,14 @@ +<?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:App/etc/routes.xsd"> + <router id="standard"> + <route id="loginascustomer" frontName="loginascustomer"> + <module name="Magento_LoginAsCustomerUi"/> + </route> + </router> +</config> diff --git a/app/code/Magento/LoginAsCustomerUi/etc/module.xml b/app/code/Magento/LoginAsCustomerUi/etc/module.xml new file mode 100644 index 0000000000000..c937de225c306 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/etc/module.xml @@ -0,0 +1,11 @@ +<?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_LoginAsCustomerUi"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerUi/registration.php b/app/code/Magento/LoginAsCustomerUi/registration.php new file mode 100644 index 0000000000000..501dc6a7ca382 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/registration.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_LoginAsCustomerUi', + __DIR__ +); diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/adminhtml_order_shipment_view.xml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/adminhtml_order_shipment_view.xml new file mode 100644 index 0000000000000..4671a2178ded7 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/adminhtml_order_shipment_view.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <update handle="loginascustomer_confirmation_popup" /> +</layout> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/customer_index_edit.xml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/customer_index_edit.xml new file mode 100644 index 0000000000000..4671a2178ded7 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/customer_index_edit.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <update handle="loginascustomer_confirmation_popup" /> +</layout> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/loginascustomer_confirmation_popup.xml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/loginascustomer_confirmation_popup.xml new file mode 100644 index 0000000000000..542cbbbd39d6e --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/loginascustomer_confirmation_popup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <referenceContainer name="content"> + <block class="Magento\LoginAsCustomerUi\Block\Adminhtml\ConfirmationPopup" name="lac.confirmation.popup" template="Magento_LoginAsCustomerUi::confirmation-popup.phtml"> + <arguments> + <argument name="jsLayout" xsi:type="array"> + <item name="components" xsi:type="array"> + <item name="lac-confirmation-popup" xsi:type="array"> + <item name="component" xsi:type="string">Magento_LoginAsCustomerUi/js/confirmation-popup</item> + </item> + </item> + </argument> + </arguments> + </block> + </referenceContainer> +</layout> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_creditmemo_view.xml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_creditmemo_view.xml new file mode 100644 index 0000000000000..4671a2178ded7 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_creditmemo_view.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <update handle="loginascustomer_confirmation_popup" /> +</layout> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_invoice_view.xml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_invoice_view.xml new file mode 100644 index 0000000000000..4671a2178ded7 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_invoice_view.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <update handle="loginascustomer_confirmation_popup" /> +</layout> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_view.xml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_view.xml new file mode 100644 index 0000000000000..4671a2178ded7 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/layout/sales_order_view.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <update handle="loginascustomer_confirmation_popup" /> +</layout> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/templates/confirmation-popup.phtml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/templates/confirmation-popup.phtml new file mode 100644 index 0000000000000..0f46bb952f9bb --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/templates/confirmation-popup.phtml @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/** @var \Magento\LoginAsCustomerUi\Block\Adminhtml\ConfirmationPopup $block */ +?> + +<script type="text/x-magento-init"> + { + "*": { + "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout();?> + } + } + </script> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/ui_component/customer_form.xml b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/ui_component/customer_form.xml new file mode 100755 index 0000000000000..96aefae34299e --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/ui_component/customer_form.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> + <settings> + <buttons> + <button name="login_as_customer" + class="\Magento\LoginAsCustomerUi\Ui\Customer\Component\Control\LoginAsCustomerButton"/> + </buttons> + </settings> +</form> diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/css/source/_module.less b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/css/source/_module.less new file mode 100644 index 0000000000000..2901f95f0e279 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/css/source/_module.less @@ -0,0 +1,42 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +// +// Variables +// --------------------------------------------- + +@lac-confirm-popup-title-background-color: #ccc; +@lac-confirm-popup-content-color: #514943; + +// +// Common +// --------------------------------------------- + +& when (@media-common = true) { + .modal-popup.confirm.lac-confirm { + .modal-inner-wrap { + max-width: 55rem; + } + .modal-title { + border-bottom: 1px solid @lac-confirm-popup-title-background-color; + padding-bottom: 15px; + width: 100%; + } + + .store-view-ptions { + padding-top: 15px; + } + + .modal-content { + .message-warning { + padding-left: 4.5rem; + &:before { + color: @lac-confirm-popup-content-color; + left: 5px; + } + } + } + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/js/confirmation-popup.js b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/js/confirmation-popup.js new file mode 100644 index 0000000000000..22f379c0009f4 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/js/confirmation-popup.js @@ -0,0 +1,92 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiComponent', + 'Magento_Ui/js/modal/confirm', + 'jquery', + 'ko', + 'mage/translate', + 'mage/template', + 'text!Magento_LoginAsCustomerUi/template/confirmation-popup/store-view-ptions.html' +], function (Component, confirm, $, ko, $t, template, selectTpl) { + + 'use strict'; + + return Component.extend({ + /** + * Initialize Component + */ + initialize: function () { + var self = this, + content = '<div class="message message-warning">' + self.content + '</div>'; + + this._super(); + + if (self.showStoreViewOptions) { + content = template( + selectTpl, + { + data: { + showStoreViewOptions: self.showStoreViewOptions, + storeViewOptions: self.storeViewOptions, + label: $t('Store View') + } + }) + content; + } + + /** + * Confirmation popup + * + * @param {String} url + * @returns {Boolean} + */ + window.lacConfirmationPopup = function (url) { + confirm({ + title: self.title, + content: content, + modalClass: 'confirm lac-confirm', + actions: { + /** + * Confirm action. + */ + confirm: function () { + var storeId = $('#lac-confirmation-popup-store-id').val(); + + if (storeId) { + url += url.indexOf('?') === -1 ? '?' : '&'; + url += 'store_id=' + storeId; + } + window.open(url); + } + }, + buttons: [{ + text: $t('Cancel'), + class: 'action-secondary action-dismiss', + + /** + * Click handler. + */ + click: function (event) { + this.closeModal(event); + } + }, { + text: $t('Login as Customer'), + class: 'action-primary action-accept', + + /** + * Click handler. + */ + click: function (event) { + this.closeModal(event, true); + } + }] + }); + + return false; + }; + } + }); +}); diff --git a/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/template/confirmation-popup/store-view-ptions.html b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/template/confirmation-popup/store-view-ptions.html new file mode 100644 index 0000000000000..ed1f991245e70 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/adminhtml/web/template/confirmation-popup/store-view-ptions.html @@ -0,0 +1,32 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<% if(data.showStoreViewOptions){ %> +<div class="store-view-ptions"> + <fieldset class="admin__fieldset" > + <div class="admin__field _required" > + <div class="admin__field-label"> + <label for="lac-confirmation-popup-store-id"> + <span><%= data.label %></span> + </label> + </div> + <div class="admin__field-control"> + <select class="admin__control-select" id="lac-confirmation-popup-store-id"> + <% _.each(data.storeViewOptions, function(website) { %> + <optgroup label="<%= website.label %>"></optgroup> + <% _.each(website.value, function(group) { %> + <optgroup label="<%= group.label %>"></optgroup> + <% _.each(group.value, function(storeview) { %> + <option value="<%= storeview.value %>"><%= storeview.label %></option> + <% }); %> + <% }); %> + <% }); %> + </select> + </div> + </div> + </fieldset> +</div> +<% } %> diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/layout/default.xml b/app/code/Magento/LoginAsCustomerUi/view/frontend/layout/default.xml new file mode 100644 index 0000000000000..c27f3adcf23cc --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/layout/default.xml @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="after.body.start"> + <block name="login-as-customer-notice" template="Magento_LoginAsCustomerUi::html/notices.phtml"> + <arguments> + <argument name="config" xsi:type="object">Magento\LoginAsCustomerUi\ViewModel\Configuration</argument> + </arguments> + + <container name="login-as-customer-notice-links"> + <block class="Magento\Customer\Block\Account\AuthorizationLink" name="login-as-customer-logout-link" + template="Magento_LoginAsCustomerUi::html/notices/logout-link.phtml" /> + </container> + </block> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/layout/loginascustomer_login_index.xml b/app/code/Magento/LoginAsCustomerUi/view/frontend/layout/loginascustomer_login_index.xml new file mode 100644 index 0000000000000..e7c3010a86364 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/layout/loginascustomer_login_index.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="page.main.title"> + <action method="setPageTitle"> + <argument name="title" translate="true" xsi:type="string">You are logged in.</argument> + </action> + </referenceBlock> + <referenceContainer name="content"> + <block class="Magento\Framework\View\Element\Template" name="loginascustomer_login" template="Magento_LoginAsCustomerUi::login.phtml"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/html/notices.phtml b/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/html/notices.phtml new file mode 100644 index 0000000000000..a2358cdb2f18b --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/html/notices.phtml @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @var \Magento\Framework\View\Element\Template $block + * @var \Magento\Framework\Escaper $escaper + */ +$viewFileUrl = $block->getViewFileUrl('Magento_LoginAsCustomerUi::images/magento-icon.svg'); +?> +<?php if ($block->getConfig()->isEnabled()): ?> + <div data-bind="scope: 'loginAsCustomer'" > + <div class="lac-notification clearfix" data-bind="visible: isVisible" style="display: none"> + <div class="top-container"> + <div class="lac-notification-icon wrapper"> + <img class="logo-img" src="<?= $escaper->escapeUrl($viewFileUrl) ?>" alt="Magento" /> + </div> + <div class="lac-notification-text wrapper"> + <span data-bind="html: notificationText"></span> + </div> + <div class="lac-notification-links wrapper"> + <?= $block->getChildHtml('login-as-customer-notice-links') ?> + </div> + </div> + </div> + </div> + <script type="text/x-magento-init"> + { + "*": { + "Magento_Ui/js/core/app": { + "components": { + "loginAsCustomer": { + "component": "Magento_LoginAsCustomerUi/js/view/loginAsCustomer" + } + } + } + } + } + </script> +<?php endif; ?> diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/html/notices/logout-link.phtml b/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/html/notices/logout-link.phtml new file mode 100644 index 0000000000000..ae29324ade0a1 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/html/notices/logout-link.phtml @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @var \Magento\Customer\Block\Account\AuthorizationLink $block + * @var \Magento\Framework\Escaper $escaper + */ +$dataPostParam = ''; +if ($block->isLoggedIn()) { + $dataPostParam = sprintf(" data-post='%s'", $block->getPostParams()); +} +?> + +<a class="lac-notification-close-link" <?= /* @noEscape */ $block->getLinkAttributes() +?><?= /* @noEscape */ $dataPostParam ?>> + <?= $escaper->escapeHtml(__('Close Session')) ?> +</a> + diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/login.phtml b/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/login.phtml new file mode 100644 index 0000000000000..2abd8fc204831 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/templates/login.phtml @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/** @var \Magento\Framework\View\Element\Template $block */ +?> + +<script type="text/x-magento-init"> +{ + "*": { + "Magento_LoginAsCustomerUi/js/login": { + "redirectUrl": "<?= /* @noEscape */ $block->getUrl('customer/account/index') ?>" + } + } +} +</script> diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/web/css/source/_module.less b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/css/source/_module.less new file mode 100644 index 0000000000000..372405c2635ef --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/css/source/_module.less @@ -0,0 +1,77 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +// +// Variables +// --------------------------------------------- + +@lac-notification-background-color: #373330; +@lac-notification-color: #fff; +@lac-notification-links-color: #fff; + +// +// Common +// --------------------------------------------- + +& when (@media-common = true) { + .lac-notification { + background-color: @lac-notification-background-color; + color: @lac-notification-color; + font-size: 16px; + + .lac-notification-icon { + float: left; + margin: 10px 25px 10px 10px; + + .logo-img { + display: block + } + } + + .lac-notification-text { + float: left; + padding: 15px 0; + } + + .lac-notification-links { + float: right; + padding: 15px 0; + + a { + color: @lac-notification-links-color; + font-size: 14px; + } + + .lac-notification-close-link { + &:after { + background: url('../Magento_LoginAsCustomerUi/images/close.svg'); + content: ' '; + display: inline-block; + height: 12px; + margin-left: 5px; + vertical-align: middle; + width: 12px; + } + } + } + } +} + +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .lac-notification { + padding: 5px 0; + + .lac-notification-icon { + display: none; + } + + .lac-notification-text, + .lac-notification-links { + float: none; + padding: 5px 0; + text-align: center; + } + } +} diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/web/images/close.svg b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/images/close.svg new file mode 100644 index 0000000000000..0895684d12e63 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/images/close.svg @@ -0,0 +1,2 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 413.348 413.348" height="12px" viewBox="0 0 413.348 413.348" width="12px"><g><path d="m413.348 24.354-24.354-24.354-182.32 182.32-182.32-182.32-24.354 24.354 182.32 182.32-182.32 182.32 24.354 24.354 182.32-182.32 182.32 182.32 24.354-24.354-182.32-182.32z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#FFFFFF"/></g> </svg> diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/web/images/magento-icon.svg b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/images/magento-icon.svg new file mode 100644 index 0000000000000..47e64067795ef --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/images/magento-icon.svg @@ -0,0 +1 @@ +<svg baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" width="28" height="33" viewBox="-0.154 0 54 62"><g fill="#E85D22"><path d="M26.845 8.857"/><path d="M53.692 15.5v31l-7.67 4.43v-31L26.844 8.856 7.67 19.926V50.93L0 46.5v-31L26.845 0zM26.847 62L15.34 55.355V24.357l7.67-4.43V50.93l3.835 2.327 3.837-2.327v-31l7.67 4.427v30.998z"/></g></svg> diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/web/js/login.js b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/js/login.js new file mode 100644 index 0000000000000..ab325bc90cf00 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/js/login.js @@ -0,0 +1,20 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Customer/js/customer-data', + 'Magento_Customer/js/section-config' +], function ($, customerData, sectionConfig) { + + 'use strict'; + + return function (config) { + $('body').trigger('processStart'); + customerData.reload(sectionConfig.getSectionNames()).done(function () { + window.location.href = config.redirectUrl; + }); + }; +}); diff --git a/app/code/Magento/LoginAsCustomerUi/view/frontend/web/js/view/loginAsCustomer.js b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/js/view/loginAsCustomer.js new file mode 100644 index 0000000000000..7f6cad6ce3f2d --- /dev/null +++ b/app/code/Magento/LoginAsCustomerUi/view/frontend/web/js/view/loginAsCustomer.js @@ -0,0 +1,41 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'uiComponent', + 'Magento_Customer/js/customer-data', + 'mage/translate' +], function ($, Component, customerData) { + 'use strict'; + + return Component.extend({ + + defaults: { + isVisible: false + }, + + /** @inheritdoc */ + initialize: function () { + this._super(); + + this.customer = customerData.get('customer'); + this.loginAsCustomer = customerData.get('loggedAsCustomer'); + this.isVisible(this.loginAsCustomer().adminUserId); + + this.notificationText = $.mage.__('You are connected as <strong>%1</strong> on %2') + .replace('%1', this.customer().fullname) + .replace('%2', this.loginAsCustomer().websiteName); + }, + + /** @inheritdoc */ + initObservable: function () { + this._super() + .observe('isVisible'); + + return this; + } + }); +}); diff --git a/app/code/Magento/LoginAsCustomerWebapi/Api/CreateCustomerAccessTokenInterface.php b/app/code/Magento/LoginAsCustomerWebapi/Api/CreateCustomerAccessTokenInterface.php new file mode 100644 index 0000000000000..52acc695af0cc --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/Api/CreateCustomerAccessTokenInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerWebapi\Api; + +/** + * Interface providing customer token generation for admin. + * + * @api + */ +interface CreateCustomerAccessTokenInterface +{ + /** + * Create access token for admin by customer id. + * + * Returns created token. + * + * @param int $customerId + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(int $customerId): string; +} diff --git a/app/code/Magento/LoginAsCustomerWebapi/LICENSE.txt b/app/code/Magento/LoginAsCustomerWebapi/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/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/LoginAsCustomerWebapi/LICENSE_AFL.txt b/app/code/Magento/LoginAsCustomerWebapi/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/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/LoginAsCustomerWebapi/Model/CreateCustomerAccessToken.php b/app/code/Magento/LoginAsCustomerWebapi/Model/CreateCustomerAccessToken.php new file mode 100644 index 0000000000000..0279480f1ba26 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/Model/CreateCustomerAccessToken.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerWebapi\Model; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Integration\Model\Oauth\TokenFactory; +use Magento\LoginAsCustomerApi\Api\ConfigInterface; +use Magento\LoginAsCustomerWebapi\Api\CreateCustomerAccessTokenInterface; + +/** + * @inheritdoc + */ +class CreateCustomerAccessToken implements CreateCustomerAccessTokenInterface +{ + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var ManagerInterface + */ + private $eventManager; + + /** + * @var TokenFactory + */ + private $tokenFactory; + + /** + * @param ConfigInterface $config + * @param CustomerRepositoryInterface $customerRepository + * @param ManagerInterface $eventManager + * @param TokenFactory $tokenFactory + */ + public function __construct( + ConfigInterface $config, + CustomerRepositoryInterface $customerRepository, + ManagerInterface $eventManager, + TokenFactory $tokenFactory + ) { + $this->config = $config; + $this->customerRepository = $customerRepository; + $this->eventManager = $eventManager; + $this->tokenFactory = $tokenFactory; + } + + /** + * @inheritdoc + */ + public function execute(int $customerId): string + { + if ($this->config->isEnabled()) { + $customer = $this->customerRepository->getById($customerId); + $this->eventManager->dispatch('customer_login', ['customer' => $customer]); + + return $this->tokenFactory->create()->createCustomerToken($customerId)->getToken(); + } else { + throw new LocalizedException(__('Service is disabled.')); + } + } +} diff --git a/app/code/Magento/LoginAsCustomerWebapi/README.md b/app/code/Magento/LoginAsCustomerWebapi/README.md new file mode 100644 index 0000000000000..ef42fd292cda7 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/README.md @@ -0,0 +1,4 @@ +# Magento_LoginAsCustomer module + +The Magento_LoginAsCustomerWebapi module provides API for ability to login into customer account for an admin user. + diff --git a/app/code/Magento/LoginAsCustomerWebapi/composer.json b/app/code/Magento/LoginAsCustomerWebapi/composer.json new file mode 100644 index 0000000000000..3fd430d42f7a1 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/composer.json @@ -0,0 +1,22 @@ +{ + "name": "magento/module-login-as-customer-webapi", + "description": "Grants admin token customer account", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-customer": "*", + "magento/module-integration": "*", + "magento/module-login-as-customer-api": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ "registration.php" ], + "psr-4": { + "Magento\\LoginAsCustomerWebapi\\": "" + } + } +} diff --git a/app/code/Magento/LoginAsCustomerWebapi/etc/acl.xml b/app/code/Magento/LoginAsCustomerWebapi/etc/acl.xml new file mode 100644 index 0000000000000..33d912843d7d3 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/etc/acl.xml @@ -0,0 +1,20 @@ +<?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:Acl/etc/acl.xsd"> + <acl> + <resources> + <resource id="Magento_Backend::admin"> + <resource id="Magento_Customer::customer"> + <resource id="Magento_LoginAsCustomer::login" title="Login as Customer" sortOrder="50"> + <resource id="Magento_LoginAsCustomerWebapi::login_token" title="Create Login as Customer Token" sortOrder="30" /> + </resource> + </resource> + </resource> + </resources> + </acl> +</config> diff --git a/app/code/Magento/LoginAsCustomerWebapi/etc/di.xml b/app/code/Magento/LoginAsCustomerWebapi/etc/di.xml new file mode 100644 index 0000000000000..f825f533c4961 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/etc/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"> + <preference for="Magento\LoginAsCustomerWebapi\Api\CreateCustomerAccessTokenInterface" + type="Magento\LoginAsCustomerWebapi\Model\CreateCustomerAccessToken"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerWebapi/etc/module.xml b/app/code/Magento/LoginAsCustomerWebapi/etc/module.xml new file mode 100644 index 0000000000000..f6a2e614cc95b --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/etc/module.xml @@ -0,0 +1,11 @@ +<?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_LoginAsCustomerWebapi"/> +</config> diff --git a/app/code/Magento/LoginAsCustomerWebapi/etc/webapi.xml b/app/code/Magento/LoginAsCustomerWebapi/etc/webapi.xml new file mode 100644 index 0000000000000..6e80fb1725ae4 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/etc/webapi.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd"> + <route url="/V1/login-as-customer/token" method="POST"> + <service class="\Magento\LoginAsCustomerWebapi\Api\CreateCustomerAccessTokenInterface" method="execute"/> + <resources> + <resource ref="Magento_LoginAsCustomerWebapi::login_token"/> + </resources> + </route> +</routes> diff --git a/app/code/Magento/LoginAsCustomerWebapi/registration.php b/app/code/Magento/LoginAsCustomerWebapi/registration.php new file mode 100644 index 0000000000000..f56e57bd7a971 --- /dev/null +++ b/app/code/Magento/LoginAsCustomerWebapi/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_LoginAsCustomerWebapi', + __DIR__ +); diff --git a/app/code/Magento/MediaContent/LICENSE.txt b/app/code/Magento/MediaContent/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/MediaContent/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/MediaContent/LICENSE_AFL.txt b/app/code/Magento/MediaContent/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/MediaContent/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/MediaContent/Model/Content/Config/Converter.php b/app/code/Magento/MediaContent/Model/Content/Config/Converter.php new file mode 100644 index 0000000000000..854d10c8d9812 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/Content/Config/Converter.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model\Content\Config; + +use Magento\Framework\Config\ConverterInterface; + +/** + * Media Content configuration Converter + */ +class Converter implements ConverterInterface +{ + /* + * Search tag name + */ + private const SEARCH_TAG_NAME = 'search'; + + /** + * Patterns tag name + */ + private const PATTERNS_TAG_NAME = 'patterns'; + + /** + * Pattern tag name + */ + private const PATTERN_TAG_NAME = 'pattern'; + + /** + * Convert dom node to array + * + * @param \DOMDocument $source + * @return array + */ + public function convert($source) : array + { + $result = []; + + if (!$source instanceof \DOMDocument) { + throw new \InvalidArgumentException('The source should be instance of DOMDocument'); + } + + foreach ($source->getElementsByTagName(self::SEARCH_TAG_NAME) as $search) { + $result[self::SEARCH_TAG_NAME] = []; + foreach ($search->getElementsByTagName(self::PATTERNS_TAG_NAME) as $patterns) { + $result[self::SEARCH_TAG_NAME][self::PATTERNS_TAG_NAME] = []; + foreach ($patterns->getElementsByTagName(self::PATTERN_TAG_NAME) as $pattern) { + $result[self::SEARCH_TAG_NAME][self::PATTERNS_TAG_NAME] + [$pattern->attributes->getNamedItem('name')->nodeValue] = $pattern->nodeValue; + } + } + } + + return $result; + } +} diff --git a/app/code/Magento/MediaContent/Model/Content/Config/SchemaLocator.php b/app/code/Magento/MediaContent/Model/Content/Config/SchemaLocator.php new file mode 100644 index 0000000000000..76a13b213ce8e --- /dev/null +++ b/app/code/Magento/MediaContent/Model/Content/Config/SchemaLocator.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model\Content\Config; + +use Magento\Framework\Config\SchemaLocatorInterface; +use Magento\Framework\Module\Dir; +use Magento\Framework\Module\Dir\Reader; + +/** + * Media Content configuration schema locator + */ +class SchemaLocator implements SchemaLocatorInterface +{ + /** + * Path to corresponding XSD file with validation rules for both individual and merged configs + * + * @var string + */ + private $schema; + + /** + * @param Reader $moduleReader + */ + public function __construct(Reader $moduleReader) + { + $this->schema = $moduleReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_MediaContentApi') + . '/media_content.xsd'; + } + + /** + * Get path to merged config schema + * + * @return string|null + */ + public function getSchema() + { + return $this->schema; + } + + /** + * Get path to per file validation schema + * + * @return string|null + */ + public function getPerFileSchema() + { + return $this->schema; + } +} diff --git a/app/code/Magento/MediaContent/Model/Content/SearchPatternConfig.php b/app/code/Magento/MediaContent/Model/Content/SearchPatternConfig.php new file mode 100644 index 0000000000000..32c667a3d6069 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/Content/SearchPatternConfig.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model\Content; + +use Magento\Framework\Config\DataInterface; +use Magento\MediaContentApi\Model\SearchPatternConfigInterface; + +/** + * Media content configuration + */ +class SearchPatternConfig implements SearchPatternConfigInterface +{ + private const XML_PATH_SEARCH_PATTERNS = 'search/patterns'; + + /** + * @var DataInterface + */ + private $data; + + /** + * @param DataInterface $data + */ + public function __construct(DataInterface $data) + { + $this->data = $data; + } + + /** + * Retrieve search RegExp patterns for finding media asset paths within content + * + * @return array + */ + public function get(): array + { + return $this->data->get(self::XML_PATH_SEARCH_PATTERNS); + } +} diff --git a/app/code/Magento/MediaContent/Model/ContentAssetLink.php b/app/code/Magento/MediaContent/Model/ContentAssetLink.php new file mode 100644 index 0000000000000..abfafd39c4a23 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/ContentAssetLink.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface; +use Magento\MediaContentApi\Api\Data\ContentAssetLinkExtensionInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; + +/** + * Relation of the media asset to the media content + */ +class ContentAssetLink implements ContentAssetLinkInterface +{ + /** + * @var ContentAssetLinkExtensionInterface|null + */ + private $extensionAttributes; + + /** + * @var ContentIdentityInterface + */ + private $contentIdentity; + + /** + * @var int + */ + private $assetId; + + /** + * ContentAssetLink constructor. + * @param int $assetId + * @param ContentIdentityInterface $contentIdentity + * @param ContentAssetLinkExtensionInterface|null $extensionAttributes + */ + public function __construct( + int $assetId, + ContentIdentityInterface $contentIdentity, + ?ContentAssetLinkExtensionInterface $extensionAttributes = null + ) { + $this->assetId = $assetId; + $this->contentIdentity = $contentIdentity; + $this->extensionAttributes = $extensionAttributes; + } + + /** + * @inheritdoc + */ + public function getAssetId(): int + { + return $this->assetId; + } + + /** + * @inheritdoc + */ + public function getContentId(): ContentIdentityInterface + { + return $this->contentIdentity; + } + + /** + * @inheritdoc + */ + public function getExtensionAttributes(): ?ContentAssetLinkExtensionInterface + { + return $this->extensionAttributes; + } + + /** + * @inheritdoc + */ + public function setExtensionAttributes(?ContentAssetLinkExtensionInterface $extensionAttributes): void + { + $this->extensionAttributes = $extensionAttributes; + } +} diff --git a/app/code/Magento/MediaContent/Model/ContentIdentity.php b/app/code/Magento/MediaContent/Model/ContentIdentity.php new file mode 100644 index 0000000000000..fd60aa8f19430 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/ContentIdentity.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityExtensionInterface; + +/** + * @inheritdoc + */ +class ContentIdentity implements ContentIdentityInterface +{ + private $entityType; + private $entityId; + private $field; + private $extensionAttributes; + + /** + * ContentIdentity constructor. + * @param string $entityType + * @param string $entityId + * @param string $field + * @param ContentIdentityExtensionInterface|null $extensionAttributes + */ + public function __construct( + string $entityType, + string $entityId, + string $field, + ?ContentIdentityExtensionInterface $extensionAttributes = null + ) { + $this->entityType = $entityType; + $this->entityId= $entityId; + $this->field = $field; + $this->extensionAttributes = $extensionAttributes; + } + + /** + * @inheritdoc + */ + public function getEntityType(): string + { + return $this->entityType; + } + + /** + * @inheritdoc + */ + public function getEntityId(): string + { + return $this->entityId; + } + + /** + * @inheritdoc + */ + public function getField(): string + { + return $this->field; + } + + /** + * @inheritdoc + */ + public function getExtensionAttributes(): ?ContentIdentityExtensionInterface + { + return $this->extensionAttributes; + } + + /** + * @inheritdoc + */ + public function setExtensionAttributes(?ContentIdentityExtensionInterface $extensionAttributes): void + { + $this->extensionAttributes = $extensionAttributes; + } +} diff --git a/app/code/Magento/MediaContent/Model/DeleteContentAssetLinks.php b/app/code/Magento/MediaContent/Model/DeleteContentAssetLinks.php new file mode 100644 index 0000000000000..543347c872e73 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/DeleteContentAssetLinks.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface; +use Magento\MediaContentApi\Api\DeleteContentAssetLinksInterface; +use Psr\Log\LoggerInterface; + +/** + * Used to delete links of the media asset to the media content + */ +class DeleteContentAssetLinks implements DeleteContentAssetLinksInterface +{ + private const MEDIA_CONTENT_ASSET_TABLE_NAME = 'media_content_asset'; + private const ASSET_ID = 'asset_id'; + private const ENTITY_TYPE = 'entity_type'; + private const ENTITY_ID = 'entity_id'; + private const FIELD = 'field'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct(ResourceConnection $resourceConnection, LoggerInterface $logger) + { + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * Remove relation between the media asset and the content. I.e media asset no longer part of the content + * + * @param ContentAssetLinkInterface[] $contentAssetLinks + * @throws CouldNotDeleteException + */ + public function execute(array $contentAssetLinks): void + { + try { + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName(self::MEDIA_CONTENT_ASSET_TABLE_NAME); + $whereSql = $this->buildWhereSqlPart($contentAssetLinks); + $connection->delete($tableName, $whereSql); + } catch (\Exception $exception) { + $this->logger->critical($exception); + throw new CouldNotDeleteException( + __('An error occurred at deleting links between the media asset and media content.') + ); + } + } + + /** + * Build sql where condition + * + * @param ContentAssetLinkInterface[] $contentAssetLinks + * @return string + */ + private function buildWhereSqlPart(array $contentAssetLinks): string + { + $connection = $this->resourceConnection->getConnection(); + $condition = []; + foreach ($contentAssetLinks as $contentAssetLink) { + $assetId = $connection->quoteInto(self::ASSET_ID . ' = ?', $contentAssetLink->getAssetId()); + $entityId = $connection->quoteInto( + self::ENTITY_ID . ' = ?', + $contentAssetLink->getContentId()->getEntityId() + ); + $entityType = $connection->quoteInto( + self::ENTITY_TYPE . ' = ?', + $contentAssetLink->getContentId()->getEntityType() + ); + $field = $connection->quoteInto( + self::FIELD . ' = ?', + $contentAssetLink->getContentId()->getField() + ); + $condition[] = '(' . $assetId . ' AND ' . $entityId . ' AND ' . $entityType . ' AND ' . $field . ')'; + } + return implode(' OR ', $condition); + } +} diff --git a/app/code/Magento/MediaContent/Model/DeleteContentAssetLinksByAssetIds.php b/app/code/Magento/MediaContent/Model/DeleteContentAssetLinksByAssetIds.php new file mode 100644 index 0000000000000..68495adb468d7 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/DeleteContentAssetLinksByAssetIds.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\MediaContentApi\Api\DeleteContentAssetLinksByAssetIdsInterface; +use Magento\Framework\Exception\CouldNotDeleteException; +use Psr\Log\LoggerInterface; + +/** + * Delete the relation between media asset and the piece of content. I.e media asset no longer part of the content + */ +class DeleteContentAssetLinksByAssetIds implements DeleteContentAssetLinksByAssetIdsInterface +{ + private const MEDIA_CONTENT_ASSET_TABLE_NAME = 'media_content_asset'; + private const ASSET_ID = 'asset_id'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct(ResourceConnection $resourceConnection, LoggerInterface $logger) + { + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * Delete media content relations by media asset ids + * + * @param array $assetIds + * @throws CouldNotDeleteException + */ + public function execute(array $assetIds): void + { + $commaSeparatedAssetIds = implode(', ', $assetIds); + try { + $this->resourceConnection->getConnection()->delete( + $this->resourceConnection->getTableName(self::MEDIA_CONTENT_ASSET_TABLE_NAME), + [ + self::ASSET_ID . ' IN (?)' => $commaSeparatedAssetIds + ] + ); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $message = __( + 'Could not remove media content relations for assets ids: %ids', + [ + 'ids' => $commaSeparatedAssetIds + ] + ); + throw new CouldNotDeleteException($message, $exception); + } + } +} diff --git a/app/code/Magento/MediaContent/Model/ExtractAssetsFromContent.php b/app/code/Magento/MediaContent/Model/ExtractAssetsFromContent.php new file mode 100644 index 0000000000000..92248ee1debfe --- /dev/null +++ b/app/code/Magento/MediaContent/Model/ExtractAssetsFromContent.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\MediaContentApi\Model\SearchPatternConfigInterface; +use Magento\MediaContentApi\Api\ExtractAssetsFromContentInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Model\Asset\Command\GetByPathInterface; +use Psr\Log\LoggerInterface; + +/** + * Used for extracting media asset list from a media content by the search pattern. + */ +class ExtractAssetsFromContent implements ExtractAssetsFromContentInterface +{ + /** + * @var SearchPatternConfigInterface + */ + private $searchPatternConfig; + + /** + * @var GetByPathInterface + */ + private $getMediaAssetByPath; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param SearchPatternConfigInterface $searchPatternConfig + * @param GetByPathInterface $getMediaAssetByPath + * @param LoggerInterface $logger + */ + public function __construct( + SearchPatternConfigInterface $searchPatternConfig, + GetByPathInterface $getMediaAssetByPath, + LoggerInterface $logger + ) { + $this->searchPatternConfig = $searchPatternConfig; + $this->getMediaAssetByPath = $getMediaAssetByPath; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(string $content): array + { + $matchesArrays = []; + + foreach ($this->searchPatternConfig->get() as $pattern) { + if (empty($pattern)) { + continue; + } + + preg_match_all($pattern, $content, $matches, PREG_PATTERN_ORDER); + + if (!empty($matches[1])) { + $matchesArrays[] = $matches[1]; + } + } + + return $this->getAssetsByPaths(array_unique(array_merge([], ...$matchesArrays))); + } + + /** + * Get media assets by paths array + * + * @param array $paths + * @return AssetInterface[] + */ + private function getAssetsByPaths(array $paths): array + { + $assets = []; + + foreach ($paths as $path) { + $path = htmlspecialchars_decode($path); + $path = trim($path, '"\''); + try { + /** @var AssetInterface $asset */ + $asset = $this->getMediaAssetByPath->execute($this->getPathStartingWithSlash($path)); + $assets[$asset->getId()] = $asset; + } catch (\Exception $exception) { + $this->logger->critical($exception); + } + } + + return $assets; + } + + /** + * Ensure the extracted paths matches the standard format + * + * @param string $path + * @return string + */ + private function getPathStartingWithSlash(string $path): string + { + $normalizedPath = ltrim($path, '/'); + if (strpos($normalizedPath, '/') !== false) { + return $normalizedPath; + } + return '/' . $normalizedPath; + } +} diff --git a/app/code/Magento/MediaContent/Model/GetAssetIdsByContentIdentity.php b/app/code/Magento/MediaContent/Model/GetAssetIdsByContentIdentity.php new file mode 100644 index 0000000000000..55f357d4ff1b2 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/GetAssetIdsByContentIdentity.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\IntegrationException; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; +use Magento\MediaContentApi\Api\GetAssetIdsByContentIdentityInterface; +use Psr\Log\LoggerInterface; + +/** + * Used to return media asset id list which is used in the specified media content + */ +class GetAssetIdsByContentIdentity implements GetAssetIdsByContentIdentityInterface +{ + private const MEDIA_CONTENT_ASSET_TABLE_NAME = 'media_content_asset'; + private const ASSET_ID = 'asset_id'; + private const ENTITY_TYPE = 'entity_type'; + private const ENTITY_ID = 'entity_id'; + private const FIELD = 'field'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * GetAssetsUsedInContent constructor. + * + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct(ResourceConnection $resourceConnection, LoggerInterface $logger) + { + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(ContentIdentityInterface $contentIdentity): array + { + try { + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from( + $this->resourceConnection->getTableName(self::MEDIA_CONTENT_ASSET_TABLE_NAME), + self::ASSET_ID + )->where( + self::ENTITY_TYPE . ' = ?', + $contentIdentity->getEntityType() + )->where( + self::ENTITY_ID . '= ?', + $contentIdentity->getEntityId() + )->where( + self::FIELD . '= ?', + $contentIdentity->getField() + ); + + return array_keys($connection->fetchAssoc($select)); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $message = __('An error occurred at getting asset used in content information.'); + throw new IntegrationException($message); + } + } +} diff --git a/app/code/Magento/MediaContent/Model/GetContentByAssetIds.php b/app/code/Magento/MediaContent/Model/GetContentByAssetIds.php new file mode 100644 index 0000000000000..83397e5f1f90b --- /dev/null +++ b/app/code/Magento/MediaContent/Model/GetContentByAssetIds.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\IntegrationException; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterfaceFactory; +use Magento\MediaContentApi\Api\GetContentByAssetIdsInterface; +use Psr\Log\LoggerInterface; + +/** + * Used to return media asset list for the specified asset. + */ +class GetContentByAssetIds implements GetContentByAssetIdsInterface +{ + private const MEDIA_CONTENT_ASSET_TABLE_NAME = 'media_content_asset'; + private const ASSET_ID = 'asset_id'; + private const ENTITY_TYPE = 'entity_type'; + private const ENTITY_ID = 'entity_id'; + private const FIELD = 'field'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var ContentIdentityInterfaceFactory + */ + private $factory; + + /** + * @param ContentIdentityInterfaceFactory $factory + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct( + ContentIdentityInterfaceFactory $factory, + ResourceConnection $resourceConnection, + LoggerInterface $logger + ) { + $this->factory = $factory; + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(array $assetIds): array + { + try { + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->distinct() + ->from( + $this->resourceConnection->getTableName(self::MEDIA_CONTENT_ASSET_TABLE_NAME), + ['entityType' => self::ENTITY_TYPE, 'entityId' => self::ENTITY_ID, self::FIELD] + ) + ->where(self::ASSET_ID . ' IN (?)', $assetIds); + + $contentIdentities = []; + foreach ($connection->fetchAll($select) as $contentIdentityData) { + $contentIdentities[] = $this->factory->create($contentIdentityData); + } + return $contentIdentities; + } catch (\Exception $exception) { + $this->logger->critical($exception); + throw new IntegrationException( + __('An error occurred at getting media asset to content relation by media asset id.') + ); + } + } +} diff --git a/app/code/Magento/MediaContent/Model/SaveContentAssetLinks.php b/app/code/Magento/MediaContent/Model/SaveContentAssetLinks.php new file mode 100644 index 0000000000000..3d4ed2c0e4b80 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/SaveContentAssetLinks.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\MediaContentApi\Api\SaveContentAssetLinksInterface; +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface; +use Psr\Log\LoggerInterface; + +/** + * Used for saving relation between the media asset and media content where the media asset is used + */ +class SaveContentAssetLinks implements SaveContentAssetLinksInterface +{ + private const MEDIA_CONTENT_ASSET_TABLE_NAME = 'media_content_asset'; + private const ASSET_ID = 'asset_id'; + private const ENTITY_TYPE = 'entity_type'; + private const ENTITY_ID = 'entity_id'; + private const FIELD = 'field'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct(ResourceConnection $resourceConnection, LoggerInterface $logger) + { + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * Save a media asset to content link. + * + * @param ContentAssetLinkInterface[] $contentAssetLinks + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function execute(array $contentAssetLinks): void + { + try { + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName(self::MEDIA_CONTENT_ASSET_TABLE_NAME); + $data = []; + foreach ($contentAssetLinks as $contentAssetLink) { + $data[] = [ + self::ASSET_ID => $contentAssetLink->getAssetId(), + self::ENTITY_TYPE => $contentAssetLink->getContentId()->getEntityType(), + self::ENTITY_ID => $contentAssetLink->getContentId()->getEntityId(), + self::FIELD => $contentAssetLink->getContentId()->getField() + ]; + } + $connection->insertMultiple($tableName, $data); + } catch (\Exception $exception) { + $this->logger->critical($exception); + throw new CouldNotSaveException( + __('An error occurred while saving relation between media asset and media content.'), + $exception + ); + } + } +} diff --git a/app/code/Magento/MediaContent/Model/UpdateContentAssetLinks.php b/app/code/Magento/MediaContent/Model/UpdateContentAssetLinks.php new file mode 100644 index 0000000000000..d71978d3ca092 --- /dev/null +++ b/app/code/Magento/MediaContent/Model/UpdateContentAssetLinks.php @@ -0,0 +1,131 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\IntegrationException; +use Magento\MediaContentApi\Api\SaveContentAssetLinksInterface; +use Magento\MediaContentApi\Api\DeleteContentAssetLinksInterface; +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterfaceFactory; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; +use Magento\MediaContentApi\Api\ExtractAssetsFromContentInterface; +use Magento\MediaContentApi\Api\GetAssetIdsByContentIdentityInterface; +use Magento\MediaContentApi\Api\UpdateContentAssetLinksInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Psr\Log\LoggerInterface; + +/** + * Process relation managing between media asset and content: create or delete link if exists. + */ +class UpdateContentAssetLinks implements UpdateContentAssetLinksInterface +{ + private const ASSET_ID = 'assetId'; + private const CONTENT_IDENTITY = 'contentIdentity'; + + /** + * @var ExtractAssetsFromContentInterface + */ + private $extractAssetFromContent; + + /** + * @var SaveContentAssetLinksInterface + */ + private $saveContentAssetLinks; + + /** + * @var DeleteContentAssetLinksInterface + */ + private $deleteContentAssetLinks; + + /** + * @var ContentAssetLinkInterfaceFactory + */ + private $contentAssetLinkFactory; + + /** + * @var GetAssetIdsByContentIdentityInterface + */ + private $getAssetIdsByContentIdentity; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param ExtractAssetsFromContentInterface $extractAssetFromContent + * @param SaveContentAssetLinksInterface $saveContentAssetLinks + * @param DeleteContentAssetLinksInterface $deleteContentAssetLinks + * @param ContentAssetLinkInterfaceFactory $contentAssetLinkFactory + * @param GetAssetIdsByContentIdentityInterface $getAssetIdsByContentIdentity + * @param LoggerInterface $logger + */ + public function __construct( + ExtractAssetsFromContentInterface $extractAssetFromContent, + SaveContentAssetLinksInterface $saveContentAssetLinks, + DeleteContentAssetLinksInterface $deleteContentAssetLinks, + ContentAssetLinkInterfaceFactory $contentAssetLinkFactory, + GetAssetIdsByContentIdentityInterface $getAssetIdsByContentIdentity, + LoggerInterface $logger + ) { + $this->extractAssetFromContent = $extractAssetFromContent; + $this->saveContentAssetLinks = $saveContentAssetLinks; + $this->deleteContentAssetLinks = $deleteContentAssetLinks; + $this->contentAssetLinkFactory = $contentAssetLinkFactory; + $this->getAssetIdsByContentIdentity = $getAssetIdsByContentIdentity; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(ContentIdentityInterface $contentIdentity, string $data): void + { + try { + $this->updateRelation($contentIdentity, $data); + } catch (\Exception $exception) { + $this->logger->critical($exception); + } + } + + /** + * Find out which relations are obsolete and which are new and update them + * + * @param ContentIdentityInterface $contentIdentity + * @param string $data + * @throws CouldNotDeleteException + * @throws CouldNotSaveException + * @throws IntegrationException + */ + private function updateRelation(ContentIdentityInterface $contentIdentity, string $data) + { + $existingAssetIds = $this->getAssetIdsByContentIdentity->execute($contentIdentity); + $currentAssets = $this->extractAssetFromContent->execute($data); + /** @var AssetInterface $asset */ + foreach ($currentAssets as $asset) { + if (!in_array($asset->getId(), $existingAssetIds)) { + $contentAssetLink = $this->contentAssetLinkFactory->create([ + self::ASSET_ID => $asset->getId(), + self::CONTENT_IDENTITY => $contentIdentity + ]); + $this->saveContentAssetLinks->execute([$contentAssetLink]); + } + } + + foreach ($existingAssetIds as $assetId) { + if (!isset($currentAssets[$assetId])) { + $contentAssetLink = $this->contentAssetLinkFactory->create([ + self::ASSET_ID => $assetId, + self::CONTENT_IDENTITY => $contentIdentity + ]); + $this->deleteContentAssetLinks->execute([$contentAssetLink]); + } + } + } +} diff --git a/app/code/Magento/MediaContent/Plugin/MediaGalleryAssetDeleteByDirectoryPath.php b/app/code/Magento/MediaContent/Plugin/MediaGalleryAssetDeleteByDirectoryPath.php new file mode 100644 index 0000000000000..c260c877cdb4a --- /dev/null +++ b/app/code/Magento/MediaContent/Plugin/MediaGalleryAssetDeleteByDirectoryPath.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Plugin; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\MediaContentApi\Api\DeleteContentAssetLinksByAssetIdsInterface; +use Magento\MediaGalleryApi\Api\DeleteDirectoriesByPathsInterface; +use Psr\Log\LoggerInterface; + +/** + * Remove media content record after media gallery asset removal. + */ +class MediaGalleryAssetDeleteByDirectoryPath +{ + private const TABLE_MEDIA_GALLERY_ASSET = 'media_gallery_asset'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var DeleteContentAssetLinksByAssetIdsInterface + */ + private $deleteContentAssetLinksByAssetIds; + + /** + * @param DeleteContentAssetLinksByAssetIdsInterface $deleteContentAssetLinksByAssetIds + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct( + DeleteContentAssetLinksByAssetIdsInterface $deleteContentAssetLinksByAssetIds, + ResourceConnection $resourceConnection, + LoggerInterface $logger + ) { + $this->deleteContentAssetLinksByAssetIds = $deleteContentAssetLinksByAssetIds; + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * Around plugin on execute method + * + * @param DeleteDirectoriesByPathsInterface $subject + * @param \Closure $proceed + * @param array $paths + * @throws CouldNotDeleteException + * @return void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundExecute( + DeleteDirectoriesByPathsInterface $subject, + \Closure $proceed, + array $paths + ) : void { + $assetIdsArrays =[]; + foreach ($paths as $path) { + $assetIdsArrays[] = $this->getAssetIdsByDirectoryPath($path); + } + + $assetIds = array_unique(array_merge([], ...$assetIdsArrays)); + + $proceed($paths); + + $this->deleteContentAssetLinksByAssetIds->execute($assetIds); + } + + /** + * Get ids of media assets by directory path + * + * @param string $path + * @return int[] + */ + private function getAssetIdsByDirectoryPath(string $path): array + { + /** @var AdapterInterface $connection */ + $connection = $this->resourceConnection->getConnection(); + $galleryAssetTableName = $this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET); + + $select = $connection->select(); + $select->from($galleryAssetTableName, ['id']); + $select->where('path LIKE ?', $path . '%'); + return $connection->fetchCol($select); + } +} diff --git a/app/code/Magento/MediaContent/Plugin/MediaGalleryAssetDeleteByPath.php b/app/code/Magento/MediaContent/Plugin/MediaGalleryAssetDeleteByPath.php new file mode 100644 index 0000000000000..83df5c13f36c8 --- /dev/null +++ b/app/code/Magento/MediaContent/Plugin/MediaGalleryAssetDeleteByPath.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Plugin; + +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\Framework\Exception\LocalizedException; +use Magento\MediaContentApi\Api\DeleteContentAssetLinksByAssetIdsInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Psr\Log\LoggerInterface; + +/** + * Remove media content record after media gallery asset removal. + */ +class MediaGalleryAssetDeleteByPath +{ + /** + * @var GetAssetsByPathsInterface + */ + private $getByPaths; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var DeleteContentAssetLinksByAssetIdsInterface + */ + private $deleteContentAssetLinksByAssetIds; + + /** + * @param DeleteContentAssetLinksByAssetIdsInterface $deleteContentAssetLinksByAssetIds + * @param GetAssetsByPathsInterface $getByPath + * @param LoggerInterface $logger + */ + public function __construct( + DeleteContentAssetLinksByAssetIdsInterface $deleteContentAssetLinksByAssetIds, + GetAssetsByPathsInterface $getByPath, + LoggerInterface $logger + ) { + $this->deleteContentAssetLinksByAssetIds = $deleteContentAssetLinksByAssetIds; + $this->getByPaths = $getByPath; + $this->logger = $logger; + } + + /** + * Around plugin on execute method + * + * @param DeleteAssetsByPathsInterface $subject + * @param \Closure $proceed + * @param array $paths + * @throws CouldNotDeleteException + * @throws LocalizedException + * @return void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundExecute( + DeleteAssetsByPathsInterface $subject, + \Closure $proceed, + array $paths + ) : void { + $assets = $this->getByPaths->execute($paths); + + $proceed($paths); + + $assetIds = array_map( + function (AssetInterface $asset) { + return $asset->getId(); + }, + $assets + ); + + $this->deleteContentAssetLinksByAssetIds->execute($assetIds); + } +} diff --git a/app/code/Magento/MediaContent/README.md b/app/code/Magento/MediaContent/README.md new file mode 100644 index 0000000000000..b5813540acef7 --- /dev/null +++ b/app/code/Magento/MediaContent/README.md @@ -0,0 +1,13 @@ +# Magento_MediaContent module + +The Magento_MediaContent module provides implementations for managing relations between content and media files used in that content. + +## Extensibility + +Extension developers can interact with the Magento_MediaContent module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_MediaContent module. + +## Additional information + +For information about significant changes in patch releases, see [2.3.x Release information](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html). diff --git a/app/code/Magento/MediaContent/composer.json b/app/code/Magento/MediaContent/composer.json new file mode 100644 index 0000000000000..6734c1a300810 --- /dev/null +++ b/app/code/Magento/MediaContent/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-media-content", + "description": "Magento module provides the implementation for managing relations between content and media files used in that content", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-media-content-api": "*", + "magento/module-media-gallery-api": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\MediaContent\\": "" + } + } +} diff --git a/app/code/Magento/MediaContent/etc/db_schema.xml b/app/code/Magento/MediaContent/etc/db_schema.xml new file mode 100644 index 0000000000000..7e45e29cbb434 --- /dev/null +++ b/app/code/Magento/MediaContent/etc/db_schema.xml @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="media_content_asset" resource="default" engine="innodb" comment="Relation between media content and media asset"> + <column xsi:type="int" padding="10" name="asset_id" unsigned="true" nullable="false" comment="Entity ID"/> + <column xsi:type="varchar" length="255" name="entity_type" nullable="false" comment="Content type"/> + <column xsi:type="varchar" length="255" name="entity_id" nullable="false" comment="Content entity id"/> + <column xsi:type="varchar" length="255" name="field" nullable="false" comment="Content field"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="entity_type"/> + <column name="entity_id"/> + <column name="field"/> + <column name="asset_id"/> + </constraint> + <index referenceId="MEDIA_CONTENT_ASSET_ASSET_ID" indexType="btree"> + <column name="asset_id"/> + </index> + </table> +</schema> diff --git a/app/code/Magento/MediaContent/etc/db_schema_whitelist.json b/app/code/Magento/MediaContent/etc/db_schema_whitelist.json new file mode 100644 index 0000000000000..e33768b5e1ab5 --- /dev/null +++ b/app/code/Magento/MediaContent/etc/db_schema_whitelist.json @@ -0,0 +1,16 @@ +{ + "media_content_asset": { + "column": { + "entity_type": true, + "entity_id": true, + "field": true, + "asset_id": true + }, + "index": { + "MEDIA_CONTENT_ASSET_ASSET_ID": true + }, + "constraint": { + "PRIMARY": true + } + } +} diff --git a/app/code/Magento/MediaContent/etc/di.xml b/app/code/Magento/MediaContent/etc/di.xml new file mode 100644 index 0000000000000..f2a9459447001 --- /dev/null +++ b/app/code/Magento/MediaContent/etc/di.xml @@ -0,0 +1,46 @@ +<?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"> + <preference for="Magento\MediaContentApi\Api\DeleteContentAssetLinksByAssetIdsInterface" type="Magento\MediaContent\Model\DeleteContentAssetLinksByAssetIds"/> + <preference for="Magento\MediaContentApi\Api\GetAssetIdsByContentIdentityInterface" type="Magento\MediaContent\Model\GetAssetIdsByContentIdentity"/> + <preference for="Magento\MediaContentApi\Api\GetContentByAssetIdsInterface" type="Magento\MediaContent\Model\GetContentByAssetIds"/> + <preference for="Magento\MediaContentApi\Api\ExtractAssetsFromContentInterface" type="Magento\MediaContent\Model\ExtractAssetsFromContent"/> + <preference for="Magento\MediaContentApi\Api\UpdateContentAssetLinksInterface" type="Magento\MediaContent\Model\UpdateContentAssetLinks"/> + <preference for="Magento\MediaContentApi\Api\DeleteContentAssetLinksInterface" type="Magento\MediaContent\Model\DeleteContentAssetLinks"/> + <preference for="Magento\MediaContentApi\Api\SaveContentAssetLinksInterface" type="Magento\MediaContent\Model\SaveContentAssetLinks"/> + <preference for="Magento\MediaContentApi\Api\Data\ContentIdentityInterface" type="Magento\MediaContent\Model\ContentIdentity"/> + <preference for="Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface" type="Magento\MediaContent\Model\ContentAssetLink"/> + <preference for="Magento\MediaContentApi\Model\SearchPatternConfigInterface" type="Magento\MediaContent\Model\Content\SearchPatternConfig"/> + <type name="Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface"> + <plugin name="remove_media_content_after_asset_is_removed_by_path" type="Magento\MediaContent\Plugin\MediaGalleryAssetDeleteByPath" /> + </type> + <type name="Magento\MediaGalleryApi\Api\DeleteDirectoriesByPathsInterface"> + <plugin name="remove_media_content_after_asset_is_removed_by_directory_path" type="Magento\MediaContent\Plugin\MediaGalleryAssetDeleteByDirectoryPath" /> + </type> + <virtualType name="Magento\MediaContent\Model\Content\Config\Reader" type="Magento\Framework\Config\Reader\Filesystem"> + <arguments> + <argument name="fileName" xsi:type="string">media_content.xml</argument> + <argument name="converter" xsi:type="object">Magento\MediaContent\Model\Content\Config\Converter</argument> + <argument name="schemaLocator" xsi:type="object">Magento\MediaContent\Model\Content\Config\SchemaLocator</argument> + <argument name="idAttributes" xsi:type="array"> + <item name="/config/search/patterns/pattern" xsi:type="string">name</item> + </argument> + </arguments> + </virtualType> + <virtualType name="Magento\MediaContent\Model\Content\Config\Data" type="Magento\Framework\Config\Data"> + <arguments> + <argument name="reader" xsi:type="object">Magento\MediaContent\Model\Content\Config\Reader</argument> + <argument name="cacheId" xsi:type="string">Media_Content_Patterns_CacheId</argument> + </arguments> + </virtualType> + <type name="Magento\MediaContent\Model\Content\SearchPatternConfig"> + <arguments> + <argument name="data" xsi:type="object">Magento\MediaContent\Model\Content\Config\Data</argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/MediaContent/etc/module.xml b/app/code/Magento/MediaContent/etc/module.xml new file mode 100644 index 0000000000000..58cff2aabf3a6 --- /dev/null +++ b/app/code/Magento/MediaContent/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_MediaContent"/> +</config> diff --git a/app/code/Magento/MediaContent/registration.php b/app/code/Magento/MediaContent/registration.php new file mode 100644 index 0000000000000..d6776f29f46df --- /dev/null +++ b/app/code/Magento/MediaContent/registration.php @@ -0,0 +1,10 @@ +<?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_MediaContent', __DIR__); diff --git a/app/code/Magento/MediaContentApi/Api/Data/ContentAssetLinkInterface.php b/app/code/Magento/MediaContentApi/Api/Data/ContentAssetLinkInterface.php new file mode 100644 index 0000000000000..5ff490655d464 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/Data/ContentAssetLinkInterface.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; +use Magento\MediaContentApi\Api\Data\ContentAssetLinkExtensionInterface; + +/** + * Data interface representing the identificator of content. I.e. short description field of product entity with id 42 + * @api + */ +interface ContentAssetLinkInterface extends ExtensibleDataInterface +{ + /** + * Return the object that represent content identity + * + * @return ContentIdentityInterface + */ + public function getContentId(): ContentIdentityInterface; + + /** + * Array of assets related to the content entity + * + * @return int + */ + public function getAssetId(): int; + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\MediaContentApi\Api\Data\ContentAssetLinkExtensionInterface|null + */ + public function getExtensionAttributes(): ?ContentAssetLinkExtensionInterface; + + /** + * Set extension attributes + * + * @param \Magento\MediaContentApi\Api\Data\ContentAssetLinkExtensionInterface|null $extensionAttributes + * @return void + */ + public function setExtensionAttributes(?ContentAssetLinkExtensionInterface $extensionAttributes): void; +} diff --git a/app/code/Magento/MediaContentApi/Api/Data/ContentIdentityInterface.php b/app/code/Magento/MediaContentApi/Api/Data/ContentIdentityInterface.php new file mode 100644 index 0000000000000..f1b701fe9d964 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/Data/ContentIdentityInterface.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityExtensionInterface; + +/** + * Data interface representing the identificator of content. I.e. short description field of product entity with id 42 + * @api + */ +interface ContentIdentityInterface extends ExtensibleDataInterface +{ + /** + * Type of entity that can have a content with media. I.e. catalog_product or cms_page + * + * @return string + */ + public function getEntityType(): string; + + /** + * Id of the entity containing content with media + * + * @return string + */ + public function getEntityId(): string; + + /** + * Field of the entity where the content can be stored. I.e. short_description for product + * + * @return string + */ + public function getField(): string; + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\MediaContentApi\Api\Data\ContentIdentityExtensionInterface|null + */ + public function getExtensionAttributes(): ?ContentIdentityExtensionInterface; + + /** + * Set extension attributes + * + * @param \Magento\MediaContentApi\Api\Data\ContentIdentityExtensionInterface|null $extensionAttributes + * @return void + */ + public function setExtensionAttributes(?ContentIdentityExtensionInterface $extensionAttributes): void; +} diff --git a/app/code/Magento/MediaContentApi/Api/DeleteContentAssetLinksByAssetIdsInterface.php b/app/code/Magento/MediaContentApi/Api/DeleteContentAssetLinksByAssetIdsInterface.php new file mode 100644 index 0000000000000..8997e4b6e7e77 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/DeleteContentAssetLinksByAssetIdsInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api; + +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface; + +/** + * Delete the relation between media asset and the piece of content. I.e media asset no longer part of the content + * @api + */ +interface DeleteContentAssetLinksByAssetIdsInterface +{ + /** + * Delete relation between the media asset and the content. I.e media asset no longer part of the content + * + * @param int[] $assetIds + * @throws \Magento\Framework\Exception\CouldNotDeleteException + */ + public function execute(array $assetIds): void; +} diff --git a/app/code/Magento/MediaContentApi/Api/DeleteContentAssetLinksInterface.php b/app/code/Magento/MediaContentApi/Api/DeleteContentAssetLinksInterface.php new file mode 100644 index 0000000000000..9c50793f51303 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/DeleteContentAssetLinksInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api; + +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface; + +/** + * Remove the relation between media asset and the piece of content. I.e media asset no longer part of the content + * @api + */ +interface DeleteContentAssetLinksInterface +{ + /** + * Remove relation between the media asset and the content. I.e media asset no longer part of the content + * + * @param ContentAssetLinkInterface[] $contentAssetLinks + * @throws \Magento\Framework\Exception\CouldNotDeleteException + */ + public function execute(array $contentAssetLinks): void; +} diff --git a/app/code/Magento/MediaContentApi/Api/ExtractAssetsFromContentInterface.php b/app/code/Magento/MediaContentApi/Api/ExtractAssetsFromContentInterface.php new file mode 100644 index 0000000000000..4f95571f30ffd --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/ExtractAssetsFromContentInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api; + +use Magento\MediaGalleryApi\Api\Data\AssetInterface; + +/** + * Parse the content string for references to media assets and return the list of identified media assets + * @api + */ +interface ExtractAssetsFromContentInterface +{ + /** + * Parse the content string for references to media assets and return the list of identified media assets + * + * @param string $content + * @return AssetInterface[] + */ + public function execute(string $content): array; +} diff --git a/app/code/Magento/MediaContentApi/Api/GetAssetIdsByContentIdentityInterface.php b/app/code/Magento/MediaContentApi/Api/GetAssetIdsByContentIdentityInterface.php new file mode 100644 index 0000000000000..4316a0d6ee33d --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/GetAssetIdsByContentIdentityInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api; + +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; + +/** + * Get media asset ids that are used in the piece of content identified by the specified content identity + * @api + */ +interface GetAssetIdsByContentIdentityInterface +{ + /** + * Get media asset ids that are used in the piece of content identified by the specified content identity + * + * @param ContentIdentityInterface $contentIdentity + * @return int[] + * @throws \Magento\Framework\Exception\IntegrationException + */ + public function execute(ContentIdentityInterface $contentIdentity): array; +} diff --git a/app/code/Magento/MediaContentApi/Api/GetContentByAssetIdsInterface.php b/app/code/Magento/MediaContentApi/Api/GetContentByAssetIdsInterface.php new file mode 100644 index 0000000000000..cb117545c257e --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/GetContentByAssetIdsInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api; + +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; + +/** + * Get list of content identifiers for pieces of content that include the specified media asset + * @api + */ +interface GetContentByAssetIdsInterface +{ + /** + * Get list of content identifiers for pieces of content that include the specified media asset + * + * @param int[] $assetIds + * @return ContentIdentityInterface[] + * @throws \Magento\Framework\Exception\IntegrationException + */ + public function execute(array $assetIds): array; +} diff --git a/app/code/Magento/MediaContentApi/Api/SaveContentAssetLinksInterface.php b/app/code/Magento/MediaContentApi/Api/SaveContentAssetLinksInterface.php new file mode 100644 index 0000000000000..1c86953ce6f84 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/SaveContentAssetLinksInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api; + +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface; + +/** + * Save a media asset to content relation. + * @api + */ +interface SaveContentAssetLinksInterface +{ + /** + * Save a media asset to content relation. Should be executed when media assets is added to the content + * + * @param ContentAssetLinkInterface[] $contentAssetLinks + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function execute(array $contentAssetLinks): void; +} diff --git a/app/code/Magento/MediaContentApi/Api/UpdateContentAssetLinksInterface.php b/app/code/Magento/MediaContentApi/Api/UpdateContentAssetLinksInterface.php new file mode 100644 index 0000000000000..c552e8bb2149b --- /dev/null +++ b/app/code/Magento/MediaContentApi/Api/UpdateContentAssetLinksInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentApi\Api; + +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; + +/** + * Update the media assets to content relations. Assign new media assets and unassign media assets no longer used + */ +interface UpdateContentAssetLinksInterface +{ + /** + * Update the media assets to content relations. Assign new media assets and unassign media assets no longer used + * + * @param ContentIdentityInterface $contentIdentity + * @param string $content + */ + public function execute(ContentIdentityInterface $contentIdentity, string $content): void; +} diff --git a/app/code/Magento/MediaContentApi/LICENSE.txt b/app/code/Magento/MediaContentApi/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/MediaContentApi/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/MediaContentApi/LICENSE_AFL.txt b/app/code/Magento/MediaContentApi/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/MediaContentApi/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/MediaContentApi/Model/Composite/GetEntityContents.php b/app/code/Magento/MediaContentApi/Model/Composite/GetEntityContents.php new file mode 100644 index 0000000000000..a53d61ae61ad3 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Model/Composite/GetEntityContents.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentApi\Model\Composite; + +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; +use Magento\MediaContentApi\Model\GetEntityContentsInterface; + +/** + * Get concatenated content for all store views + */ +class GetEntityContents implements GetEntityContentsInterface +{ + /** + * @var GetEntityContentsInterface[] + */ + private $items; + + /** + * @param GetEntityContentsInterface[] $items + */ + public function __construct( + $items = [] + ) { + foreach ($items as $item) { + if (!$item instanceof GetEntityContentsInterface) { + throw new \InvalidArgumentException( + __('GetContent items must implement %1.', GetEntityContentsInterface::class) + ); + } + } + + $this->items = $items; + } + + /** + * Get concatenated content for the content identity + * + * @param ContentIdentityInterface $contentIdentity + * @return string[] + */ + public function execute(ContentIdentityInterface $contentIdentity): array + { + if (isset($this->items[$contentIdentity->getEntityType()])) { + return $this->items[$contentIdentity->getEntityType()]->execute($contentIdentity); + } + return []; + } +} diff --git a/app/code/Magento/MediaContentApi/Model/GetEntityContentsInterface.php b/app/code/Magento/MediaContentApi/Model/GetEntityContentsInterface.php new file mode 100644 index 0000000000000..c58d543a597b7 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Model/GetEntityContentsInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentApi\Model; + +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; + +/** + * Get Entity Contents. + * @api + */ +interface GetEntityContentsInterface +{ + /** + * Get concatenated content by the content identity + * + * @param ContentIdentityInterface $contentIdentity + * @return string[] + */ + public function execute(ContentIdentityInterface $contentIdentity): array; +} diff --git a/app/code/Magento/MediaContentApi/Model/SearchPatternConfigInterface.php b/app/code/Magento/MediaContentApi/Model/SearchPatternConfigInterface.php new file mode 100644 index 0000000000000..3900dc53284c7 --- /dev/null +++ b/app/code/Magento/MediaContentApi/Model/SearchPatternConfigInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentApi\Model; + +/** + * Interface for Media content Config. + */ +interface SearchPatternConfigInterface +{ + /** + * Retrieve search RegExp patterns for finding media asset paths within content + * + * @return array + */ + public function get(): array; +} diff --git a/app/code/Magento/MediaContentApi/README.md b/app/code/Magento/MediaContentApi/README.md new file mode 100644 index 0000000000000..87e0d7524f59b --- /dev/null +++ b/app/code/Magento/MediaContentApi/README.md @@ -0,0 +1,13 @@ +# Magento_MediaContentApi module + +The Magento_MediaContentApi module provides interfaces for managing relations between content and media files used in that content. + +## Extensibility + +Extension developers can interact with the Magento_MediaContent module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_MediaContent module. + +## Additional information + +For information about significant changes in patch releases, see [2.3.x Release information](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html). diff --git a/app/code/Magento/MediaContentApi/composer.json b/app/code/Magento/MediaContentApi/composer.json new file mode 100644 index 0000000000000..f8c27093280f6 --- /dev/null +++ b/app/code/Magento/MediaContentApi/composer.json @@ -0,0 +1,22 @@ +{ + "name": "magento/module-media-content-api", + "description": "Magento module provides the API interfaces for managing relations between content and media files used in that content", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/module-media-gallery-api": "*", + "magento/framework": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\MediaContentApi\\": "" + } + } +} diff --git a/app/code/Magento/MediaContentApi/etc/di.xml b/app/code/Magento/MediaContentApi/etc/di.xml new file mode 100644 index 0000000000000..4d4c52e61fb48 --- /dev/null +++ b/app/code/Magento/MediaContentApi/etc/di.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:ObjectManager/etc/config.xsd"> + <preference for="Magento\MediaContentApi\Model\GetEntityContentsInterface" type="Magento\MediaContentApi\Model\Composite\GetEntityContents"/> +</config> diff --git a/app/code/Magento/MediaContentApi/etc/media_content.xsd b/app/code/Magento/MediaContentApi/etc/media_content.xsd new file mode 100644 index 0000000000000..bcc7eb1105072 --- /dev/null +++ b/app/code/Magento/MediaContentApi/etc/media_content.xsd @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="config" type="configType"> + </xs:element> + + <xs:complexType name="configType"> + <xs:sequence> + <xs:element type="searchType" name="search" maxOccurs="unbounded" minOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="searchType"> + <xs:annotation> + <xs:documentation> + Search used for find assets in content + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element type="patternsType" name="patterns" maxOccurs="unbounded" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="patternsType"> + <xs:annotation> + <xs:documentation> + List of RegExp patterns that used for find assets + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element type="patternType" name="pattern" maxOccurs="unbounded" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="patternType"> + <xs:simpleContent> + <xs:annotation> + <xs:documentation> + RegExp pattern that used for find assets + </xs:documentation> + </xs:annotation> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="required"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> +</xs:schema> diff --git a/app/code/Magento/MediaContentApi/etc/module.xml b/app/code/Magento/MediaContentApi/etc/module.xml new file mode 100644 index 0000000000000..3aa553170fe2a --- /dev/null +++ b/app/code/Magento/MediaContentApi/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_MediaContentApi" /> +</config> diff --git a/app/code/Magento/MediaContentApi/registration.php b/app/code/Magento/MediaContentApi/registration.php new file mode 100644 index 0000000000000..fb3faefa2734f --- /dev/null +++ b/app/code/Magento/MediaContentApi/registration.php @@ -0,0 +1,10 @@ +<?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_MediaContentApi', __DIR__); diff --git a/app/code/Magento/MediaContentCatalog/LICENSE.txt b/app/code/Magento/MediaContentCatalog/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/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/MediaContentCatalog/LICENSE_AFL.txt b/app/code/Magento/MediaContentCatalog/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/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/MediaContentCatalog/Model/ResourceModel/GetEntityContent.php b/app/code/Magento/MediaContentCatalog/Model/ResourceModel/GetEntityContent.php new file mode 100644 index 0000000000000..c3766484ce4f1 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/Model/ResourceModel/GetEntityContent.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentCatalog\Model\ResourceModel; + +use Magento\Catalog\Model\ResourceModel\Product; +use Magento\Framework\App\ResourceConnection; +use Magento\MediaContentApi\Model\GetEntityContentsInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; +use Magento\Eav\Model\Config; + +/** + * Get concatenated content for all store views + */ +class GetEntityContent implements GetEntityContentsInterface +{ + /** + * @var Config + */ + private $config; + + /** + * @var Product + */ + private $productResource; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param Config $config + * @param ResourceConnection $resourceConnection + * @param Product $productResource + */ + public function __construct( + Config $config, + ResourceConnection $resourceConnection, + Product $productResource + ) { + $this->config = $config; + $this->productResource = $productResource; + $this->resourceConnection = $resourceConnection; + } + + /** + * Get product content for all store views + * + * @param ContentIdentityInterface $contentIdentity + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(ContentIdentityInterface $contentIdentity): array + { + $attribute = $this->config->getAttribute($contentIdentity->getEntityType(), $contentIdentity->getField()); + $connection = $this->resourceConnection->getConnection(); + + $select = $connection->select()->from( + ['abt' => $attribute->getBackendTable()], + 'abt.value' + )->where( + $connection->quoteIdentifier('abt.attribute_id') . ' = ?', + (int) $attribute->getAttributeId() + )->where( + $connection->quoteIdentifier('abt.' . $attribute->getEntityIdField()) . ' = ?', + $contentIdentity->getEntityId() + )->distinct(true); + + return $connection->fetchCol($select); + } +} diff --git a/app/code/Magento/MediaContentCatalog/Observer/Category.php b/app/code/Magento/MediaContentCatalog/Observer/Category.php new file mode 100644 index 0000000000000..5c2deeab258df --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/Observer/Category.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentCatalog\Observer; + +use Magento\Catalog\Model\Category as CatalogCategory; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\MediaContentApi\Api\UpdateContentAssetLinksInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterfaceFactory; +use Magento\MediaContentApi\Model\GetEntityContentsInterface; + +/** + * Observe the catalog_category_save_after event and run processing relation between category content and media asset. + */ +class Category implements ObserverInterface +{ + private const CONTENT_TYPE = 'catalog_category'; + private const TYPE = 'entityType'; + private const ENTITY_ID = 'entityId'; + private const FIELD = 'field'; + + /** + * @var UpdateContentAssetLinksInterface + */ + private $updateContentAssetLinks; + + /** + * @var array + */ + private $fields; + + /** + * @var ContentIdentityInterfaceFactory + */ + private $contentIdentityFactory; + + /** + * @var GetEntityContentsInterface + */ + private $getContent; + + /** + * Create links for category content + * + * @param ContentIdentityInterfaceFactory $contentIdentityFactory + * @param GetEntityContentsInterface $getContent + * @param UpdateContentAssetLinksInterface $updateContentAssetLinks + * @param array $fields + */ + public function __construct( + ContentIdentityInterfaceFactory $contentIdentityFactory, + GetEntityContentsInterface $getContent, + UpdateContentAssetLinksInterface $updateContentAssetLinks, + array $fields + ) { + $this->contentIdentityFactory = $contentIdentityFactory; + $this->getContent = $getContent; + $this->updateContentAssetLinks = $updateContentAssetLinks; + $this->fields = $fields; + } + + /** + * Retrieve the saved category and pass it to the model processor to save content - asset relations + * + * @param Observer $observer + * @throws \Exception + */ + public function execute(Observer $observer): void + { + $model = $observer->getEvent()->getData('category'); + + if ($model instanceof CatalogCategory) { + foreach ($this->fields as $field) { + if (!$model->dataHasChangedFor($field)) { + continue; + } + $contentIdentity = $this->contentIdentityFactory->create( + [ + self::TYPE => self::CONTENT_TYPE, + self::FIELD => $field, + self::ENTITY_ID => (string) $model->getEntityId(), + ] + ); + $concatenatedContent = implode(PHP_EOL, $this->getContent->execute($contentIdentity)); + $this->updateContentAssetLinks->execute($contentIdentity, $concatenatedContent); + } + } + } +} diff --git a/app/code/Magento/MediaContentCatalog/Observer/Product.php b/app/code/Magento/MediaContentCatalog/Observer/Product.php new file mode 100644 index 0000000000000..306bcc0b466c2 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/Observer/Product.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentCatalog\Observer; + +use Magento\Catalog\Model\Product as CatalogProduct; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\MediaContentApi\Api\UpdateContentAssetLinksInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterfaceFactory; +use Magento\MediaContentApi\Model\GetEntityContentsInterface; + +/** + * Observe the catalog_product_save_after event and run processing relation between product content and media asset + */ +class Product implements ObserverInterface +{ + private const CONTENT_TYPE = 'catalog_product'; + private const TYPE = 'entityType'; + private const ENTITY_ID = 'entityId'; + private const FIELD = 'field'; + + /** + * @var UpdateContentAssetLinksInterface + */ + private $updateContentAssetLinks; + + /** + * @var array + */ + private $fields; + + /** + * @var ContentIdentityInterfaceFactory + */ + private $contentIdentityFactory; + + /** + * @var GetEntityContentsInterface + */ + private $getContent; + + /** + * * Create links for product content + * + * @param ContentIdentityInterfaceFactory $contentIdentityFactory + * @param GetEntityContentsInterface $getContent + * @param UpdateContentAssetLinksInterface $updateContentAssetLinks + * @param array $fields + */ + public function __construct( + ContentIdentityInterfaceFactory $contentIdentityFactory, + GetEntityContentsInterface $getContent, + UpdateContentAssetLinksInterface $updateContentAssetLinks, + array $fields + ) { + $this->contentIdentityFactory = $contentIdentityFactory; + $this->getContent = $getContent; + $this->updateContentAssetLinks = $updateContentAssetLinks; + $this->fields = $fields; + } + + /** + * Retrieve the saved product and pass it to the model processor to save content - asset relations + * + * @param Observer $observer + * @throws \Exception + */ + public function execute(Observer $observer): void + { + $model = $observer->getEvent()->getData('product'); + if ($model instanceof CatalogProduct) { + foreach ($this->fields as $field) { + if (!$model->dataHasChangedFor($field)) { + continue; + } + $contentIdentity = $this->contentIdentityFactory->create( + [ + self::TYPE => self::CONTENT_TYPE, + self::FIELD => $field, + self::ENTITY_ID => (string) $model->getEntityId(), + ] + ); + $concatenatedContent = implode(PHP_EOL, $this->getContent->execute($contentIdentity)); + $this->updateContentAssetLinks->execute($contentIdentity, $concatenatedContent); + } + } + } +} diff --git a/app/code/Magento/MediaContentCatalog/README.md b/app/code/Magento/MediaContentCatalog/README.md new file mode 100644 index 0000000000000..359126a8b7a13 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/README.md @@ -0,0 +1,13 @@ +# Magento_MediaContentCatalog module + +The Magento_MediaContentCatalog provides the implementation of MediaContent functionality for Magento_Catalog module + +## Extensibility + +Extension developers can interact with the Magento_MediaContent module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_MediaContent module. + +## Additional information + +For information about significant changes in patch releases, see [2.3.x Release information](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html). diff --git a/app/code/Magento/MediaContentCatalog/composer.json b/app/code/Magento/MediaContentCatalog/composer.json new file mode 100644 index 0000000000000..c48cb58209f23 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-media-content-catalog", + "description": "Magento module provides the implementation of MediaContent functionality for Magento_Catalog module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/module-media-content-api": "*", + "magento/module-catalog": "*", + "magento/module-eav": "*", + "magento/framework": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\MediaContentCatalog\\": "" + } + } +} diff --git a/app/code/Magento/MediaContentCatalog/etc/di.xml b/app/code/Magento/MediaContentCatalog/etc/di.xml new file mode 100644 index 0000000000000..a2d300a2bb208 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/etc/di.xml @@ -0,0 +1,33 @@ +<?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\MediaContentApi\Model\Composite\GetEntityContents"> + <arguments> + <argument name="items" xsi:type="array"> + <item name="catalog_product" xsi:type="object">Magento\MediaContentCatalog\Model\ResourceModel\GetEntityContent</item> + <item name="catalog_category" xsi:type="object">Magento\MediaContentCatalog\Model\ResourceModel\GetEntityContent</item> + </argument> + </arguments> + </type> + <type name="Magento\MediaContentCatalog\Observer\Category"> + <arguments> + <argument name="fields" xsi:type="array"> + <item name="image" xsi:type="string">image</item> + <item name="description" xsi:type="string">description</item> + </argument> + </arguments> + </type> + <type name="Magento\MediaContentCatalog\Observer\Product"> + <arguments> + <argument name="fields" xsi:type="array"> + <item name="description" xsi:type="string">description</item> + <item name="short_description" xsi:type="string">short_description</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/MediaContentCatalog/etc/events.xml b/app/code/Magento/MediaContentCatalog/etc/events.xml new file mode 100644 index 0000000000000..f68d66eb3cc40 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/etc/events.xml @@ -0,0 +1,15 @@ +<?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:Event/etc/events.xsd"> + <event name="catalog_category_save_after"> + <observer name="media_content_catalog_category_save_after" instance="Magento\MediaContentCatalog\Observer\Category" /> + </event> + <event name="catalog_product_save_after"> + <observer name="media_content_catalog_product_save_after" instance="Magento\MediaContentCatalog\Observer\Product" /> + </event> +</config> diff --git a/app/code/Magento/MediaContentCatalog/etc/media_content.xml b/app/code/Magento/MediaContentCatalog/etc/media_content.xml new file mode 100644 index 0000000000000..8734d084b4d47 --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/etc/media_content.xml @@ -0,0 +1,15 @@ +<?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:module:Magento_MediaContentApi:etc/media_content.xsd"> + <search> + <patterns> + <pattern name="catalog_image">/^\/?media\/(.*)/</pattern> + <pattern name="catalog_image_with_pub">/^\/pub\/?media\/(.*)/</pattern> + </patterns> + </search> +</config> diff --git a/app/code/Magento/MediaContentCatalog/etc/module.xml b/app/code/Magento/MediaContentCatalog/etc/module.xml new file mode 100644 index 0000000000000..9b863edd57e5e --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/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_MediaContentCatalog" /> +</config> diff --git a/app/code/Magento/MediaContentCatalog/registration.php b/app/code/Magento/MediaContentCatalog/registration.php new file mode 100644 index 0000000000000..e954285921cba --- /dev/null +++ b/app/code/Magento/MediaContentCatalog/registration.php @@ -0,0 +1,10 @@ +<?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_MediaContentCatalog', __DIR__); diff --git a/app/code/Magento/MediaContentCms/LICENSE.txt b/app/code/Magento/MediaContentCms/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/MediaContentCms/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/MediaContentCms/LICENSE_AFL.txt b/app/code/Magento/MediaContentCms/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/MediaContentCms/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/MediaContentCms/Observer/Block.php b/app/code/Magento/MediaContentCms/Observer/Block.php new file mode 100644 index 0000000000000..ccd1abb98bc60 --- /dev/null +++ b/app/code/Magento/MediaContentCms/Observer/Block.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentCms\Observer; + +use Magento\Cms\Model\Block as CmsBlock; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\MediaContentApi\Api\UpdateContentAssetLinksInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterfaceFactory; + +/** + * Observe cms_block_save_after event and run processing relation between cms block content and media asset + */ +class Block implements ObserverInterface +{ + private const CONTENT_TYPE = 'cms_block'; + private const TYPE = 'entityType'; + private const ENTITY_ID = 'entityId'; + private const FIELD = 'field'; + + /** + * @var UpdateContentAssetLinksInterface + */ + private $updateContentAssetLinks; + + /** + * @var array + */ + private $fields; + + /** + * @var ContentIdentityInterfaceFactory + */ + private $contentIdentityFactory; + + /** + * @param ContentIdentityInterfaceFactory $contentIdentityFactory + * @param UpdateContentAssetLinksInterface $updateContentAssetLinks + * @param array $fields + */ + public function __construct( + ContentIdentityInterfaceFactory $contentIdentityFactory, + UpdateContentAssetLinksInterface $updateContentAssetLinks, + array $fields + ) { + $this->contentIdentityFactory = $contentIdentityFactory; + $this->updateContentAssetLinks = $updateContentAssetLinks; + $this->fields = $fields; + } + + /** + * Retrieve the saved block and pass it to the model processor to save content - asset relations + * + * @param Observer $observer + */ + public function execute(Observer $observer): void + { + $model = $observer->getEvent()->getData('object'); + + if ($model instanceof CmsBlock) { + foreach ($this->fields as $field) { + if (!$model->dataHasChangedFor($field)) { + continue; + } + $this->updateContentAssetLinks->execute( + $this->contentIdentityFactory->create( + [ + self::TYPE => self::CONTENT_TYPE, + self::FIELD => $field, + self::ENTITY_ID => (string) $model->getId(), + ] + ), + (string) $model->getData($field) + ); + } + } + } +} diff --git a/app/code/Magento/MediaContentCms/Observer/Page.php b/app/code/Magento/MediaContentCms/Observer/Page.php new file mode 100644 index 0000000000000..4c0ed5c628d1c --- /dev/null +++ b/app/code/Magento/MediaContentCms/Observer/Page.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaContentCms\Observer; + +use Magento\Cms\Model\Page as CmsPage; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\MediaContentApi\Api\UpdateContentAssetLinksInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterfaceFactory; + +/** + * Observe cms_page_save_after event and run processing relation between cms page content and media asset. + */ +class Page implements ObserverInterface +{ + private const CONTENT_TYPE = 'cms_page'; + private const TYPE = 'entityType'; + private const ENTITY_ID = 'entityId'; + private const FIELD = 'field'; + + /** + * @var UpdateContentAssetLinksInterface + */ + private $updateContentAssetLinks; + + /** + * @var array + */ + private $fields; + + /** + * @var ContentIdentityInterfaceFactory + */ + private $contentIdentityFactory; + + /** + * @param ContentIdentityInterfaceFactory $contentIdentityFactory + * @param UpdateContentAssetLinksInterface $updateContentAssetLinks + * @param array $fields + */ + public function __construct( + ContentIdentityInterfaceFactory $contentIdentityFactory, + UpdateContentAssetLinksInterface $updateContentAssetLinks, + array $fields + ) { + $this->contentIdentityFactory = $contentIdentityFactory; + $this->updateContentAssetLinks = $updateContentAssetLinks; + $this->fields = $fields; + } + + /** + * Retrieve the saved page and pass it to the model processor to save content - asset relations + * + * @param Observer $observer + */ + public function execute(Observer $observer): void + { + $model = $observer->getEvent()->getData('object'); + + if ($model instanceof CmsPage) { + foreach ($this->fields as $field) { + if (!$model->dataHasChangedFor($field)) { + continue; + } + $this->updateContentAssetLinks->execute( + $this->contentIdentityFactory->create( + [ + self::TYPE => self::CONTENT_TYPE, + self::FIELD => $field, + self::ENTITY_ID => (string) $model->getId(), + ] + ), + (string) $model->getData($field) + ); + } + } + } +} diff --git a/app/code/Magento/MediaContentCms/README.md b/app/code/Magento/MediaContentCms/README.md new file mode 100644 index 0000000000000..32b0fdc2bbd2f --- /dev/null +++ b/app/code/Magento/MediaContentCms/README.md @@ -0,0 +1,13 @@ +# Magento_MediaContentCms module + +The Magento_MediaContentCms provides the implementation of MediaContent functionality for Magento_Cms module + +## Extensibility + +Extension developers can interact with the Magento_MediaContent module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_MediaContent module. + +## Additional information + +For information about significant changes in patch releases, see [2.3.x Release information](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html). diff --git a/app/code/Magento/MediaContentCms/composer.json b/app/code/Magento/MediaContentCms/composer.json new file mode 100644 index 0000000000000..5fbc14d5b8117 --- /dev/null +++ b/app/code/Magento/MediaContentCms/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-media-content-cms", + "description": "Magento module provides the implementation of MediaContent functionality for Magento_Cms module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/module-media-content-api": "*", + "magento/module-cms": "*", + "magento/framework": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\MediaContentCms\\": "" + } + } +} diff --git a/app/code/Magento/MediaContentCms/etc/di.xml b/app/code/Magento/MediaContentCms/etc/di.xml new file mode 100644 index 0000000000000..f980936465faf --- /dev/null +++ b/app/code/Magento/MediaContentCms/etc/di.xml @@ -0,0 +1,23 @@ +<?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\MediaContentCms\Observer\Block"> + <arguments> + <argument name="fields" xsi:type="array"> + <item name="content" xsi:type="string">content</item> + </argument> + </arguments> + </type> + <type name="Magento\MediaContentCms\Observer\Page"> + <arguments> + <argument name="fields" xsi:type="array"> + <item name="content" xsi:type="string">content</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/MediaContentCms/etc/events.xml b/app/code/Magento/MediaContentCms/etc/events.xml new file mode 100644 index 0000000000000..7e9abe3bf19c4 --- /dev/null +++ b/app/code/Magento/MediaContentCms/etc/events.xml @@ -0,0 +1,15 @@ +<?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:Event/etc/events.xsd"> + <event name="cms_page_save_after"> + <observer name="media_content_cms_page_save_after" instance="Magento\MediaContentCms\Observer\Page" /> + </event> + <event name="cms_block_save_after"> + <observer name="media_content_cms_block_save_after" instance="Magento\MediaContentCms\Observer\Block" /> + </event> +</config> diff --git a/app/code/Magento/MediaContentCms/etc/media_content.xml b/app/code/Magento/MediaContentCms/etc/media_content.xml new file mode 100644 index 0000000000000..c0f41d1092134 --- /dev/null +++ b/app/code/Magento/MediaContentCms/etc/media_content.xml @@ -0,0 +1,15 @@ +<?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:module:Magento_MediaContentApi:etc/media_content.xsd"> + <search> + <patterns> + <pattern name="media_gallery">/{{media url="?(.*?)"?}}/</pattern> + <pattern name="wysiwyg">/src=".*\/media\/(.*?)"/</pattern> + </patterns> + </search> +</config> \ No newline at end of file diff --git a/app/code/Magento/MediaContentCms/etc/module.xml b/app/code/Magento/MediaContentCms/etc/module.xml new file mode 100644 index 0000000000000..868a6d96fdf51 --- /dev/null +++ b/app/code/Magento/MediaContentCms/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_MediaContentCms"/> +</config> diff --git a/app/code/Magento/MediaContentCms/registration.php b/app/code/Magento/MediaContentCms/registration.php new file mode 100644 index 0000000000000..b231ce3c6f3de --- /dev/null +++ b/app/code/Magento/MediaContentCms/registration.php @@ -0,0 +1,10 @@ +<?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_MediaContentCms', __DIR__); diff --git a/app/code/Magento/MediaGallery/Model/Asset.php b/app/code/Magento/MediaGallery/Model/Asset.php index 8e232ff0336d9..78b9477a70b08 100644 --- a/app/code/Magento/MediaGallery/Model/Asset.php +++ b/app/code/Magento/MediaGallery/Model/Asset.php @@ -10,36 +10,113 @@ use Magento\MediaGalleryApi\Api\Data\AssetExtensionInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterface; -use Magento\Framework\Model\AbstractExtensibleModel; /** * Media Gallery Asset + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ -class Asset extends AbstractExtensibleModel implements AssetInterface +class Asset implements AssetInterface { - private const ID = 'id'; - private const PATH = 'path'; - private const TITLE = 'title'; - private const SOURCE = 'source'; - private const CONTENT_TYPE = 'content_type'; - private const WIDTH = 'width'; - private const HEIGHT = 'height'; - private const SIZE = 'size'; - private const CREATED_AT = 'created_at'; - private const UPDATED_AT = 'updated_at'; + /** + * @var int|null + */ + private $id; + + /** + * @var string + */ + private $path; + + /** + * @var string|null + */ + private $title; + + /** + * @var string|null + */ + private $source; + + /** + * @var string + */ + private $contentType; + + /** + * @var int + */ + private $width; + + /** + * @var int + */ + private $height; + + /** + * @var int + */ + private $size; + + /** + * @var string|null + */ + private $createdAt; + + /** + * @var string|null + */ + private $updatedAt; + + /** + * @var AssetExtensionInterface|null + */ + private $extensionAttributes; + + /** + * @param string $path + * @param string $contentType + * @param int $width + * @param int $height + * @param int $size + * @param int|null $id + * @param string|null $title + * @param string|null $source + * @param string|null $createdAt + * @param string|null $updatedAt + * @param AssetExtensionInterface|null $extensionAttributes + */ + public function __construct( + string $path, + string $contentType, + int $width, + int $height, + int $size, + ?int $id = null, + ?string $title = null, + ?string $source = null, + ?string $createdAt = null, + ?string $updatedAt = null, + ?AssetExtensionInterface $extensionAttributes = null + ) { + $this->path = $path; + $this->contentType = $contentType; + $this->width = $width; + $this->height = $height; + $this->size = $size; + $this->id = $id; + $this->title = $title; + $this->source = $source; + $this->createdAt = $createdAt; + $this->updatedAt = $updatedAt; + $this->extensionAttributes = $extensionAttributes; + } /** * @inheritdoc */ public function getId(): ?int { - $id = $this->getData(self::ID); - - if (!$id) { - return null; - } - - return (int) $id; + return $this->id; } /** @@ -47,7 +124,7 @@ public function getId(): ?int */ public function getPath(): string { - return (string) $this->getData(self::PATH); + return $this->path; } /** @@ -55,7 +132,7 @@ public function getPath(): string */ public function getTitle(): ?string { - return $this->getData(self::TITLE); + return $this->title; } /** @@ -63,7 +140,7 @@ public function getTitle(): ?string */ public function getSource(): ?string { - return $this->getData(self::SOURCE); + return $this->source; } /** @@ -71,7 +148,7 @@ public function getSource(): ?string */ public function getContentType(): string { - return (string) $this->getData(self::CONTENT_TYPE); + return $this->contentType; } /** @@ -79,7 +156,7 @@ public function getContentType(): string */ public function getWidth(): int { - return (int) $this->getData(self::WIDTH); + return $this->width; } /** @@ -87,7 +164,7 @@ public function getWidth(): int */ public function getHeight(): int { - return (int) $this->getData(self::HEIGHT); + return $this->height; } /** @@ -95,38 +172,38 @@ public function getHeight(): int */ public function getSize(): int { - return (int) $this->getData(self::SIZE); + return $this->size; } /** * @inheritdoc */ - public function getCreatedAt(): string + public function getCreatedAt(): ?string { - return (string) $this->getData(self::CREATED_AT); + return $this->createdAt; } /** * @inheritdoc */ - public function getUpdatedAt(): string + public function getUpdatedAt(): ?string { - return (string) $this->getData(self::UPDATED_AT); + return $this->updatedAt; } /** * @inheritdoc */ - public function getExtensionAttributes(): AssetExtensionInterface + public function getExtensionAttributes(): ?AssetExtensionInterface { - return $this->_getExtensionAttributes(); + return $this->extensionAttributes; } /** * @inheritdoc */ - public function setExtensionAttributes(AssetExtensionInterface $extensionAttributes): void + public function setExtensionAttributes(?AssetExtensionInterface $extensionAttributes): void { - $this->_setExtensionAttributes($extensionAttributes); + $this->extensionAttributes = $extensionAttributes; } } diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php index a50095fb3d8e7..3abe4cb50f2ea 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php @@ -14,9 +14,9 @@ use Psr\Log\LoggerInterface; /** - * Class DeleteByDirectoryPath - * * Remove asset(s) that correspond the provided directory path + * @deprecated use \Magento\MediaGalleryApi\Api\DeleteAssetsByPathInterface instead + * @see \Magento\MediaGalleryApi\Api\DeleteAssetsByPathInterfac */ class DeleteByDirectoryPath implements DeleteByDirectoryPathInterface { diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByPath.php index c05a08149bfca..fc8e5d7c84bfd 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByPath.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByPath.php @@ -14,7 +14,10 @@ use Psr\Log\LoggerInterface; /** - * Class DeleteByPath + * Delete media asset by path + * + * @deprecated use \Magento\MediaGalleryApi\Api\DeleteAssetsByPathInterface instead + * @see \Magento\MediaGalleryApi\Api\DeleteAssetsByPathInterface */ class DeleteByPath implements DeleteByPathInterface { diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php b/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php index 6be11610ac197..b2f900233e46a 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php @@ -16,7 +16,9 @@ use Psr\Log\LoggerInterface; /** - * Class GetById + * Get media asset by id + * @deprecated use \Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface instead + * @see \Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface */ class GetById implements GetByIdInterface { @@ -28,7 +30,7 @@ class GetById implements GetByIdInterface private $resourceConnection; /** - * @var AssetInterface + * @var AssetInterfaceFactory */ private $assetFactory; @@ -87,7 +89,20 @@ public function execute(int $mediaAssetId): AssetInterface } try { - return $this->assetFactory->create(['data' => $mediaAssetData]); + return $this->assetFactory->create( + [ + 'id' => $mediaAssetData['id'], + 'path' => $mediaAssetData['path'], + 'title' => $mediaAssetData['title'], + 'source' => $mediaAssetData['source'], + 'contentType' => $mediaAssetData['content_type'], + 'width' => $mediaAssetData['width'], + 'height' => $mediaAssetData['height'], + 'size' => $mediaAssetData['size'], + 'createdAt' => $mediaAssetData['created_at'], + 'updatedAt' => $mediaAssetData['updated_at'], + ] + ); } catch (\Exception $exception) { $this->logger->critical($exception); $message = __( diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php index db8482d3399ba..d9faad62b2cd1 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php @@ -16,7 +16,10 @@ use Psr\Log\LoggerInterface; /** - * Class GetListByIds + * Provide media asset by path + * + * @deprecated use \Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface instead + * @see \Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface */ class GetByPath implements GetByPathInterface { @@ -30,7 +33,7 @@ class GetByPath implements GetByPathInterface private $resourceConnection; /** - * @var AssetInterface + * @var AssetInterfaceFactory */ private $mediaAssetFactory; @@ -57,30 +60,41 @@ public function __construct( } /** - * Return media asset asset list + * Return media asset * - * @param string $mediaFilePath + * @param string $path * * @return AssetInterface * @throws IntegrationException */ - public function execute(string $mediaFilePath): AssetInterface + public function execute(string $path): AssetInterface { try { $connection = $this->resourceConnection->getConnection(); $select = $connection->select() ->from($this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET)) - ->where(self::MEDIA_GALLERY_ASSET_PATH . ' = ?', $mediaFilePath); + ->where(self::MEDIA_GALLERY_ASSET_PATH . ' = ?', $path); $data = $connection->query($select)->fetch(); if (empty($data)) { - $message = __('There is no such media asset with path "%1"', $mediaFilePath); + $message = __('There is no such media asset with path "%1"', $path); throw new NoSuchEntityException($message); } - $mediaAssets = $this->mediaAssetFactory->create(['data' => $data]); - - return $mediaAssets; + return $this->mediaAssetFactory->create( + [ + 'id' => $data['id'], + 'path' => $data['path'], + 'title' => $data['title'], + 'source' => $data['source'], + 'contentType' => $data['content_type'], + 'width' => $data['width'], + 'height' => $data['height'], + 'size' => $data['size'], + 'createdAt' => $data['created_at'], + 'updatedAt' => $data['updated_at'], + ] + ); } catch (\Exception $exception) { $this->logger->critical($exception); $message = __('An error occurred during get media asset list: %1', $exception->getMessage()); diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/Save.php b/app/code/Magento/MediaGallery/Model/Asset/Command/Save.php index 7cb2f73169642..1710176c1b3af 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/Save.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/Save.php @@ -7,15 +7,17 @@ namespace Magento\MediaGallery\Model\Asset\Command; -use Magento\MediaGalleryApi\Model\DataExtractorInterface; -use Magento\MediaGalleryApi\Api\Data\AssetInterface; -use Magento\MediaGalleryApi\Model\Asset\Command\SaveInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Model\Asset\Command\SaveInterface; use Psr\Log\LoggerInterface; /** - * Class Save + * Save media asset + * + * @deprecated use \Magento\MediaGalleryApi\Api\SaveAssetsInterface instead + * @see \Magento\MediaGalleryApi\Api\SaveAssetsInterface */ class Save implements SaveInterface { @@ -26,11 +28,6 @@ class Save implements SaveInterface */ private $resourceConnection; - /** - * @var DataExtractorInterface - */ - private $extractor; - /** * @var LoggerInterface */ @@ -40,21 +37,18 @@ class Save implements SaveInterface * Save constructor. * * @param ResourceConnection $resourceConnection - * @param DataExtractorInterface $extractor * @param LoggerInterface $logger */ public function __construct( ResourceConnection $resourceConnection, - DataExtractorInterface $extractor, LoggerInterface $logger ) { $this->resourceConnection = $resourceConnection; - $this->extractor = $extractor; $this->logger = $logger; } /** - * Save media assets + * Save media asset * * @param AssetInterface $mediaAsset * @@ -67,9 +61,27 @@ public function execute(AssetInterface $mediaAsset): int /** @var \Magento\Framework\DB\Adapter\Pdo\Mysql $connection */ $connection = $this->resourceConnection->getConnection(); $tableName = $this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET); + $record = [ + 'id' => $mediaAsset->getId(), + 'path' => $mediaAsset->getPath(), + 'title' => $mediaAsset->getTitle(), + 'source' => $mediaAsset->getSource(), + 'content_type' => $mediaAsset->getContentType(), + 'width' => $mediaAsset->getWidth(), + 'height' => $mediaAsset->getHeight(), + 'size' => $mediaAsset->getSize(), + ]; + + if ($mediaAsset->getCreatedAt()) { + $record['created_at'] = $mediaAsset->getCreatedAt(); + } + + if ($mediaAsset->getUpdatedAt()) { + $record['updated_at'] = $mediaAsset->getUpdatedAt(); + } - $connection->insertOnDuplicate($tableName, $this->extractor->extract($mediaAsset, AssetInterface::class)); - return (int) $connection->lastInsertId($tableName); + $connection->insertOnDuplicate($tableName, $record); + return (int)$connection->lastInsertId($tableName); } catch (\Exception $exception) { $this->logger->critical($exception); $message = __('An error occurred during media asset save: %1', $exception->getMessage()); diff --git a/app/code/Magento/MediaGallery/Model/AssetKeywords.php b/app/code/Magento/MediaGallery/Model/AssetKeywords.php new file mode 100644 index 0000000000000..4ffea9551263c --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/AssetKeywords.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model; + +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterface; +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsExtensionInterface; + +/** + * Asset Id and Keywords combination data object for bulk operations with keyword services + */ +class AssetKeywords implements AssetKeywordsInterface +{ + /** + * @var int + */ + private $assetId; + + /** + * @var array + */ + private $keywords; + + /** + * @var AssetKeywordsExtensionInterface|null + */ + private $extensionAttributes; + + /** + * @param int $assetId + * @param array $keywords + * @param AssetKeywordsExtensionInterface|null $extensionAttributes + */ + public function __construct( + int $assetId, + array $keywords, + ?AssetKeywordsExtensionInterface $extensionAttributes = null + ) { + $this->assetId = $assetId; + $this->keywords = $keywords; + $this->extensionAttributes = $extensionAttributes; + } + + /** + * @inheritdoc + */ + public function getAssetId(): int + { + return $this->assetId; + } + + /** + * @inheritdoc + */ + public function getKeywords(): array + { + return $this->keywords; + } + + /** + * @inheritdoc + */ + public function getExtensionAttributes(): ?AssetKeywordsExtensionInterface + { + return $this->extensionAttributes; + } + + /** + * @inheritdoc + */ + public function setExtensionAttributes(?AssetKeywordsExtensionInterface $extensionAttributes): void + { + $this->extensionAttributes = $extensionAttributes; + } +} diff --git a/app/code/Magento/MediaGallery/Model/DataExtractor.php b/app/code/Magento/MediaGallery/Model/DataExtractor.php deleted file mode 100644 index 92cf237022c28..0000000000000 --- a/app/code/Magento/MediaGallery/Model/DataExtractor.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\MediaGallery\Model; - -use Magento\MediaGalleryApi\Model\DataExtractorInterface; - -/** - * Extract data from an object using available getters - */ -class DataExtractor implements DataExtractorInterface -{ - /** - * Extract data from an object using available getters (does not process extension attributes) - * - * @param object $object - * @param string|null $interface - * - * @return array - * @throws \ReflectionException - */ - public function extract($object, string $interface = null): array - { - $data = []; - - $reflectionClass = new \ReflectionClass($interface ?? $object); - - foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { - $methodName = $method->getName(); - if (strpos($methodName, 'get') !== 0 - || !empty($method->getParameters()) - || strpos($methodName, 'getExtensionAttributes') !== false - ) { - continue; - } - $value = $object->$methodName(); - if (!empty($value)) { - $key = strtolower(preg_replace("/([a-z])([A-Z])/", "$1_$2", substr($methodName, 3))); - $data[$key] = $value; - } - } - return $data; - } -} diff --git a/app/code/Magento/MediaGallery/Model/Directory/BlacklistPatternsConfig.php b/app/code/Magento/MediaGallery/Model/Directory/BlacklistPatternsConfig.php new file mode 100644 index 0000000000000..8fdd4f70d5060 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/Directory/BlacklistPatternsConfig.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory; + +use Magento\Framework\Config\DataInterface; +use Magento\MediaGalleryApi\Model\BlacklistPatternsConfigInterface; + +/** + * Media gallery directory config + */ +class BlacklistPatternsConfig implements BlacklistPatternsConfigInterface +{ + private const XML_PATH_BLACKLIST_PATTERNS = 'blacklist/patterns'; + + /** + * @var DataInterface + */ + private $data; + + /** + * @param DataInterface $data + */ + public function __construct(DataInterface $data) + { + $this->data = $data; + } + + /** + * Returns list of blacklist regexp patterns + * + * @return array + */ + public function get() : array + { + return $this->data->get(self::XML_PATH_BLACKLIST_PATTERNS); + } +} diff --git a/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php b/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php new file mode 100644 index 0000000000000..4d87c1aa95285 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory\Command; + +use Magento\Cms\Model\Wysiwyg\Images\Storage; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\MediaGalleryApi\Api\CreateDirectoriesByPathsInterface; +use Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface; +use Psr\Log\LoggerInterface; + +/** + * Create directories by provided paths in the media storage + */ +class CreateByPaths implements CreateDirectoriesByPathsInterface +{ + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var Storage + */ + private $storage; + + /** + * @var IsPathBlacklistedInterface + */ + private $isPathBlacklisted; + + /** + * @param LoggerInterface $logger + * @param Storage $storage + * @param IsPathBlacklistedInterface $isPathBlacklisted + */ + public function __construct( + LoggerInterface $logger, + Storage $storage, + IsPathBlacklistedInterface $isPathBlacklisted + ) { + $this->logger = $logger; + $this->storage = $storage; + $this->isPathBlacklisted = $isPathBlacklisted; + } + + /** + * @inheritdoc + */ + public function execute(array $paths): void + { + $failedPaths = []; + foreach ($paths as $path) { + if ($this->isPathBlacklisted->execute($path)) { + $failedPaths[] = $path; + continue; + } + try { + //phpcs:ignore Magento2.Functions.DiscouragedFunction + $name = basename($path); + //phpcs:ignore Magento2.Functions.DiscouragedFunction + $folder = substr($path, 0, strrpos($path, $name)); + + $this->storage->createDirectory( + $name, + $this->storage->getCmsWysiwygImages()->getStorageRoot() . $folder + ); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $failedPaths[] = $path; + } + } + + if (!empty($failedPaths)) { + throw new CouldNotSaveException( + __( + 'Could not save directories: %paths', + [ + 'paths' => implode(' ,', $failedPaths) + ] + ) + ); + } + } +} diff --git a/app/code/Magento/MediaGallery/Model/Directory/Command/DeleteByPaths.php b/app/code/Magento/MediaGallery/Model/Directory/Command/DeleteByPaths.php new file mode 100644 index 0000000000000..d46fb854fff22 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/Directory/Command/DeleteByPaths.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory\Command; + +use Magento\Cms\Model\Wysiwyg\Images\Storage; +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\MediaGalleryApi\Api\DeleteDirectoriesByPathsInterface; +use Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface; +use Psr\Log\LoggerInterface; + +/** + * Delete directory by provided paths in the media storage + */ +class DeleteByPaths implements DeleteDirectoriesByPathsInterface +{ + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var Storage + */ + private $storage; + + /** + * @var IsPathBlacklistedInterface + */ + private $isPathBlacklisted; + + /** + * @param LoggerInterface $logger + * @param Storage $storage + * @param IsPathBlacklistedInterface $isPathBlacklisted + */ + public function __construct( + LoggerInterface $logger, + Storage $storage, + IsPathBlacklistedInterface $isPathBlacklisted + ) { + $this->logger = $logger; + $this->storage = $storage; + $this->isPathBlacklisted = $isPathBlacklisted; + } + + /** + * @inheritdoc + */ + public function execute(array $paths): void + { + $failedPaths = []; + foreach ($paths as $path) { + if ($this->isPathBlacklisted->execute($path)) { + $failedPaths[] = $path; + continue; + } + try { + $this->storage->deleteDirectory($this->storage->getCmsWysiwygImages()->getStorageRoot() . $path); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $failedPaths[] = $path; + } + } + + if (!empty($failedPaths)) { + throw new CouldNotDeleteException( + __( + 'Could not delete directories: %paths', + [ + 'paths' => implode(' ,', $failedPaths) + ] + ) + ); + } + } +} diff --git a/app/code/Magento/MediaGallery/Model/Directory/Config/Converter.php b/app/code/Magento/MediaGallery/Model/Directory/Config/Converter.php new file mode 100644 index 0000000000000..91f16d246f636 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/Directory/Config/Converter.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory\Config; + +use Magento\Framework\Config\ConverterInterface; + +/** + * Media gallery directory config converter + */ +class Converter implements ConverterInterface +{ + /** + * Blacklist tag name + */ + private const BLACKLIST_TAG_NAME = 'blacklist'; + + /** + * Patterns tag name + */ + private const PATTERNS_TAG_NAME = 'patterns'; + + /** + * Pattern tag name + */ + private const PATTERN_TAG_NAME = 'pattern'; + + /** + * Convert dom node to array + * + * @param \DOMDocument $source + * @return array + */ + public function convert($source): array + { + $result = []; + + if (!$source instanceof \DOMDocument) { + throw new \InvalidArgumentException('The source should be instance of DOMDocument'); + } + + foreach ($source->getElementsByTagName(self::BLACKLIST_TAG_NAME) as $blacklist) { + $result[self::BLACKLIST_TAG_NAME] = []; + foreach ($blacklist->getElementsByTagName(self::PATTERNS_TAG_NAME) as $patterns) { + $result[self::BLACKLIST_TAG_NAME][self::PATTERNS_TAG_NAME] = []; + foreach ($patterns->getElementsByTagName(self::PATTERN_TAG_NAME) as $pattern) { + $result[self::BLACKLIST_TAG_NAME][self::PATTERNS_TAG_NAME] + [$pattern->attributes->getNamedItem('name')->nodeValue] = $pattern->nodeValue; + } + } + } + + return $result; + } +} diff --git a/app/code/Magento/MediaGallery/Model/Directory/Config/SchemaLocator.php b/app/code/Magento/MediaGallery/Model/Directory/Config/SchemaLocator.php new file mode 100644 index 0000000000000..7fdf414cdd228 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/Directory/Config/SchemaLocator.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory\Config; + +use Magento\Framework\Module\Dir; +use Magento\Framework\Module\Dir\Reader; +use Magento\Framework\Config\SchemaLocatorInterface; + +/** + * Media gallery directory config schema locator + */ +class SchemaLocator implements SchemaLocatorInterface +{ + /** + * Path to corresponding XSD file with validation rules for both individual and merged configs + * + * @var string + */ + private $schema; + + /** + * @param Reader $moduleReader + */ + public function __construct(Reader $moduleReader) + { + $this->schema = $moduleReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_MediaGalleryApi') . '/directory.xsd'; + } + + /** + * Get path to merged config schema + * + * @return string|null + */ + public function getSchema() + { + return $this->schema; + } + + /** + * Get path to per file validation schema + * + * @return string|null + */ + public function getPerFileSchema() + { + return $this->schema; + } +} diff --git a/app/code/Magento/MediaGallery/Model/Directory/IsBlacklisted.php b/app/code/Magento/MediaGallery/Model/Directory/IsBlacklisted.php new file mode 100644 index 0000000000000..0191b357aaefa --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/Directory/IsBlacklisted.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory; + +use Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface; +use Magento\MediaGalleryApi\Model\BlacklistPatternsConfigInterface; + +/** + * Check if the path is blacklisted for media gallery. Directory path may be blacklisted if it's reserved by the system + */ +class IsBlacklisted implements IsPathBlacklistedInterface +{ + /** + * @var BlacklistPatternsConfigInterface + */ + private $config; + + /** + * @param BlacklistPatternsConfigInterface $config + */ + public function __construct(BlacklistPatternsConfigInterface $config) + { + $this->config = $config; + } + + /** + * Check if the directory path can be used in the media gallery operations + * + * @param string $path + * @return bool + */ + public function execute(string $path): bool + { + foreach ($this->config->get() as $pattern) { + if (empty($pattern)) { + continue; + } + preg_match($pattern, $path, $result); + + if ($result) { + return true; + } + } + return false; + } +} diff --git a/app/code/Magento/MediaGallery/Model/Keyword.php b/app/code/Magento/MediaGallery/Model/Keyword.php index c5c60d3152846..5d3afd2096a62 100644 --- a/app/code/Magento/MediaGallery/Model/Keyword.php +++ b/app/code/Magento/MediaGallery/Model/Keyword.php @@ -10,29 +10,48 @@ use Magento\MediaGalleryApi\Api\Data\KeywordExtensionInterface; use Magento\MediaGalleryApi\Api\Data\KeywordInterface; -use Magento\Framework\Model\AbstractExtensibleModel; /** * Asset's Keyword */ -class Keyword extends AbstractExtensibleModel implements KeywordInterface +class Keyword implements KeywordInterface { - private const ID = 'id'; + /** + * @var int + */ + private $id; - private const KEYWORD = 'keyword'; + /** + * @var string + */ + private $keyword; + + /** + * @var KeywordExtensionInterface|null + */ + private $extensionAttributes; + + /** + * @param string $keyword + * @param int|null $id + * @param KeywordExtensionInterface|null $extensionAttributes + */ + public function __construct( + string $keyword, + ?int $id = null, + ?KeywordExtensionInterface $extensionAttributes = null + ) { + $this->keyword = $keyword; + $this->id = $id; + $this->extensionAttributes = $extensionAttributes; + } /** * @inheritdoc */ public function getId(): ?int { - $id = $this->getData(self::ID); - - if (!$id) { - return null; - } - - return (int) $id; + return $this->id; } /** @@ -40,22 +59,22 @@ public function getId(): ?int */ public function getKeyword(): string { - return (string)$this->getData(self::KEYWORD); + return $this->keyword; } /** * @inheritdoc */ - public function getExtensionAttributes(): KeywordExtensionInterface + public function getExtensionAttributes(): ?KeywordExtensionInterface { - return $this->_getExtensionAttributes(); + return $this->extensionAttributes; } /** * @inheritdoc */ - public function setExtensionAttributes(KeywordExtensionInterface $extensionAttributes): void + public function setExtensionAttributes(?KeywordExtensionInterface $extensionAttributes): void { - $this->_setExtensionAttributes($extensionAttributes); + $this->extensionAttributes = $extensionAttributes; } } diff --git a/app/code/Magento/MediaGallery/Model/Keyword/Command/GetAssetKeywords.php b/app/code/Magento/MediaGallery/Model/Keyword/Command/GetAssetKeywords.php index 5b826a26e937d..27d32e5444f4b 100644 --- a/app/code/Magento/MediaGallery/Model/Keyword/Command/GetAssetKeywords.php +++ b/app/code/Magento/MediaGallery/Model/Keyword/Command/GetAssetKeywords.php @@ -15,7 +15,8 @@ use Psr\Log\LoggerInterface; /** - * ClassGetAssetKeywords + * Retrieve keywords for the media asset + * @deprecated use \Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface instead */ class GetAssetKeywords implements GetAssetKeywordsInterface { @@ -59,7 +60,7 @@ public function __construct( * * @param int $assetId * - * @return KeywordInterface[]|[] + * @return KeywordInterface[] * @throws IntegrationException */ public function execute(int $assetId): array @@ -75,7 +76,12 @@ public function execute(int $assetId): array $keywords = []; foreach ($data as $keywordData) { - $keywords[] = $this->assetKeywordFactory->create(['data' => $keywordData]); + $keywords[] = $this->assetKeywordFactory->create( + [ + 'id' => $keywordData['id'], + 'keyword' => $keywordData['keyword'], + ] + ); } return $keywords; diff --git a/app/code/Magento/MediaGallery/Model/Keyword/Command/SaveAssetKeywords.php b/app/code/Magento/MediaGallery/Model/Keyword/Command/SaveAssetKeywords.php index b355a9a651cd4..f21db25bac767 100644 --- a/app/code/Magento/MediaGallery/Model/Keyword/Command/SaveAssetKeywords.php +++ b/app/code/Magento/MediaGallery/Model/Keyword/Command/SaveAssetKeywords.php @@ -7,16 +7,18 @@ namespace Magento\MediaGallery\Model\Keyword\Command; -use Magento\MediaGalleryApi\Api\Data\KeywordInterface; -use Magento\MediaGalleryApi\Model\Keyword\Command\SaveAssetKeywordsInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Adapter\Pdo\Mysql; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\MediaGallery\Model\ResourceModel\Keyword\SaveAssetLinks; +use Magento\MediaGalleryApi\Api\Data\KeywordInterface; +use Magento\MediaGalleryApi\Model\Keyword\Command\SaveAssetKeywordsInterface; use Psr\Log\LoggerInterface; /** - * Class SaveAssetKeywords + * Save media asset keywords to database + * @deprecated use \Magento\MediaGalleryApi\Api\SaveAssetKeywordsInterface instead */ class SaveAssetKeywords implements SaveAssetKeywordsInterface { @@ -40,8 +42,6 @@ class SaveAssetKeywords implements SaveAssetKeywordsInterface private $logger; /** - * SaveAssetKeywords constructor. - * * @param ResourceConnection $resourceConnection * @param SaveAssetLinks $saveAssetLinks * @param LoggerInterface $logger diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/DeleteAssetsByPaths.php b/app/code/Magento/MediaGallery/Model/ResourceModel/DeleteAssetsByPaths.php new file mode 100644 index 0000000000000..bf6379644cb6f --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/DeleteAssetsByPaths.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; +use Psr\Log\LoggerInterface; + +/** + * Remove asset(s) that correspond the provided path + */ +class DeleteAssetsByPaths implements DeleteAssetsByPathsInterface +{ + private const TABLE_MEDIA_GALLERY_ASSET = 'media_gallery_asset'; + private const MEDIA_GALLERY_ASSET_PATH = 'path'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * DeleteAssetsByPaths constructor. + * + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct( + ResourceConnection $resourceConnection, + LoggerInterface $logger + ) { + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(array $paths): void + { + $failedPaths = []; + + foreach ($paths as $path) { + try { + $this->validateDirectoryPath($path); + $this->deleteAssetsByDirectoryPath($path); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $failedPaths[] = $path; + } + } + + if (!empty($failedPaths)) { + throw new CouldNotDeleteException( + __( + 'Could not delete media assets by paths: %paths', + [ + 'paths' => implode(' ,', $failedPaths) + ] + ) + ); + } + } + + /** + * Delete assets from database based on the first part (beginning) of the path + * + * @param string $path + */ + private function deleteAssetsByDirectoryPath(string $path): void + { + /** @var AdapterInterface $connection */ + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET); + $connection->delete($tableName, [self::MEDIA_GALLERY_ASSET_PATH . ' LIKE ?' => $path . '%']); + } + + /** + * Validate the directory path + * + * @param string $path + * @throws CouldNotDeleteException + */ + private function validateDirectoryPath(string $path): void + { + if (!$path || trim($path) === '') { + throw new CouldNotDeleteException(__('Cannot remove assets, the directory path does not exist')); + } + } +} diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByIds.php b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByIds.php new file mode 100644 index 0000000000000..53185939b2283 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByIds.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\LocalizedException; +use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; +use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; +use Psr\Log\LoggerInterface; + +/** + * Get media assets by ids + */ +class GetAssetsByIds implements GetAssetsByIdsInterface +{ + private const TABLE_MEDIA_GALLERY_ASSET = 'media_gallery_asset'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var AssetInterfaceFactory + */ + private $assetFactory; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * GetById constructor. + * + * @param ResourceConnection $resourceConnection + * @param AssetInterfaceFactory $assetFactory + * @param LoggerInterface $logger + */ + public function __construct( + ResourceConnection $resourceConnection, + AssetInterfaceFactory $assetFactory, + LoggerInterface $logger + ) { + $this->resourceConnection = $resourceConnection; + $this->assetFactory = $assetFactory; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(array $ids): array + { + $assets = []; + try { + foreach ($this->getAssetsData($ids) as $assetData) { + $assets[] = $this->assetFactory->create( + [ + 'id' => $assetData['id'], + 'path' => $assetData['path'], + 'title' => $assetData['title'], + 'source' => $assetData['source'], + 'contentType' => $assetData['content_type'], + 'width' => $assetData['width'], + 'height' => $assetData['height'], + 'size' => $assetData['size'], + 'createdAt' => $assetData['created_at'], + 'updatedAt' => $assetData['updated_at'], + ] + ); + } + } catch (\Exception $exception) { + $this->logger->critical($exception); + throw new LocalizedException(__('Could not retrieve media assets'), $exception); + } + return $assets; + } + + /** + * Retrieve assets data from database + * + * @param array $ids + * @return array + * @throws \Zend_Db_Statement_Exception + */ + private function getAssetsData(array $ids): array + { + $mediaAssetTable = $this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET); + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from(['amg' => $mediaAssetTable]) + ->where('amg.id IN (?)', $ids); + return $connection->query($select)->fetchAll(); + } +} diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByPaths.php b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByPaths.php new file mode 100644 index 0000000000000..5593083d9673a --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByPaths.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Magento\Framework\Exception\LocalizedException; +use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\Framework\App\ResourceConnection; +use Psr\Log\LoggerInterface; + +/** + * Get media assets by paths + */ +class GetAssetsByPaths implements GetAssetsByPathsInterface +{ + private const TABLE_MEDIA_GALLERY_ASSET = 'media_gallery_asset'; + private const MEDIA_GALLERY_ASSET_PATH = 'path'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var AssetInterfaceFactory + */ + private $mediaAssetFactory; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * GetByPath constructor. + * + * @param ResourceConnection $resourceConnection + * @param AssetInterfaceFactory $mediaAssetFactory + * @param LoggerInterface $logger + */ + public function __construct( + ResourceConnection $resourceConnection, + AssetInterfaceFactory $mediaAssetFactory, + LoggerInterface $logger + ) { + $this->resourceConnection = $resourceConnection; + $this->mediaAssetFactory = $mediaAssetFactory; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(array $paths): array + { + $assets = []; + try { + foreach ($this->getAssetsData($paths) as $assetData) { + $assets[] = $this->mediaAssetFactory->create( + [ + 'id' => $assetData['id'], + 'path' => $assetData['path'], + 'title' => $assetData['title'], + 'source' => $assetData['source'], + 'contentType' => $assetData['content_type'], + 'width' => $assetData['width'], + 'height' => $assetData['height'], + 'size' => $assetData['size'], + 'createdAt' => $assetData['created_at'], + 'updatedAt' => $assetData['updated_at'], + ] + ); + } + } catch (\Exception $exception) { + $this->logger->critical($exception); + throw new LocalizedException( + __( + 'Could not get media assets for paths: %paths', + [ + 'paths' => implode(' ,', $paths) + ] + ) + ); + } + return $assets; + } + + /** + * Retrieve assets data from database + * + * @param array $paths + * @return array + * @throws \Zend_Db_Statement_Exception + */ + private function getAssetsData(array $paths): array + { + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from($this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET)) + ->where(self::MEDIA_GALLERY_ASSET_PATH . ' IN (?)', $paths); + return $connection->query($select)->fetchAll(); + } +} diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/GetAssetsKeywords.php b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/GetAssetsKeywords.php new file mode 100644 index 0000000000000..11b0a0fa3a359 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/GetAssetsKeywords.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel\Keyword; + +use Magento\Framework\Exception\IntegrationException; +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterface; +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterfaceFactory; +use Magento\MediaGalleryApi\Api\Data\KeywordInterfaceFactory; +use Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface; +use Magento\Framework\App\ResourceConnection; +use Psr\Log\LoggerInterface; + +/** + * Retrieve keywords of the media assets + */ +class GetAssetsKeywords implements GetAssetsKeywordsInterface +{ + private const TABLE_KEYWORD = 'media_gallery_keyword'; + private const TABLE_ASSET_KEYWORD = 'media_gallery_asset_keyword'; + private const FIELD_ASSET_ID = 'asset_id'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var KeywordInterfaceFactory + */ + private $keywordFactory; + + /** + * @var AssetKeywordsInterfaceFactory + */ + private $assetKeywordsFactory; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param AssetKeywordsInterfaceFactory $assetKeywordsFactory + * @param ResourceConnection $resourceConnection + * @param KeywordInterfaceFactory $keywordFactory + * @param LoggerInterface $logger + */ + public function __construct( + AssetKeywordsInterfaceFactory $assetKeywordsFactory, + ResourceConnection $resourceConnection, + KeywordInterfaceFactory $keywordFactory, + LoggerInterface $logger + ) { + $this->assetKeywordsFactory = $assetKeywordsFactory; + $this->resourceConnection = $resourceConnection; + $this->keywordFactory = $keywordFactory; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(array $assetIds): array + { + try { + return $this->getAssetKeywords($this->getKeywordsData($assetIds)); + } catch (\Exception $exception) { + $this->logger->critical($exception); + throw new IntegrationException(__('Could not retrieve asset keywords.'), $exception); + } + } + + /** + * Load keywords data + * + * @param array $assetIds + * @return array + * @throws \Zend_Db_Statement_Exception + */ + private function getKeywordsData(array $assetIds): array + { + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from(['k' => $this->resourceConnection->getTableName(self::TABLE_KEYWORD)]) + ->join(['ak' => self::TABLE_ASSET_KEYWORD], 'k.id = ak.keyword_id') + ->where('ak.asset_id IN (?)', $assetIds); + return $connection->query($select)->fetchAll(); + } + + /** + * Build AssetKeywords objects array + * + * @param array $keywordsData + * @return AssetKeywordsInterface[] + */ + private function getAssetKeywords(array $keywordsData): array + { + $keywordsByAsset = []; + foreach ($keywordsData as $keywordData) { + $keywordsByAsset[$keywordData[self::FIELD_ASSET_ID]][] = $this->keywordFactory->create( + [ + 'id' => $keywordData['id'], + 'keyword' => $keywordData['keyword'], + ] + ); + } + + $assetKeywords = []; + foreach ($keywordsByAsset as $assetId => $keywords) { + $assetKeywords[$assetId] = $this->assetKeywordsFactory->create( + [ + 'assetId' => $assetId, + 'keywords' => $keywords + ] + ); + } + + return $assetKeywords; + } +} diff --git a/app/code/Magento/MediaGallery/Model/Keyword/Command/SaveAssetLinks.php b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetLinks.php similarity index 84% rename from app/code/Magento/MediaGallery/Model/Keyword/Command/SaveAssetLinks.php rename to app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetLinks.php index 4d3fd2bb5c30d..3437cc1c519e8 100644 --- a/app/code/Magento/MediaGallery/Model/Keyword/Command/SaveAssetLinks.php +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetLinks.php @@ -5,18 +5,17 @@ */ declare(strict_types=1); -namespace Magento\MediaGallery\Model\Keyword\Command; +namespace Magento\MediaGallery\Model\ResourceModel\Keyword; -use Magento\MediaGalleryApi\Api\Data\KeywordInterface; -use Magento\MediaGalleryApi\Model\Keyword\Command\SaveAssetLinksInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Adapter\Pdo\Mysql; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\MediaGalleryApi\Api\Data\KeywordInterface; use Psr\Log\LoggerInterface; /** - * Class SaveAssetLinks + * Save links between asset and keyword to media_gallery_asset_keyword table */ class SaveAssetLinks { @@ -35,8 +34,6 @@ class SaveAssetLinks private $logger; /** - * SaveAssetLinks constructor. - * * @param ResourceConnection $resourceConnection * @param LoggerInterface $logger */ @@ -76,8 +73,10 @@ public function execute(int $assetId, array $keywordIds): void } } catch (\Exception $exception) { $this->logger->critical($exception); - $message = __('An error occurred during save asset keyword links: %1', $exception->getMessage()); - throw new CouldNotSaveException($message, $exception); + throw new CouldNotSaveException( + __('Could not save asset keyword links'), + $exception + ); } } } diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetsKeywords.php b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetsKeywords.php new file mode 100644 index 0000000000000..a97c5f602c5c7 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetsKeywords.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel\Keyword; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Adapter\Pdo\Mysql; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\MediaGalleryApi\Api\Data\KeywordInterface; +use Magento\MediaGalleryApi\Api\SaveAssetsKeywordsInterface; +use Psr\Log\LoggerInterface; + +/** + * Save keywords of media assets + */ +class SaveAssetsKeywords implements SaveAssetsKeywordsInterface +{ + private const TABLE_KEYWORD = 'media_gallery_keyword'; + private const ID = 'id'; + private const KEYWORD = 'keyword'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var SaveAssetLinks + */ + private $saveAssetLinks; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * SaveAssetKeywords constructor. + * + * @param ResourceConnection $resourceConnection + * @param SaveAssetLinks $saveAssetLinks + * @param LoggerInterface $logger + */ + public function __construct( + ResourceConnection $resourceConnection, + SaveAssetLinks $saveAssetLinks, + LoggerInterface $logger + ) { + $this->resourceConnection = $resourceConnection; + $this->saveAssetLinks = $saveAssetLinks; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(array $assetKeywords): void + { + $failedAssetIds = []; + foreach ($assetKeywords as $assetKeyword) { + try { + $this->saveAssetKeywords($assetKeyword->getKeywords(), $assetKeyword->getAssetId()); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $failedAssetIds[] = $assetKeyword->getAssetId(); + } + } + + if (!empty($failedAssetIds)) { + throw new CouldNotSaveException( + __('Could not save keywords for asset ids: %ids', ['ids' => implode(' ,', $failedAssetIds)]) + ); + } + } + + /** + * Save asset keywords. + * + * @param KeywordInterface[] $keywords + * @param int $assetId + * @throws CouldNotSaveException + * @throws \Zend_Db_Exception + */ + private function saveAssetKeywords(array $keywords, int $assetId): void + { + $data = []; + foreach ($keywords as $keyword) { + $data[] = $keyword->getKeyword(); + } + + if (empty($data)) { + return; + } + + /** @var Mysql $connection */ + $connection = $this->resourceConnection->getConnection(); + $connection->insertArray( + $this->resourceConnection->getTableName(self::TABLE_KEYWORD), + [self::KEYWORD], + $data, + AdapterInterface::INSERT_IGNORE + ); + + $this->saveAssetLinks->execute($assetId, $this->getKeywordIds($data)); + } + + /** + * Select keywords by names + * + * @param string[] $keywords + * @return int[] + */ + private function getKeywordIds(array $keywords): array + { + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from(['k' => $this->resourceConnection->getTableName(self::TABLE_KEYWORD)]) + ->columns(self::ID) + ->where('k.' . self::KEYWORD . ' in (?)', $keywords); + + return $connection->fetchCol($select); + } +} diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/SaveAssets.php b/app/code/Magento/MediaGallery/Model/ResourceModel/SaveAssets.php new file mode 100644 index 0000000000000..ec08addf93462 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/SaveAssets.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\SaveAssetsInterface; +use Psr\Log\LoggerInterface; + +/** + * Save media asset to the database + */ +class SaveAssets implements SaveAssetsInterface +{ + private const TABLE_MEDIA_GALLERY_ASSET = 'media_gallery_asset'; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * Save constructor. + * + * @param ResourceConnection $resourceConnection + * @param LoggerInterface $logger + */ + public function __construct( + ResourceConnection $resourceConnection, + LoggerInterface $logger + ) { + $this->resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function execute(array $assets): void + { + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET); + + $failedAssets = []; + foreach ($assets as $asset) { + try { + $record = [ + 'id' => $asset->getId(), + 'path' => $asset->getPath(), + 'title' => $asset->getTitle(), + 'source' => $asset->getSource(), + 'content_type' => $asset->getContentType(), + 'width' => $asset->getWidth(), + 'height' => $asset->getHeight(), + 'size' => $asset->getSize(), + ]; + + if ($asset->getCreatedAt()) { + $record['created_at'] = $asset->getCreatedAt(); + } + + if ($asset->getUpdatedAt()) { + $record['updated_at'] = $asset->getUpdatedAt(); + } + + $connection->insertOnDuplicate($tableName, $record); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $failedAssets[] = $asset; + } + } + + if (!empty($failedAssets)) { + throw new CouldNotSaveException( + __( + 'Could not save the media assets: %assets', + [ + 'assets' => implode(' ,', $failedAssets) + ] + ) + ); + } + } +} diff --git a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php index 11331e4b9303f..3850a832e16f6 100644 --- a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php @@ -8,15 +8,11 @@ namespace Magento\MediaGallery\Plugin\Wysiwyg\Images; -use Magento\Framework\Exception\CouldNotDeleteException; -use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByDirectoryPathInterface; -use Magento\MediaGalleryApi\Model\Asset\Command\GetByPathInterface; -use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByPathInterface; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; use Magento\Cms\Model\Wysiwyg\Images\Storage as StorageSubject; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; use Psr\Log\LoggerInterface; -use Magento\Framework\Exception\ValidatorException; /** * Ensures that metadata is removed from the database when a file is deleted and it is an image @@ -24,20 +20,10 @@ class Storage { /** - * @var GetByPathInterface - */ - private $getMediaAssetByPath; - - /** - * @var DeleteByPathInterface + * @var DeleteAssetsByPathsInterface */ private $deleteMediaAssetByPath; - /** - * @var DeleteByDirectoryPathInterface - */ - private $deleteMediaAssetByDirectoryPath; - /** * @var Filesystem */ @@ -51,22 +37,16 @@ class Storage /** * Storage constructor. * - * @param GetByPathInterface $getMediaAssetByPath - * @param DeleteByPathInterface $deleteMediaAssetByPath - * @param DeleteByDirectoryPathInterface $deleteByDirectoryPath + * @param DeleteAssetsByPathsInterface $deleteMediaAssetByPath * @param Filesystem $filesystem * @param LoggerInterface $logger */ public function __construct( - GetByPathInterface $getMediaAssetByPath, - DeleteByPathInterface $deleteMediaAssetByPath, - DeleteByDirectoryPathInterface $deleteByDirectoryPath, + DeleteAssetsByPathsInterface $deleteMediaAssetByPath, Filesystem $filesystem, LoggerInterface $logger ) { - $this->getMediaAssetByPath = $getMediaAssetByPath; $this->deleteMediaAssetByPath = $deleteMediaAssetByPath; - $this->deleteMediaAssetByDirectoryPath = $deleteByDirectoryPath; $this->filesystem = $filesystem; $this->logger = $logger; } @@ -88,13 +68,13 @@ public function afterDeleteFile(StorageSubject $subject, StorageSubject $result, return $result; } - $relativePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getRelativePath($target); + $relativePath = $this->getMediaDirectoryRelativePath($target); if (!$relativePath) { return $result; } try { - $this->deleteMediaAssetByPath->execute($relativePath); + $this->deleteMediaAssetByPath->execute([$relativePath]); } catch (\Exception $exception) { $this->logger->critical($exception); } @@ -120,13 +100,28 @@ public function afterDeleteDirectory(StorageSubject $subject, $result, $path) } try { - $mediaDirectoryRead = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); - $relativePath = $mediaDirectoryRead->getRelativePath($path); - $this->deleteMediaAssetByDirectoryPath->execute($relativePath); - } catch (ValidatorException $exception) { + $this->deleteMediaAssetByPath->execute( + [ + $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getRelativePath($path) + ] + ); + } catch (\Exception $exception) { $this->logger->critical($exception); } return $result; } + + /** + * Get path relative to media directory + * + * @param string $path + * @return string + */ + private function getMediaDirectoryRelativePath(string $path): string + { + $relativePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getRelativePath($path); + + return ($relativePath && false === strpos($relativePath, '/')) ? '/' . $relativePath : $relativePath; + } } diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php index 49a5421e623a5..834e8027584dc 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php @@ -23,9 +23,19 @@ */ class GetByIdExceptionDuringMediaAssetInitializationTest extends \PHPUnit\Framework\TestCase { - private const MEDIA_ASSET_STUB_ID = 1; - - private const MEDIA_ASSET_DATA = ['id' => 1]; + private const MEDIA_ASSET_STUB_ID = 45; + private const MEDIA_ASSET_DATA = [ + 'id' => 45, + 'path' => 'img.jpg', + 'title' => 'Img', + 'source' => 'Adobe Stock', + 'content_type' => 'image/jpeg', + 'width' => 420, + 'height' => 240, + 'size' => 12877, + 'created_at' => '2020', + 'updated_at' => '2020' + ]; /** * @var GetById|MockObject @@ -47,11 +57,6 @@ class GetByIdExceptionDuringMediaAssetInitializationTest extends \PHPUnit\Framew */ private $selectStub; - /** - * @var Statement|MockObject - */ - private $statementMock; - /** * @var LoggerInterface|MockObject */ @@ -81,8 +86,6 @@ protected function setUp(): void $this->selectStub->method('from')->willReturnSelf(); $this->selectStub->method('where')->willReturnSelf(); $this->adapter->method('select')->willReturn($this->selectStub); - - $this->statementMock = $this->getMockBuilder(\Zend_Db_Statement_Interface::class)->getMock(); } /** @@ -90,10 +93,14 @@ protected function setUp(): void */ public function testErrorDuringMediaAssetInitializationException(): void { - $this->statementMock->method('fetch')->willReturn(self::MEDIA_ASSET_DATA); - $this->adapter->method('query')->willReturn($this->statementMock); - - $this->assetFactory->expects($this->once())->method('create')->willThrowException(new \Exception()); + $statementMock = $this->createMock(\Zend_Db_Statement_Interface::class); + $statementMock->method('fetch') + ->willReturn(self::MEDIA_ASSET_DATA); + $this->adapter->method('query')->willReturn($statementMock); + + $this->assetFactory->expects($this->once()) + ->method('create') + ->willThrowException(new \Exception()); $this->expectException(IntegrationException::class); $this->logger->expects($this->any()) diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php index f76552487e0f7..19c295424cbf9 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php @@ -23,9 +23,19 @@ */ class GetByIdExceptionOnGetDataTest extends \PHPUnit\Framework\TestCase { - private const MEDIA_ASSET_STUB_ID = 1; - - private const MEDIA_ASSET_DATA = ['id' => 1]; + private const MEDIA_ASSET_STUB_ID = 45; + private const MEDIA_ASSET_DATA = [ + 'id' => 45, + 'path' => 'img.jpg', + 'title' => 'Img', + 'source' => 'Adobe Stock', + 'content_type' => 'image/jpeg', + 'width' => 420, + 'height' => 240, + 'size' => 12877, + 'created_at' => '2020', + 'updated_at' => '2020' + ]; /** * @var GetById|MockObject diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php index c9e8416c53156..410dd5bef18c8 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php @@ -23,9 +23,19 @@ */ class GetByIdSuccessfulTest extends \PHPUnit\Framework\TestCase { - private const MEDIA_ASSET_STUB_ID = 1; - - private const MEDIA_ASSET_DATA = ['id' => 1]; + private const MEDIA_ASSET_STUB_ID = 45; + private const MEDIA_ASSET_DATA = [ + 'id' => 45, + 'path' => 'img.jpg', + 'title' => 'Img', + 'source' => 'Adobe Stock', + 'content_type' => 'image/jpeg', + 'width' => 420, + 'height' => 240, + 'size' => 12877, + 'created_at' => '2020', + 'updated_at' => '2020' + ]; /** * @var GetById|MockObject diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/SaveTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/SaveTest.php index 2f736fb832eac..6f82d2f2a5cb3 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/SaveTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/SaveTest.php @@ -9,7 +9,6 @@ use Magento\MediaGallery\Model\Asset\Command\Save; use Magento\MediaGalleryApi\Api\Data\AssetInterface; -use Magento\MediaGalleryApi\Model\DataExtractorInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Adapter\Pdo\Mysql; @@ -17,6 +16,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Magento\Framework\Reflection\DataObjectProcessor; use Psr\Log\LoggerInterface; /** @@ -24,7 +24,6 @@ */ class SaveTest extends TestCase { - /** * Constant for tablename of media gallery assets */ @@ -49,12 +48,14 @@ class SaveTest extends TestCase * Constant for image data */ private const IMAGE_DATA = [ + 'id' => null, 'path' => '/test/path', 'title' => 'Test Title', 'source' => 'Adobe Stock', 'content_type' => 'image/jpeg', 'height' => 4863, - 'width' => 12129 + 'width' => 12129, + 'size' => 300, ]; /** @@ -63,14 +64,14 @@ class SaveTest extends TestCase private $resourceConnectionMock; /** - * @var MockObject | DataExtractorInterface + * @var MockObject | LoggerInterface */ private $loggerMock; /** - * @var MockObject | LoggerInterface + * @var MockObject | DataObjectProcessor */ - private $extractorMock; + private $objectProcessor; /** * @var MockObject | AdapterInterface @@ -97,7 +98,7 @@ protected function setUp(): void $this->mediaAssetMock = $this->createMock(AssetInterface::class); /* Save constructor mocks */ - $this->extractorMock = $this->createMock(DataExtractorInterface::class); + $this->objectProcessor = $this->createMock(DataObjectProcessor::class); $this->loggerMock = $this->createMock(LoggerInterface::class); $this->resourceConnectionMock = $this->createConfiguredMock( ResourceConnection::class, @@ -112,7 +113,7 @@ protected function setUp(): void Save::class, [ 'resourceConnection' => $this->resourceConnectionMock, - 'extractor' => $this->extractorMock, + 'objectProcessor' => $this->objectProcessor, 'logger' => $this->loggerMock ] ); @@ -126,11 +127,16 @@ public function testSuccessfulExecute(): void $this->resourceConnectionMock->expects(self::once())->method('getConnection'); $this->resourceConnectionMock->expects(self::once())->method('getTableName'); - $this->extractorMock - ->expects(self::once()) - ->method('extract') - ->with($this->mediaAssetMock, AssetInterface::class) - ->willReturn(self::IMAGE_DATA); + $this->mediaAssetMock->expects(self::once())->method('getId')->willReturn(self::IMAGE_DATA['id']); + $this->mediaAssetMock->expects(self::once())->method('getPath')->willReturn(self::IMAGE_DATA['path']); + $this->mediaAssetMock->expects(self::once())->method('getTitle')->willReturn(self::IMAGE_DATA['title']); + $this->mediaAssetMock->expects(self::once())->method('getSource')->willReturn(self::IMAGE_DATA['source']); + $this->mediaAssetMock->expects(self::once())->method('getWidth')->willReturn(self::IMAGE_DATA['width']); + $this->mediaAssetMock->expects(self::once())->method('getHeight')->willReturn(self::IMAGE_DATA['height']); + $this->mediaAssetMock->expects(self::once())->method('getSize')->willReturn(self::IMAGE_DATA['size']); + $this->mediaAssetMock->expects(self::once()) + ->method('getContentType') + ->willReturn(self::IMAGE_DATA['content_type']); $this->adapterMock ->expects(self::once()) @@ -155,11 +161,16 @@ public function testExceptionExecute(): void $this->resourceConnectionMock->expects(self::once())->method('getConnection'); $this->resourceConnectionMock->expects(self::once())->method('getTableName'); - $this->extractorMock - ->expects(self::once()) - ->method('extract') - ->with($this->mediaAssetMock, AssetInterface::class) - ->willReturn(self::IMAGE_DATA); + $this->mediaAssetMock->expects(self::once())->method('getId')->willReturn(self::IMAGE_DATA['id']); + $this->mediaAssetMock->expects(self::once())->method('getPath')->willReturn(self::IMAGE_DATA['path']); + $this->mediaAssetMock->expects(self::once())->method('getTitle')->willReturn(self::IMAGE_DATA['title']); + $this->mediaAssetMock->expects(self::once())->method('getSource')->willReturn(self::IMAGE_DATA['source']); + $this->mediaAssetMock->expects(self::once())->method('getWidth')->willReturn(self::IMAGE_DATA['width']); + $this->mediaAssetMock->expects(self::once())->method('getHeight')->willReturn(self::IMAGE_DATA['height']); + $this->mediaAssetMock->expects(self::once())->method('getSize')->willReturn(self::IMAGE_DATA['size']); + $this->mediaAssetMock->expects(self::once()) + ->method('getContentType') + ->willReturn(self::IMAGE_DATA['content_type']); $this->adapterMock ->expects(self::once()) diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/DataExtractorTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/DataExtractorTest.php deleted file mode 100644 index f70e4ccdae22c..0000000000000 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/DataExtractorTest.php +++ /dev/null @@ -1,161 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\MediaGallery\Test\Unit\Model; - -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\MediaGallery\Model\Asset; -use Magento\MediaGallery\Model\DataExtractor; -use Magento\MediaGallery\Model\Keyword; -use Magento\MediaGalleryApi\Api\Data\AssetInterface; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; - -class DataExtractorTest extends TestCase -{ - /** - * @var DataExtractor|MockObject - */ - private $dataExtractor; - - /** - * Initialize basic test class mocks - */ - protected function setUp(): void - { - $this->dataExtractor = new DataExtractor(); - } - - /** - * Test extract object data by interface - * - * @dataProvider assetProvider - * - * @param string $class - * @param string|null $interfaceClass - * @param array $expectedData - * - * @throws \ReflectionException - */ - public function testExtractData(string $class, $interfaceClass, array $expectedData): void - { - $data = []; - foreach ($expectedData as $expectedDataKey => $expectedDataItem) { - $data[$expectedDataKey] = $expectedDataItem['value']; - } - $model = (new ObjectManager($this))->getObject( - $class, - [ - 'data' => $data, - ] - ); - if ($interfaceClass) { - $receivedData = $this->dataExtractor->extract($model, $interfaceClass); - $this->checkAssetValues($expectedData, $receivedData, $model); - } else { - $receivedData = $this->dataExtractor->extract($model); - $this->checkKeyWordValues($expectedData, $receivedData, $model); - } - } - - /** - * @param array $expectedData - * @param array $data - * @param object $model - */ - private function checkAssetValues(array $expectedData, array $data, $model) - { - foreach ($expectedData as $expectedDataKey => $expectedDataItem) { - $this->assertEquals($data[$expectedDataKey] ?? null, $model->{$expectedDataItem['method']}()); - $this->assertEquals($data[$expectedDataKey] ?? null, $expectedDataItem['value']); - } - $this->assertEquals(array_keys($expectedData), array_keys($data)); - } - - /** - * @param array $expectedData - * @param array $data - * @param object $model - */ - private function checkKeyWordValues(array $expectedData, array $data, $model) - { - foreach ($expectedData as $expectedDataKey => $expectedDataItem) { - $this->assertEquals($data[$expectedDataKey] ?? null, $model->{$expectedDataItem['method']}()); - $this->assertEquals($data[$expectedDataKey] ?? null, $expectedDataItem['value']); - } - $this->assertEquals(array_keys($expectedData), array_keys(array_slice($data, 0, 2))); - } - - /** - * @return array - */ - public function assetProvider() - { - return [ - 'Asset conversion with interface' => [ - Asset::class, - AssetInterface::class, - [ - 'id' => [ - 'value' => 2, - 'method' => 'getId', - ], - 'path' => [ - 'value' => 'path', - 'method' => 'getPath', - ], - 'title' => [ - 'value' => 'title', - 'method' => 'getTitle', - ], - 'source' => [ - 'value' => 'source', - 'method' => 'getSource', - ], - 'content_type' => [ - 'value' => 'content_type', - 'method' => 'getContentType', - ], - 'height' => [ - 'value' => 4, - 'method' => 'getHeight', - ], - 'width' => [ - 'value' => 3, - 'method' => 'getWidth', - ], - 'size' => [ - 'value' => 300, - 'method' => 'getSize', - ], - 'created_at' => [ - 'value' => '2019-11-28 10:40:09', - 'method' => 'getCreatedAt', - ], - 'updated_at' => [ - 'value' => '2019-11-28 10:41:08', - 'method' => 'getUpdatedAt', - ], - ], - ], - 'Keyword conversion without interface' => [ - Keyword::class, - '', - [ - 'id' => [ - 'value' => 2, - 'method' => 'getId', - ], - 'keyword' => [ - 'value' => 'keyword', - 'method' => 'getKeyword', - ], - ], - ] - ]; - } -} diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsBlacklistedTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsBlacklistedTest.php new file mode 100644 index 0000000000000..5b069dcc70030 --- /dev/null +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsBlacklistedTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Test\Unit\Model\Directory; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\MediaGallery\Model\Directory\IsBlacklisted; +use Magento\MediaGalleryApi\Model\BlacklistPatternsConfigInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test for IsBlacklisted + */ +class IsBlacklistedTest extends TestCase +{ + /** + * @var IsBlacklisted + */ + private $object; + + /** + * @var BlacklistPatternsConfigInterface|MockObject + */ + private $config; + + /** + * Initialize basic test class mocks + */ + protected function setUp(): void + { + $this->config = $this->getMockBuilder(BlacklistPatternsConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->config->expects($this->at(0))->method('get')->willReturn([ + 'tmp' => '/pub\/media\/tmp/', + 'captcha' => '/pub\/media\/captcha/' + ]); + $this->object = (new ObjectManager($this))->getObject(IsBlacklisted::class, [ + 'config' => $this->config + ]); + } + + /** + * Test if the directory path is blacklisted + * + * @param string $path + * @param bool $isExcluded + * @dataProvider pathsProvider + */ + public function testExecute(string $path, bool $isExcluded): void + { + $this->assertEquals($isExcluded, $this->object->execute($path)); + } + + /** + * Data provider for testIsExcluded + * + * @return array + */ + public function pathsProvider() + { + return [ + ['/var/www/html/pub/media/tmp/somedir', true], + ['/var/www/html/pub/media/wysiwyg/somedir', false] + ]; + } +} diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/GetAssetKeywordsTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/GetAssetKeywordsTest.php index 2ccac4eac8343..930068aebb3fe 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/GetAssetKeywordsTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/GetAssetKeywordsTest.php @@ -19,7 +19,7 @@ use Psr\Log\LoggerInterface; /** - * GetAssetKeywordsTest + * Test for GetAssetKeywords */ class GetAssetKeywordsTest extends TestCase { @@ -83,9 +83,32 @@ public function testFind(array $databaseQueryResult, int $expectedNumberOfFoundK public function casesProvider(): array { return [ - 'not_found' => [[],0], - 'find_one_keyword' => [['keywordRawData'],1], - 'find_several_keywords' => [['keywordRawData', 'keywordRawData'],2], + 'not_found' => [ + [], + 0 + ], + 'find_one_keyword' => [ + [ + [ + 'id' => 1, + 'keyword' => 'keywordRawData' + ] + ], + 1 + ], + 'find_several_keywords' => [ + [ + [ + 'id' => 1, + 'keyword' => 'keywordRawData' + ], + [ + 'id' => 2, + 'keyword' => 'keywordRawData2' + ] + ], + 2 + ], ]; } diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetKeywordsTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetKeywordsTest.php index a55c60024c08d..302c8c8774bc6 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetKeywordsTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetKeywordsTest.php @@ -13,13 +13,13 @@ use Magento\Framework\DB\Select; use Magento\Framework\Exception\CouldNotSaveException; use Magento\MediaGallery\Model\Keyword\Command\SaveAssetKeywords; -use Magento\MediaGallery\Model\Keyword\Command\SaveAssetLinks; +use Magento\MediaGallery\Model\ResourceModel\Keyword\SaveAssetLinks; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; /** - * SaveAssetKeywordsTest. + * Test for SaveAssetKeywords */ class SaveAssetKeywordsTest extends TestCase { diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetLinksTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetLinksTest.php index 2981c534586e2..fe9202353fa2e 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetLinksTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Keyword/Command/SaveAssetLinksTest.php @@ -7,16 +7,16 @@ namespace Magento\MediaGallery\Test\Unit\Model\Keyword\Command; -use Magento\MediaGallery\Model\Keyword\Command\SaveAssetLinks; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\MediaGallery\Model\ResourceModel\Keyword\SaveAssetLinks; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; /** - * SaveAssetLinksTest. + * Test for SaveAssetLinks */ class SaveAssetLinksTest extends TestCase { diff --git a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php b/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php deleted file mode 100644 index 4ac448733c47f..0000000000000 --- a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php +++ /dev/null @@ -1,145 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\MediaGallery\Test\Unit\Plugin\Images; - -use Magento\Cms\Model\Wysiwyg\Images\Storage as StorageSubject; -use Magento\Framework\Exception\ValidatorException; -use Magento\Framework\Filesystem; -use Magento\Framework\Filesystem\Directory\ReadInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\MediaGallery\Model\Asset\Command\DeleteByDirectoryPath; -use Magento\MediaGallery\Plugin\Wysiwyg\Images\Storage as StoragePlugin; -use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByDirectoryPathInterface; -use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByPathInterface; -use Magento\MediaGalleryApi\Model\Asset\Command\GetByPathInterface; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; - -/** - * Test the DeleteByDirectoryPath command model - */ -class StorageTest extends TestCase -{ - private const NON_STRING_PATH = 2020; - private const NON_EXISTENT_PATH = 'non_existent'; - private const INVALID_PATH = '&&'; - private const VALID_PATH = 'test-directory-path/'; - - /** - * @var GetByPathInterface|MockObject - */ - private $getMediaAssetByPath; - - /** - * @var DeleteByPathInterface|MockObject - */ - private $deleteMediaAssetByPath; - - /** - * @var DeleteByDirectoryPathInterface|MockObject - */ - private $deleteMediaAssetByDirectoryPath; - - /** - * @var Filesystem|MockObject - */ - private $filesystem; - - /** - * @var LoggerInterface|MockObject - */ - private $logger; - - /** - * @var StoragePlugin - */ - private $storagePlugin; - - /** - * Initialize basic test class mocks - */ - protected function setUp(): void - { - $this->logger = $this->createMock(LoggerInterface::class); - $this->getMediaAssetByPath = $this->createMock(GetByPathInterface::class); - $this->deleteMediaAssetByPath = $this->createMock(DeleteByPathInterface::class); - $this->deleteMediaAssetByDirectoryPath = $this->createMock(DeleteByDirectoryPath::class); - $this->filesystem = $this->createMock(Filesystem::class); - - $this->storagePlugin = (new ObjectManager($this))->getObject( - StoragePlugin::class, - [ - 'getMediaAssetByPath' => $this->getMediaAssetByPath, - 'deleteMediaAssetByPath' => $this->deleteMediaAssetByPath, - 'deleteByDirectoryPath' => $this->deleteMediaAssetByDirectoryPath, - 'filesystem' => $this->filesystem, - 'logger' => $this->logger - ] - ); - } - - /** - * @param string $path - * - * @dataProvider pathPathDataProvider - */ - public function testAfterDeleteDirectory(string $path): void - { - /** @var StorageSubject|MockObject $storageSubject */ - $storageSubject = $this->getMockBuilder(StorageSubject::class) - ->disableOriginalConstructor() - ->getMock(); - $directoryRead = $this->createMock(ReadInterface::class); - $this->filesystem->expects($this->any()) - ->method('getDirectoryRead') - ->willReturn($directoryRead); - - switch ($path) { - case self::NON_STRING_PATH: - $result = $this->storagePlugin->afterDeleteDirectory($storageSubject, null, (int)$path); - self::assertNull($result); - break; - case self::INVALID_PATH: - $exception = new ValidatorException(__('Path cannot be used with directory')); - $directoryRead->expects($this->once()) - ->method('getRelativePath') - ->with($path) - ->willThrowException($exception); - $this->logger->expects($this->once()) - ->method('critical') - ->with($exception); - $this->storagePlugin->afterDeleteDirectory($storageSubject, null, $path); - break; - case self::VALID_PATH: - $directoryRead->expects($this->once()) - ->method('getRelativePath') - ->with($path) - ->willReturn($path); - $this->deleteMediaAssetByDirectoryPath->expects($this->once()) - ->method('execute') - ->with($path); - $this->storagePlugin->afterDeleteDirectory($storageSubject, null, $path); - break; - } - } - - /** - * Data provider for path - * - * @return array - */ - public function pathPathDataProvider(): array - { - return [ - 'Non string path' => [2020], - 'Invalid path' => [self::INVALID_PATH], - 'Existent path' => [self::VALID_PATH] - ]; - } -} diff --git a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Wysiwyg/Images/StorageTest.php b/app/code/Magento/MediaGallery/Test/Unit/Plugin/Wysiwyg/Images/StorageTest.php index 0c653a9543b19..ca8178384fc50 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Wysiwyg/Images/StorageTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Plugin/Wysiwyg/Images/StorageTest.php @@ -9,12 +9,11 @@ namespace Magento\MediaGallery\Test\Unit\Plugin\Wysiwyg\Images; use Magento\Cms\Model\Wysiwyg\Images\Storage as StorageSubject; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\MediaGallery\Plugin\Wysiwyg\Images\Storage; -use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByPathInterface; -use Magento\MediaGalleryApi\Model\Asset\Command\GetByPathInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\MediaGallery\Plugin\Wysiwyg\Images\Storage as StoragePlugin; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; @@ -24,21 +23,14 @@ */ class StorageTest extends TestCase { - const STUB_TARGET = '/stub/test.png'; - const STUB_RELATIVE_PATH = 'test.png'; + private const STUB_TARGET = '/stub/test.png'; + private const STUB_RELATIVE_PATH = 'test.png'; + private const NON_STRING_PATH = 2020; + private const INVALID_PATH = '&&'; + private const VALID_PATH = 'test-directory-path/'; /** - * @var Storage - */ - private $storage; - - /** - * @var GetByPathInterface|MockObject - */ - private $getMediaAssetByPathMock; - - /** - * @var DeleteByPathInterface|MockObject + * @var DeleteAssetsByPathsInterface|MockObject */ private $deleteMediaAssetByPathMock; @@ -63,30 +55,24 @@ class StorageTest extends TestCase private $readInterfaceMock; /** - * @inheritDoc + * @var StoragePlugin */ - protected function setUp() + private $storage; + + /** + * @inheritdoc + */ + protected function setUp(): void { - $this->storageSubjectMock = $this->createMock(StorageSubject::class); + $this->deleteMediaAssetByPathMock = $this->createMock(DeleteAssetsByPathsInterface::class); $this->filesystemMock = $this->createMock(Filesystem::class); - $this->getMediaAssetByPathMock = $this->createMock(GetByPathInterface::class); - $this->deleteMediaAssetByPathMock = $this->getMockBuilder(DeleteByPathInterface::class) - ->disableOriginalConstructor() - ->setMethods(['execute']) - ->getMockForAbstractClass(); - $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) - ->disableOriginalConstructor() - ->setMethods(['critical']) - ->getMockForAbstractClass(); - $this->readInterfaceMock = $this->getMockBuilder(ReadInterface::class) - ->disableOriginalConstructor() - ->setMethods(['getRelativePath']) - ->getMockForAbstractClass(); - - $this->storage = (new ObjectManagerHelper($this))->getObject( - Storage::class, + $this->loggerMock = $this->createMock(LoggerInterface::class); + $this->storageSubjectMock = $this->createMock(StorageSubject::class); + $this->readInterfaceMock = $this->createMock(ReadInterface::class); + + $this->storage = (new ObjectManager($this))->getObject( + StoragePlugin::class, [ - 'getMediaAssetByPath' => $this->getMediaAssetByPathMock, 'deleteMediaAssetByPath' => $this->deleteMediaAssetByPathMock, 'filesystem' => $this->filesystemMock, 'logger' => $this->loggerMock @@ -94,6 +80,59 @@ protected function setUp() ); } + /** + * @param string $path + * + * @dataProvider pathPathDataProvider + */ + public function testAfterDeleteDirectory(string $path): void + { + $directoryRead = $this->createMock(ReadInterface::class); + $this->filesystemMock->expects($this->any()) + ->method('getDirectoryRead') + ->willReturn($directoryRead); + + switch ($path) { + case self::NON_STRING_PATH: + $result = $this->storage->afterDeleteDirectory($this->storageSubjectMock, null, (int)$path); + self::assertNull($result); + break; + case self::INVALID_PATH: + $directoryRead->expects($this->once()) + ->method('getRelativePath') + ->with($path) + ->willThrowException(new \Exception()); + $this->loggerMock->expects($this->once()) + ->method('critical'); + $this->storage->afterDeleteDirectory($this->storageSubjectMock, null, $path); + break; + case self::VALID_PATH: + $directoryRead->expects($this->once()) + ->method('getRelativePath') + ->with($path) + ->willReturn($path); + $this->deleteMediaAssetByPathMock->expects($this->once()) + ->method('execute') + ->with([$path]); + $this->storage->afterDeleteDirectory($this->storageSubjectMock, null, $path); + break; + } + } + + /** + * Data provider for path + * + * @return array + */ + public function pathPathDataProvider(): array + { + return [ + 'Non string path' => [2020], + 'Invalid path' => [self::INVALID_PATH], + 'Existent path' => [self::VALID_PATH] + ]; + } + /** * Test case when an exception is thrown during the method execution. */ diff --git a/app/code/Magento/MediaGallery/composer.json b/app/code/Magento/MediaGallery/composer.json index 977277d993061..686cec00b7f55 100644 --- a/app/code/Magento/MediaGallery/composer.json +++ b/app/code/Magento/MediaGallery/composer.json @@ -5,8 +5,7 @@ "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", - "magento/module-cms": "*", - "magento/module-catalog": "*" + "magento/module-cms": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/MediaGallery/etc/db_schema.xml b/app/code/Magento/MediaGallery/etc/db_schema.xml index 8bcad28de2a8e..13e619dd9e74a 100644 --- a/app/code/Magento/MediaGallery/etc/db_schema.xml +++ b/app/code/Magento/MediaGallery/etc/db_schema.xml @@ -26,6 +26,9 @@ <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> </table> <table name="media_gallery_keyword" resource="default" engine="innodb" comment="Media Gallery Keyword"> <column xsi:type="int" name="id" padding="10" unsigned="true" nullable="false" identity="true" comment="Keyword ID"/> diff --git a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json index 9e187e6dea4a5..8f5098caa9753 100644 --- a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json +++ b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json @@ -14,7 +14,8 @@ }, "index": { "MEDIA_GALLERY_ID": true, - "MEDIA_GALLERY_ASSET_ID": true + "MEDIA_GALLERY_ASSET_ID": true, + "MEDIA_GALLERY_ASSET_TITLE": true }, "constraint": { "MEDIA_GALLERY_ID_PATH_TITLE_CONTENT_TYPE_WIDTH_HEIGHT": true, diff --git a/app/code/Magento/MediaGallery/etc/di.xml b/app/code/Magento/MediaGallery/etc/di.xml index 24ed42b2b06dd..a85c26e275226 100644 --- a/app/code/Magento/MediaGallery/etc/di.xml +++ b/app/code/Magento/MediaGallery/etc/di.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\MediaGalleryApi\Api\Data\KeywordInterface" type="Magento\MediaGallery\Model\Keyword"/> <preference for="Magento\MediaGalleryApi\Api\Data\AssetInterface" type="Magento\MediaGallery\Model\Asset"/> - + <preference for="Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterface" type="Magento\MediaGallery\Model\AssetKeywords"/> <preference for="Magento\MediaGalleryApi\Model\Asset\Command\GetByIdInterface" type="Magento\MediaGallery\Model\Asset\Command\GetById"/> <preference for="Magento\MediaGalleryApi\Model\Asset\Command\SaveInterface" type="Magento\MediaGallery\Model\Asset\Command\Save"/> <preference for="Magento\MediaGalleryApi\Model\Asset\Command\GetByPathInterface" type="Magento\MediaGallery\Model\Asset\Command\GetByPath"/> @@ -17,16 +17,44 @@ <preference for="Magento\MediaGalleryApi\Model\Keyword\Command\GetAssetKeywordsInterface" type="Magento\MediaGallery\Model\Keyword\Command\GetAssetKeywords"/> <preference for="Magento\MediaGalleryApi\Model\Keyword\Command\SaveAssetKeywordsInterface" type="Magento\MediaGallery\Model\Keyword\Command\SaveAssetKeywords"/> - <preference for="Magento\MediaGalleryApi\Model\Keyword\Command\SaveAssetLinksInterface" type="Magento\MediaGallery\Model\Keyword\Command\SaveAssetLinks"/> - <preference for="Magento\MediaGalleryApi\Model\DataExtractorInterface" type="Magento\MediaGallery\Model\DataExtractor"/> + <preference for="Magento\MediaGalleryApi\Api\CreateDirectoriesByPathsInterface" type="Magento\MediaGallery\Model\Directory\Command\CreateByPaths"/> + <preference for="Magento\MediaGalleryApi\Api\DeleteDirectoriesByPathsInterface" type="Magento\MediaGallery\Model\Directory\Command\DeleteByPaths"/> + + <preference for="Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface" type="Magento\MediaGallery\Model\Directory\IsBlacklisted"/> + + <preference for="Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface" type="Magento\MediaGallery\Model\ResourceModel\DeleteAssetsByPaths"/> + <preference for="Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface" type="Magento\MediaGallery\Model\ResourceModel\GetAssetsByIds"/> + <preference for="Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface" type="Magento\MediaGallery\Model\ResourceModel\GetAssetsByPaths"/> + <preference for="Magento\MediaGalleryApi\Api\SaveAssetsInterface" type="Magento\MediaGallery\Model\ResourceModel\SaveAssets"/> + <preference for="Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface" type="Magento\MediaGallery\Model\ResourceModel\Keyword\GetAssetsKeywords"/> + <preference for="Magento\MediaGalleryApi\Api\SaveAssetsKeywordsInterface" type="Magento\MediaGallery\Model\ResourceModel\Keyword\SaveAssetsKeywords"/> - <type name="Magento\Catalog\Model\Product\Gallery\Processor"> - <plugin name="media_gallery_image_remove_metadata" type="Magento\MediaGallery\Plugin\Product\Gallery\Processor" - sortOrder="10" disabled="false"/> - </type> <type name="Magento\Cms\Model\Wysiwyg\Images\Storage"> <plugin name="media_gallery_image_remove_metadata_after_wysiwyg" type="Magento\MediaGallery\Plugin\Wysiwyg\Images\Storage" sortOrder="10" disabled="false"/> </type> + <virtualType name="Magento\MediaGallery\Model\Directory\Config\Reader" type="Magento\Framework\Config\Reader\Filesystem"> + <arguments> + <argument name="fileName" xsi:type="string">directory.xml</argument> + <argument name="converter" xsi:type="object">Magento\MediaGallery\Model\Directory\Config\Converter</argument> + <argument name="schemaLocator" xsi:type="object">Magento\MediaGallery\Model\Directory\Config\SchemaLocator</argument> + <argument name="idAttributes" xsi:type="array"> + <item name="/config/blacklist/patterns/pattern" xsi:type="string">name</item> + </argument> + </arguments> + </virtualType> + <virtualType name="Magento\MediaGallery\Model\Directory\Config\Data" type="Magento\Framework\Config\Data"> + <arguments> + <argument name="reader" xsi:type="object">Magento\MediaGallery\Model\Directory\Config\Reader</argument> + <argument name="cacheId" xsi:type="string">Media_Gallery_Patterns_CacheId</argument> + </arguments> + </virtualType> + <type name="Magento\MediaGallery\Model\Directory\BlacklistPatternsConfig"> + <arguments> + <argument name="data" xsi:type="object">Magento\MediaGallery\Model\Directory\Config\Data</argument> + </arguments> + </type> + + <preference for="Magento\MediaGalleryApi\Model\BlacklistPatternsConfigInterface" type="Magento\MediaGallery\Model\Directory\BlacklistPatternsConfig"/> </config> diff --git a/app/code/Magento/MediaGallery/etc/directory.xml b/app/code/Magento/MediaGallery/etc/directory.xml new file mode 100644 index 0000000000000..92f50b2dd0a30 --- /dev/null +++ b/app/code/Magento/MediaGallery/etc/directory.xml @@ -0,0 +1,21 @@ +<?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:module:Magento_MediaGalleryApi:etc/directory.xsd"> + <blacklist> + <patterns> + <pattern name="captcha">/^captcha/</pattern> + <pattern name="customer">/^customer/</pattern> + <pattern name="downloadable">/^downloadable/</pattern> + <pattern name="import">/^import/</pattern> + <pattern name="theme">/^theme/</pattern> + <pattern name="theme_customization">/^theme_customization/</pattern> + <pattern name="tmp">/^tmp/</pattern> + <pattern name="directories-with-dots">/^\./</pattern> + </patterns> + </blacklist> +</config> diff --git a/app/code/Magento/MediaGalleryApi/Api/CreateDirectoriesByPathsInterface.php b/app/code/Magento/MediaGalleryApi/Api/CreateDirectoriesByPathsInterface.php new file mode 100644 index 0000000000000..a0a1ec891237f --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/CreateDirectoriesByPathsInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Create folders by provided paths + * @api + */ +interface CreateDirectoriesByPathsInterface +{ + /** + * Create new directories by provided paths + * + * @param string[] $paths + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function execute(array $paths): void; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php b/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php index 0f4b78a6dc603..5df420a274933 100644 --- a/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php +++ b/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php @@ -13,6 +13,7 @@ /** * Represents a media gallery asset which contains information about a media asset entity such * as path to the media storage, media asset title and its content type, etc. + * @api */ interface AssetInterface extends ExtensibleDataInterface { @@ -38,7 +39,7 @@ public function getPath(): string; public function getTitle(): ?string; /** - * Get source of the file + * Get the name of the channel/stock/integration file was retrieved from. null if not identified. * * @return string|null */ @@ -75,29 +76,29 @@ public function getSize(): int; /** * Get created at * - * @return string + * @return string|null */ - public function getCreatedAt(): string; + public function getCreatedAt(): ?string; /** * Get updated at * - * @return string + * @return string|null */ - public function getUpdatedAt(): string; + public function getUpdatedAt(): ?string; /** * Retrieve existing extension attributes object or create a new one. * * @return \Magento\MediaGalleryApi\Api\Data\AssetExtensionInterface|null */ - public function getExtensionAttributes(): AssetExtensionInterface; + public function getExtensionAttributes(): ?AssetExtensionInterface; /** * Set extension attributes * - * @param \Magento\MediaGalleryApi\Api\Data\AssetExtensionInterface $extensionAttributes + * @param \Magento\MediaGalleryApi\Api\Data\AssetExtensionInterface|null $extensionAttributes * @return void */ - public function setExtensionAttributes(AssetExtensionInterface $extensionAttributes): void; + public function setExtensionAttributes(?AssetExtensionInterface $extensionAttributes): void; } diff --git a/app/code/Magento/MediaGalleryApi/Api/Data/AssetKeywordsInterface.php b/app/code/Magento/MediaGalleryApi/Api/Data/AssetKeywordsInterface.php new file mode 100644 index 0000000000000..1c18225470493 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/Data/AssetKeywordsInterface.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api\Data; + +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsExtensionInterface; +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Interface for asset's keywords aggregation + * @api + */ +interface AssetKeywordsInterface extends ExtensibleDataInterface +{ + /** + * Get ID + * + * @return int + */ + public function getAssetId(): int; + + /** + * Get the keyword + * + * @return KeywordInterface[] + */ + public function getKeywords(): array; + + /** + * Get extension attributes + * + * @return \Magento\MediaGalleryApi\Api\Data\AssetKeywordsExtensionInterface|null + */ + public function getExtensionAttributes(): ?AssetKeywordsExtensionInterface; + + /** + * Set extension attributes + * + * @param \Magento\MediaGalleryApi\Api\Data\AssetKeywordsExtensionInterface|null $extensionAttributes + * @return void + */ + public function setExtensionAttributes(?AssetKeywordsExtensionInterface $extensionAttributes): void; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/Data/KeywordInterface.php b/app/code/Magento/MediaGalleryApi/Api/Data/KeywordInterface.php index ae3b7dbd76291..3cba118e03a1a 100644 --- a/app/code/Magento/MediaGalleryApi/Api/Data/KeywordInterface.php +++ b/app/code/Magento/MediaGalleryApi/Api/Data/KeywordInterface.php @@ -12,6 +12,7 @@ /** * Represents a media gallery keyword. This object contains information about a media asset keyword entity. + * @api */ interface KeywordInterface extends ExtensibleDataInterface { @@ -34,13 +35,13 @@ public function getKeyword(): string; * * @return \Magento\MediaGalleryApi\Api\Data\KeywordExtensionInterface|null */ - public function getExtensionAttributes(): KeywordExtensionInterface; + public function getExtensionAttributes(): ?KeywordExtensionInterface; /** * Set extension attributes * - * @param \Magento\MediaGalleryApi\Api\Data\KeywordExtensionInterface $extensionAttributes + * @param \Magento\MediaGalleryApi\Api\Data\KeywordExtensionInterface|null $extensionAttributes * @return void */ - public function setExtensionAttributes(KeywordExtensionInterface $extensionAttributes): void; + public function setExtensionAttributes(?KeywordExtensionInterface $extensionAttributes): void; } diff --git a/app/code/Magento/MediaGalleryApi/Api/DeleteAssetsByPathsInterface.php b/app/code/Magento/MediaGalleryApi/Api/DeleteAssetsByPathsInterface.php new file mode 100644 index 0000000000000..5370235a31b95 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/DeleteAssetsByPathsInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Delete media assets by exact or directory paths + * @api + */ +interface DeleteAssetsByPathsInterface +{ + /** + * Delete media assets by paths. Removes all the assets which paths start with provided paths + * + * @param string[] $paths + * @return void + */ + public function execute(array $paths): void; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/DeleteDirectoriesByPathsInterface.php b/app/code/Magento/MediaGalleryApi/Api/DeleteDirectoriesByPathsInterface.php new file mode 100644 index 0000000000000..fe3be88fa0073 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/DeleteDirectoriesByPathsInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Delete folders by provided paths + * @api + */ +interface DeleteDirectoriesByPathsInterface +{ + /** + * Deletes the existing folders + * + * @param string[] $paths + * @return void + * @throws \Magento\Framework\Exception\CouldNotDeleteException + */ + public function execute(array $paths): void; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/GetAssetsByIdsInterface.php b/app/code/Magento/MediaGalleryApi/Api/GetAssetsByIdsInterface.php new file mode 100644 index 0000000000000..5df6722a190d4 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/GetAssetsByIdsInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Get media gallery assets by id attribute + * @api + */ +interface GetAssetsByIdsInterface +{ + /** + * Get media asset by id + * + * @param int[] $ids + * @return \Magento\MediaGalleryApi\Api\Data\AssetInterface[] + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(array $ids): array; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/GetAssetsByPathsInterface.php b/app/code/Magento/MediaGalleryApi/Api/GetAssetsByPathsInterface.php new file mode 100644 index 0000000000000..dbaed6e0e9123 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/GetAssetsByPathsInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Get media gallery assets by paths in media storage + * @api + */ +interface GetAssetsByPathsInterface +{ + /** + * Get media asset list + * + * @param string[] $paths + * @return \Magento\MediaGalleryApi\Api\Data\AssetInterface[] + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(array $paths): array; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/GetAssetsKeywordsInterface.php b/app/code/Magento/MediaGalleryApi/Api/GetAssetsKeywordsInterface.php new file mode 100644 index 0000000000000..99b05291f32a0 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/GetAssetsKeywordsInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Get a media gallery asset keywords related to media gallery asset ids provided + * @api + */ +interface GetAssetsKeywordsInterface +{ + /** + * Get assets related keywords + * + * @param int[] $assetIds + * @return \Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterface[] + */ + public function execute(array $assetIds): array; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/IsPathBlacklistedInterface.php b/app/code/Magento/MediaGalleryApi/Api/IsPathBlacklistedInterface.php new file mode 100644 index 0000000000000..cbd23ec3fbde7 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/IsPathBlacklistedInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Check if the path is blacklisted for media gallery. + * + * Directory path may be blacklisted if it's reserved by the system. + * @api + */ +interface IsPathBlacklistedInterface +{ + /** + * Check if the path is excluded from displaying and processing in the media gallery + * + * @param string $path + * @return bool + */ + public function execute(string $path): bool; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/SaveAssetsInterface.php b/app/code/Magento/MediaGalleryApi/Api/SaveAssetsInterface.php new file mode 100644 index 0000000000000..c63f7bd8c0818 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/SaveAssetsInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Save media gallery assets to the database + * @api + */ +interface SaveAssetsInterface +{ + /** + * Save media asset. The saved asset can later be retrieved by path + * + * @param \Magento\MediaGalleryApi\Api\Data\AssetInterface[] $assets + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function execute(array $assets): void; +} diff --git a/app/code/Magento/MediaGalleryApi/Api/SaveAssetsKeywordsInterface.php b/app/code/Magento/MediaGalleryApi/Api/SaveAssetsKeywordsInterface.php new file mode 100644 index 0000000000000..04efe7d32ccc1 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Api/SaveAssetsKeywordsInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryApi\Api; + +/** + * Save keywords related to assets to the database + * @api + */ +interface SaveAssetsKeywordsInterface +{ + /** + * Save assets keywords + * + * @param \Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterface[] $assetKeywords + * @return void + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function execute(array $assetKeywords): void; +} diff --git a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php index e55f7cb714f77..79b209823aeb0 100644 --- a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php +++ b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php @@ -11,6 +11,8 @@ /** * A command represents the media gallery assets delete action. A media gallery asset is filtered by directory * path value. + * @deprecated use \Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface instead + * @see \Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface */ interface DeleteByDirectoryPathInterface { @@ -18,7 +20,6 @@ interface DeleteByDirectoryPathInterface * Delete media assets by directory path * * @param string $directoryPath - * * @return void */ public function execute(string $directoryPath): void; diff --git a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByPathInterface.php b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByPathInterface.php index b3612a67ed536..f33022e75d2fe 100644 --- a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByPathInterface.php +++ b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByPathInterface.php @@ -10,6 +10,8 @@ /** * A command represents the media gallery asset delete action. A media gallery asset is filtered by path value. + * @deprecated use \Magento\MediaGalleryApi\Api\DeleteAssetsByPathInterface instead + * @see \Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface */ interface DeleteByPathInterface { @@ -17,7 +19,6 @@ interface DeleteByPathInterface * Delete media asset by path * * @param string $mediaAssetPath - * * @return void */ public function execute(string $mediaAssetPath): void; diff --git a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByIdInterface.php b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByIdInterface.php index ef2ceb5ffbfe6..65cc2e3eae109 100644 --- a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByIdInterface.php +++ b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByIdInterface.php @@ -10,6 +10,8 @@ /** * A command represents the get media gallery asset by using media gallery asset id as a filter parameter. + * @deprecated use \Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface instead + * @see \Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface */ interface GetByIdInterface { @@ -17,7 +19,6 @@ interface GetByIdInterface * Get media asset by id * * @param int $mediaAssetId - * * @return \Magento\MediaGalleryApi\Api\Data\AssetInterface * @throws \Magento\Framework\Exception\NoSuchEntityException * @throws \Magento\Framework\Exception\IntegrationException diff --git a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByPathInterface.php b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByPathInterface.php index 547b0dc695dae..d8d5b6773fbbc 100644 --- a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByPathInterface.php +++ b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/GetByPathInterface.php @@ -10,15 +10,16 @@ /** * A command represents the get media gallery asset by using media gallery asset path as a filter parameter. + * @deprecated use \Magento\MediaGalleryApi\Api\GetAssetsByPathInterface instead + * @see \Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface */ interface GetByPathInterface { /** * Get media asset list * - * @param string $mediaFilePath - * + * @param string $path * @return \Magento\MediaGalleryApi\Api\Data\AssetInterface */ - public function execute(string $mediaFilePath): \Magento\MediaGalleryApi\Api\Data\AssetInterface; + public function execute(string $path): \Magento\MediaGalleryApi\Api\Data\AssetInterface; } diff --git a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/SaveInterface.php b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/SaveInterface.php index b3e3607e6e822..610ecf0cd22bf 100644 --- a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/SaveInterface.php +++ b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/SaveInterface.php @@ -12,14 +12,15 @@ /** * A command which executes the media gallery asset save operation. + * @deprecated use \Magento\MediaGalleryApi\Api\SaveAssetsInterface instead + * @see \Magento\MediaGalleryApi\Api\SaveAssetsInterface */ interface SaveInterface { /** - * Save media asset + * Save media asset and return the media asset id * * @param \Magento\MediaGalleryApi\Api\Data\AssetInterface $mediaAsset - * * @return int * @throws \Magento\Framework\Exception\CouldNotSaveException */ diff --git a/app/code/Magento/MediaGalleryApi/Model/BlacklistPatternsConfigInterface.php b/app/code/Magento/MediaGalleryApi/Model/BlacklistPatternsConfigInterface.php new file mode 100644 index 0000000000000..b4710f32e0c46 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Model/BlacklistPatternsConfigInterface.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\MediaGalleryApi\Model; + +/** + * Returns list of blacklist regexp patterns + */ +interface BlacklistPatternsConfigInterface +{ + /** + * Get regexp patterns + * + * @return array + */ + public function get(): array; +} diff --git a/app/code/Magento/MediaGalleryApi/Model/DataExtractorInterface.php b/app/code/Magento/MediaGalleryApi/Model/DataExtractorInterface.php deleted file mode 100644 index 6570cd2235412..0000000000000 --- a/app/code/Magento/MediaGalleryApi/Model/DataExtractorInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\MediaGalleryApi\Model; - -/** - * Extract data from an object using available getters - */ -interface DataExtractorInterface -{ - /** - * Extract data from an object using available getters (does not process extension attributes) - * - * @param object $object - * @param string|null $interface - * @return array - */ - public function extract($object, string $interface = null): array; -} diff --git a/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/GetAssetKeywordsInterface.php b/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/GetAssetKeywordsInterface.php index d449df5684c4b..e42c370c1c6f7 100644 --- a/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/GetAssetKeywordsInterface.php +++ b/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/GetAssetKeywordsInterface.php @@ -9,6 +9,8 @@ /** * A command represents functionality to get a media gallery asset keywords filtered by media gallery asset id. + * @deprecated use \Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface instead + * @see \Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface */ interface GetAssetKeywordsInterface { @@ -16,7 +18,6 @@ interface GetAssetKeywordsInterface * Get asset related keywords. * * @param int $assetId - * * @return \Magento\MediaGalleryApi\Api\Data\KeywordInterface[] */ public function execute(int $assetId): array; diff --git a/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/SaveAssetKeywordsInterface.php b/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/SaveAssetKeywordsInterface.php index 9c0d89c3456f8..824cbca178988 100644 --- a/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/SaveAssetKeywordsInterface.php +++ b/app/code/Magento/MediaGalleryApi/Model/Keyword/Command/SaveAssetKeywordsInterface.php @@ -9,6 +9,8 @@ /** * A command represents the media gallery asset keywords save operation. + * @deprecated use \Magento\MediaGalleryApi\Api\SaveAssetsKeywordsInterface instead + * @see \Magento\MediaGalleryApi\Api\SaveAssetsKeywordsInterface */ interface SaveAssetKeywordsInterface { diff --git a/app/code/Magento/MediaGalleryApi/etc/directory.xsd b/app/code/Magento/MediaGalleryApi/etc/directory.xsd new file mode 100644 index 0000000000000..2ad76c8fcc9f2 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/etc/directory.xsd @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="config" type="configType"> + </xs:element> + + <xs:complexType name="configType"> + <xs:sequence> + <xs:element type="blacklistType" name="blacklist" maxOccurs="unbounded" minOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="blacklistType"> + <xs:annotation> + <xs:documentation> + Blacklist used for excluding directories from media gallery rendering and operations + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element type="patternsType" name="patterns" maxOccurs="unbounded" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="patternsType"> + <xs:annotation> + <xs:documentation> + List of directory paths RegExp patterns + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element type="patternType" name="pattern" maxOccurs="unbounded" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="patternType"> + <xs:annotation> + <xs:documentation> + Directory path regexp pattern + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="required"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> +</xs:schema> diff --git a/app/code/Magento/MediaGalleryCatalog/LICENSE.txt b/app/code/Magento/MediaGalleryCatalog/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/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/MediaGalleryCatalog/LICENSE_AFL.txt b/app/code/Magento/MediaGalleryCatalog/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/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/MediaGallery/Plugin/Product/Gallery/Processor.php b/app/code/Magento/MediaGalleryCatalog/Plugin/Product/Gallery/RemoveAssetAfterRemoveImage.php similarity index 69% rename from app/code/Magento/MediaGallery/Plugin/Product/Gallery/Processor.php rename to app/code/Magento/MediaGalleryCatalog/Plugin/Product/Gallery/RemoveAssetAfterRemoveImage.php index 3fbe4e3a91a2b..865b40ce90a18 100644 --- a/app/code/Magento/MediaGallery/Plugin/Product/Gallery/Processor.php +++ b/app/code/Magento/MediaGalleryCatalog/Plugin/Product/Gallery/RemoveAssetAfterRemoveImage.php @@ -3,25 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); -namespace Magento\MediaGallery\Plugin\Product\Gallery; +namespace Magento\MediaGalleryCatalog\Plugin\Product\Gallery; -use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByPathInterface; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Gallery\Processor as ProcessorSubject; use Psr\Log\LoggerInterface; /** - * Ensures that metadata is removed from the database when a product image has been deleted. + * Ensures that metadata is removed from the database when an image has been deleted (from legacy media gallery) */ -class Processor +class RemoveAssetAfterRemoveImage { /** - * @var DeleteByPathInterface + * @var DeleteAssetsByPathsInterface */ - private $deleteMediaAssetByPath; + private $deleteByPaths; /** * @var LoggerInterface @@ -31,14 +30,14 @@ class Processor /** * Processor constructor. * - * @param DeleteByPathInterface $deleteMediaAssetByPath + * @param DeleteAssetsByPathsInterface $deleteByPaths * @param LoggerInterface $logger */ public function __construct( - DeleteByPathInterface $deleteMediaAssetByPath, + DeleteAssetsByPathsInterface $deleteByPaths, LoggerInterface $logger ) { - $this->deleteMediaAssetByPath = $deleteMediaAssetByPath; + $this->deleteByPaths = $deleteByPaths; $this->logger = $logger; } @@ -49,7 +48,6 @@ public function __construct( * @param ProcessorSubject $result * @param Product $product * @param string $file - * * @return ProcessorSubject * * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -65,7 +63,7 @@ public function afterRemoveImage( } try { - $this->deleteMediaAssetByPath->execute($file); + $this->deleteByPaths->execute([$file]); } catch (\Exception $exception) { $this->logger->critical($exception); } diff --git a/app/code/Magento/MediaGalleryCatalog/README.md b/app/code/Magento/MediaGalleryCatalog/README.md new file mode 100644 index 0000000000000..b39b1fae756d5 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/README.md @@ -0,0 +1,17 @@ +# Magento_MediaGalleryCatalog module + +The Magento_MediaGalleryCatalog module is responsible for for catalog gallery processor delete operation handling + +## Installation details + +For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html). + +## Extensibility + +Extension developers can interact with the Magento_MediaGallery module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_MediaGallery module. + +## Additional information + +For information about significant changes in patch releases, see [2.3.x Release information](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html). diff --git a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Product/Gallery/ProcessorTest.php b/app/code/Magento/MediaGalleryCatalog/Test/Unit/Plugin/Product/Gallery/RemoveAssetAfterRemoveImageTest.php similarity index 75% rename from app/code/Magento/MediaGallery/Test/Unit/Plugin/Product/Gallery/ProcessorTest.php rename to app/code/Magento/MediaGalleryCatalog/Test/Unit/Plugin/Product/Gallery/RemoveAssetAfterRemoveImageTest.php index 94c3aaf5c2f19..7cb071a5aebe4 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Product/Gallery/ProcessorTest.php +++ b/app/code/Magento/MediaGalleryCatalog/Test/Unit/Plugin/Product/Gallery/RemoveAssetAfterRemoveImageTest.php @@ -3,29 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); -namespace Magento\MediaGallery\Test\Unit\Plugin\Product\Gallery; +namespace Magento\MediaGalleryCatalog\Test\Unit\Plugin\Product\Gallery; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Gallery\Processor as ProcessorSubject; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\MediaGallery\Plugin\Product\Gallery\Processor; -use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByPathInterface; +use Magento\MediaGalleryCatalog\Plugin\Product\Gallery\RemoveAssetAfterRemoveImage; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; /** - * Unit test for \Magento\MediaGallery\Plugin\Product\Gallery\Processor + * Unit test for \Magento\MediaGalleryCatalog\Plugin\Product\Gallery\Processor */ -class ProcessorTest extends TestCase +class RemoveAssetAfterRemoveImageTest extends TestCase { private const STUB_FILE_NAME = 'file'; /** - * @var DeleteByPathInterface|MockObject + * @var DeleteAssetsByPathsInterface|MockObject */ private $deleteMediaAssetByPathMock; @@ -45,7 +44,7 @@ class ProcessorTest extends TestCase private $productMock; /** - * @var Processor + * @var RemoveAssetAfterRemoveImage */ private $plugin; @@ -57,19 +56,13 @@ protected function setUp() $this->processorSubjectMock = $this->createMock(ProcessorSubject::class); $this->productMock = $this->createMock(Product::class); - $this->deleteMediaAssetByPathMock = $this->getMockBuilder(DeleteByPathInterface::class) - ->disableOriginalConstructor() - ->setMethods(['execute']) - ->getMockForAbstractClass(); - $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) - ->disableOriginalConstructor() - ->setMethods(['critical']) - ->getMockForAbstractClass(); + $this->deleteMediaAssetByPathMock = $this->createMock(DeleteAssetsByPathsInterface::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); $this->plugin = (new ObjectManagerHelper($this))->getObject( - Processor::class, + RemoveAssetAfterRemoveImage::class, [ - 'deleteMediaAssetByPath' => $this->deleteMediaAssetByPathMock, + 'deleteByPaths' => $this->deleteMediaAssetByPathMock, 'logger' => $this->loggerMock ] ); @@ -82,7 +75,7 @@ public function testAfterRemoveImageExpectsExecuteCalled() { $this->deleteMediaAssetByPathMock->expects($this->once()) ->method('execute') - ->with(self::STUB_FILE_NAME); + ->with([self::STUB_FILE_NAME]); $this->loggerMock->expects($this->never())->method('critical'); $actualResult = $this->plugin->afterRemoveImage( @@ -118,7 +111,7 @@ public function testAfterRemoveImageExpectsExecuteWillThrowException() { $this->deleteMediaAssetByPathMock->expects($this->once()) ->method('execute') - ->with(self::STUB_FILE_NAME) + ->with([self::STUB_FILE_NAME]) ->willThrowException(new \Exception('Some Exception')); $this->loggerMock->expects($this->once())->method('critical'); diff --git a/app/code/Magento/MediaGalleryCatalog/composer.json b/app/code/Magento/MediaGalleryCatalog/composer.json new file mode 100644 index 0000000000000..ed3eb63ac970a --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-media-gallery-catalog", + "description": "Magento module responsible for catalog gallery processor delete operation handling", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-media-gallery-api": "*", + "magento/module-catalog": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\MediaGalleryCatalog\\": "" + } + } +} diff --git a/app/code/Magento/MediaGalleryCatalog/etc/di.xml b/app/code/Magento/MediaGalleryCatalog/etc/di.xml new file mode 100644 index 0000000000000..bdfdf2c5ef959 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/etc/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\Catalog\Model\Product\Gallery\Processor"> + <plugin name="media_gallery_image_remove_metadata" type="Magento\MediaGalleryCatalog\Plugin\Product\Gallery\RemoveAssetAfterRemoveImage"/> + </type> +</config> diff --git a/app/code/Magento/MediaGalleryCatalog/etc/directory.xml b/app/code/Magento/MediaGalleryCatalog/etc/directory.xml new file mode 100644 index 0000000000000..eaced3f642f70 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/etc/directory.xml @@ -0,0 +1,14 @@ +<?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:module:Magento_MediaGalleryApi:etc/directory.xsd"> + <blacklist> + <patterns> + <pattern name="catalog">/^catalog\/product/</pattern> + </patterns> + </blacklist> +</config> diff --git a/app/code/Magento/MediaGalleryCatalog/etc/module.xml b/app/code/Magento/MediaGalleryCatalog/etc/module.xml new file mode 100644 index 0000000000000..83f60ded3d35a --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/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_MediaGalleryCatalog"/> +</config> diff --git a/app/code/Magento/MediaGalleryCatalog/registration.php b/app/code/Magento/MediaGalleryCatalog/registration.php new file mode 100644 index 0000000000000..3c24d0c298f38 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalog/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_MediaGalleryCatalog', __DIR__); diff --git a/app/code/Magento/MsrpGroupedProduct/Plugin/Model/Product/Type/Grouped.php b/app/code/Magento/MsrpGroupedProduct/Plugin/Model/Product/Type/Grouped.php new file mode 100644 index 0000000000000..460bd34102e66 --- /dev/null +++ b/app/code/Magento/MsrpGroupedProduct/Plugin/Model/Product/Type/Grouped.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MsrpGroupedProduct\Plugin\Model\Product\Type; + +use Magento\Catalog\Model\ResourceModel\Product\Link\Product\Collection; + +/** + * Minimum advertised price plugin for grouped product + */ +class Grouped +{ + /** + * Add minimum advertised price to the attribute selection for associated products + * + * @param \Magento\GroupedProduct\Model\Product\Type\Grouped $subject + * @param Collection $collection + * @return Collection + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetAssociatedProductCollection( + \Magento\GroupedProduct\Model\Product\Type\Grouped $subject, + Collection $collection + ): Collection { + $collection->addAttributeToSelect(['msrp']); + return $collection; + } +} diff --git a/app/code/Magento/MsrpGroupedProduct/Pricing/MsrpPriceCalculator.php b/app/code/Magento/MsrpGroupedProduct/Pricing/MsrpPriceCalculator.php index b99f328a8b200..02231caa96526 100644 --- a/app/code/Magento/MsrpGroupedProduct/Pricing/MsrpPriceCalculator.php +++ b/app/code/Magento/MsrpGroupedProduct/Pricing/MsrpPriceCalculator.php @@ -31,6 +31,6 @@ public function getMsrpPriceValue(ProductInterface $product): float /** @var Grouped $groupedProduct */ $groupedProduct = $product->getTypeInstance(); - return $groupedProduct->getChildrenMsrp($product); + return (float) $groupedProduct->getChildrenMsrp($product); } } diff --git a/app/code/Magento/MsrpGroupedProduct/etc/di.xml b/app/code/Magento/MsrpGroupedProduct/etc/di.xml index 29b25f15bc2c1..4ad62eb56f49f 100644 --- a/app/code/Magento/MsrpGroupedProduct/etc/di.xml +++ b/app/code/Magento/MsrpGroupedProduct/etc/di.xml @@ -16,4 +16,7 @@ </argument> </arguments> </type> -</config> \ No newline at end of file + <type name="\Magento\GroupedProduct\Model\Product\Type\Grouped"> + <plugin name="grouped_product_minimum_advertised_price" type="\Magento\MsrpGroupedProduct\Plugin\Model\Product\Type\Grouped"/> + </type> +</config> diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StorefrontGoCheckoutWithMultipleAddressesActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StorefrontGoCheckoutWithMultipleAddressesActionGroup.xml index fd57a3b095a3d..1d8e836041129 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StorefrontGoCheckoutWithMultipleAddressesActionGroup.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StorefrontGoCheckoutWithMultipleAddressesActionGroup.xml @@ -9,6 +9,9 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontGoCheckoutWithMultipleAddressesActionGroup"> + <waitForAjaxLoad stepKey="waitAjaxLoad"/> <click selector="{{MultishippingSection.shippingMultipleCheckout}}" stepKey="clickToMultipleAddressShippingButton"/> + <waitForPageLoad stepKey="waitForMultipleCheckoutLoad"/> + <seeElement selector="{{MultishippingSection.pageTitle}}" stepKey="seeMultipleCheckoutPageTitle"/> </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 fab513cc8d11a..db037d50f7dc6 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection/MultishippingSection.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection/MultishippingSection.xml @@ -8,6 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="MultishippingSection"> + <element name="pageTitle" type="text" selector="//span[text()='Ship to Multiple Addresses']"/> <element name="checkoutWithMultipleAddresses" type="button" selector="//span[text()='Check Out with Multiple Addresses']"/> <element name="shippingMultipleCheckout" type="button" selector=".action.multicheckout"/> <element name="shippingAddressSelector" type="select" selector="//tr[position()={{addressPosition}}]//td[@data-th='Send To']//select" parameterized="true"/> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml index 2925f2c5b1690..caf0ce3a51bae 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml @@ -59,9 +59,8 @@ <argument name="product" value="$$createSecondProduct$$"/> <argument name="productCount" value="2"/> </actionGroup> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnShoppingCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnShoppingCartPage"/> <!-- Click 'Check Out with Multiple Addresses' --> - <waitForPageLoad stepKey="waitForSecondPageLoad"/> <actionGroup ref="StorefrontGoCheckoutWithMultipleAddressesActionGroup" stepKey="goCheckoutWithMultipleAddresses"/> <!-- Select different addresses and click 'Go to Shipping Information' --> <actionGroup ref="StorefrontCheckoutShippingSelectMultipleAddressesActionGroup" stepKey="selectMultipleAddresses"> @@ -71,7 +70,7 @@ <waitForPageLoad stepKey="waitPageLoad"/> <!-- Open the Cart page in another browser window and go back --> <openNewTab stepKey="openNewTab"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnShoppingCartPageNewTab"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnShoppingCartPageNewTab"/> <actionGroup ref="AssertStorefrontCheckoutCartItemsActionGroup" stepKey="assertFirstProductItemInCheckOutCart"> <argument name="productName" value="$$createFirstProduct.name$$"/> <argument name="productSku" value="$$createFirstProduct.sku$$"/> diff --git a/app/code/Magento/OfflineShipping/Test/Mftf/Test/StorefrontFreeShippingDisplayWithInclTaxOptionTest.xml b/app/code/Magento/OfflineShipping/Test/Mftf/Test/StorefrontFreeShippingDisplayWithInclTaxOptionTest.xml index d2e092283e9bd..114c53d215647 100644 --- a/app/code/Magento/OfflineShipping/Test/Mftf/Test/StorefrontFreeShippingDisplayWithInclTaxOptionTest.xml +++ b/app/code/Magento/OfflineShipping/Test/Mftf/Test/StorefrontFreeShippingDisplayWithInclTaxOptionTest.xml @@ -45,8 +45,7 @@ <argument name="product" value="$$createSimpleProduct$$"/> </actionGroup> <!-- Assert that taxes are applied correctly for CA --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> <waitForElementVisible selector="{{CheckoutPaymentSection.tax}}" stepKey="waitForOverviewVisible"/> <waitForElement time="30" selector="{{CheckoutCartSummarySection.estimateShippingAndTaxForm}}" stepKey="waitForEstimateShippingAndTaxForm"/> <waitForElement time="30" selector="{{CheckoutCartSummarySection.shippingMethodForm}}" stepKey="waitForShippingMethodForm"/> diff --git a/app/code/Magento/Payment/Test/Mftf/ActionGroup/ApplyCouponOnPaymentPageActionGroup.xml b/app/code/Magento/Payment/Test/Mftf/ActionGroup/ApplyCouponOnPaymentPageActionGroup.xml index 0f22222b5f767..f069e6ac4a4d5 100644 --- a/app/code/Magento/Payment/Test/Mftf/ActionGroup/ApplyCouponOnPaymentPageActionGroup.xml +++ b/app/code/Magento/Payment/Test/Mftf/ActionGroup/ApplyCouponOnPaymentPageActionGroup.xml @@ -20,6 +20,7 @@ <fillField selector="{{ProductCardSection.addCode}}" userInput="{{couponCode}}" stepKey="TypeDiscountCode"/> <click selector="{{ProductCardSection.applyDiscount}}" stepKey="clickToApplyDiscount"/> <waitForPageLoad stepKey="WaitForDiscountToBeAdded"/> - <see selector="{{ProductCardSection.discountVerificationMsg}}" userInput="Your coupon was successfully applied" stepKey="discountApplyMessage"/> + <!-- Success message will change to display none so will check DOM instead --> + <seeElementInDOM selector="{{ProductCardSection.discountSuccessMsgInDOM}}" stepKey="discountApplyMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php b/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php index 71e0384c72f79..b97cf2aa1fc2a 100644 --- a/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php +++ b/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php @@ -7,7 +7,7 @@ namespace Magento\Payment\Ui\Component\Listing\Column\Method; /** - * Class Options + * Class Options for Listing Column Method */ class Options implements \Magento\Framework\Data\OptionSourceInterface { @@ -43,13 +43,6 @@ public function toOptionArray() $this->options = $this->paymentHelper->getPaymentMethodList(true, true); } - array_walk( - $this->options, - function (&$item) { - $item['__disableTmpl'] = true; - } - ); - return $this->options; } } diff --git a/app/code/Magento/Paypal/Controller/Express/GetTokenData.php b/app/code/Magento/Paypal/Controller/Express/GetTokenData.php index 512dac4cdec06..5c41e753e8a95 100644 --- a/app/code/Magento/Paypal/Controller/Express/GetTokenData.php +++ b/app/code/Magento/Paypal/Controller/Express/GetTokenData.php @@ -152,6 +152,10 @@ public function execute(): ResultInterface $responseContent['error_message'] = __('Sorry, but something went wrong'); } + if (!$responseContent['success']) { + $this->messageManager->addErrorMessage($responseContent['error_message']); + } + return $controllerResult->setData($responseContent); } diff --git a/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php index 0d7ec3fc6f32d..b496c7f10d937 100644 --- a/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php +++ b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php @@ -10,6 +10,7 @@ use Magento\Framework\Controller\ResultFactory; use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Paypal\Model\Config as PayPalConfig; use Magento\Paypal\Model\Express\Checkout as PayPalCheckout; use Magento\Paypal\Model\Api\ProcessableException as ApiProcessableException; @@ -160,7 +161,7 @@ public function execute(): ResultInterface } catch (ApiProcessableException $e) { $responseContent['success'] = false; $responseContent['error_message'] = $e->getUserMessage(); - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $responseContent['success'] = false; $responseContent['error_message'] = $e->getMessage(); } catch (\Exception $e) { @@ -168,6 +169,10 @@ public function execute(): ResultInterface $responseContent['error_message'] = __('We can\'t process Express Checkout approval.'); } + if (!$responseContent['success']) { + $this->messageManager->addErrorMessage($responseContent['error_message']); + } + return $controllerResult->setData($responseContent); } } diff --git a/app/code/Magento/Paypal/Model/Config.php b/app/code/Magento/Paypal/Model/Config.php index 6f6728ebfa47f..c790a4524f18b 100644 --- a/app/code/Magento/Paypal/Model/Config.php +++ b/app/code/Magento/Paypal/Model/Config.php @@ -1512,7 +1512,10 @@ protected function _mapExpressFieldset($fieldName) case 'allow_ba_signup': case 'in_context': case 'merchant_id': + case 'client_id': + case 'sandbox_client_id': case 'supported_locales': + case 'smart_buttons_supported_locales': return "payment/{$this->_methodCode}/{$fieldName}"; default: return $this->_mapMethodFieldset($fieldName); diff --git a/app/code/Magento/Paypal/Model/Express/LocaleResolver.php b/app/code/Magento/Paypal/Model/Express/LocaleResolver.php index c9136d03036d2..656e33b108fa9 100644 --- a/app/code/Magento/Paypal/Model/Express/LocaleResolver.php +++ b/app/code/Magento/Paypal/Model/Express/LocaleResolver.php @@ -89,7 +89,9 @@ public function setLocale($locale = null) public function getLocale(): string { $locale = $this->localeMap[$this->resolver->getLocale()] ?? $this->resolver->getLocale(); - $allowedLocales = $this->config->getValue('supported_locales'); + $allowedLocales =(bool)(int) $this->config->getValue('in_context') + ? $this->config->getValue('smart_buttons_supported_locales') + : $this->config->getValue('supported_locales'); return strpos($allowedLocales, $locale) !== false ? $locale : 'en_US'; } diff --git a/app/code/Magento/Paypal/Model/SmartButtonConfig.php b/app/code/Magento/Paypal/Model/SmartButtonConfig.php index 921dc4679b089..88d68511ae5fe 100644 --- a/app/code/Magento/Paypal/Model/SmartButtonConfig.php +++ b/app/code/Magento/Paypal/Model/SmartButtonConfig.php @@ -11,9 +11,10 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Locale\ResolverInterface; use Magento\Store\Model\ScopeInterface; +use Magento\Paypal\Model\Config as PayPalConfig; /** - * Smart button configuration. + * Provides configuration values for PayPal in-context checkout */ class SmartButtonConfig { @@ -33,35 +34,50 @@ class SmartButtonConfig private $defaultStyles; /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * Maps the old checkout SDK configuration values to the current ones * @var array */ - private $allowedFunding; + private $disallowedFundingMap; /** - * @var ScopeConfigInterface + * These payment methods will be added as parameters to the SDK url to disable them. + * @var array */ - private $scopeConfig; + private $unsupportedPaymentMethods; + + /** + * Base url for Paypal SDK + */ + private const BASE_URL = 'https://www.paypal.com/sdk/js?'; /** * @param ResolverInterface $localeResolver * @param ConfigFactory $configFactory * @param ScopeConfigInterface $scopeConfig * @param array $defaultStyles - * @param array $allowedFunding + * @param array $disallowedFundingMap + * @param array $unsupportedPaymentMethods */ public function __construct( ResolverInterface $localeResolver, ConfigFactory $configFactory, ScopeConfigInterface $scopeConfig, $defaultStyles = [], - $allowedFunding = [] + $disallowedFundingMap = [], + $unsupportedPaymentMethods = [] ) { $this->localeResolver = $localeResolver; $this->config = $configFactory->create(); $this->config->setMethod(Config::METHOD_EXPRESS); $this->scopeConfig = $scopeConfig; $this->defaultStyles = $defaultStyles; - $this->allowedFunding = $allowedFunding; + $this->disallowedFundingMap = $disallowedFundingMap; + $this->unsupportedPaymentMethods = $unsupportedPaymentMethods; } /** @@ -76,20 +92,63 @@ public function getConfig(string $page): array Data::XML_PATH_GUEST_CHECKOUT, ScopeInterface::SCOPE_STORE ); + return [ - 'merchantId' => $this->config->getValue('merchant_id'), - 'environment' => ((int)$this->config->getValue('sandbox_flag') ? 'sandbox' : 'production'), - 'locale' => $this->localeResolver->getLocale(), - 'allowedFunding' => $this->getAllowedFunding($page), - 'disallowedFunding' => $this->getDisallowedFunding(), 'styles' => $this->getButtonStyles($page), 'isVisibleOnProductPage' => (bool)$this->config->getValue('visible_on_product'), - 'isGuestCheckoutAllowed' => $isGuestCheckoutAllowed + 'isGuestCheckoutAllowed' => $isGuestCheckoutAllowed, + 'sdkUrl' => $this->generatePaypalSdkUrl($page) ]; } /** - * Returns disallowed funding from configuration + * Generate the url to download the Paypal SDK + * + * @param string $page + * + * @return string + */ + private function generatePaypalSdkUrl(string $page): string + { + $clientId = (int)$this->config->getValue('sandbox_flag') ? + $this->config->getValue('sandbox_client_id') : $this->config->getValue('client_id'); + $disallowedFunding = implode(',', $this->getDisallowedFunding()); + + $commit = $page === 'checkout' ? 'true' : 'false'; + + $params = + [ + 'client-id' => $clientId, + 'commit' => $commit, + 'merchant-id' => $this->config->getValue('merchant_id'), + 'locale' => $this->localeResolver->getLocale(), + 'intent' => $this->getIntent(), + ]; + if ($disallowedFunding) { + $params['disable-funding'] = $disallowedFunding; + } + + return self::BASE_URL . http_build_query($params); + } + + /** + * Return intent value from the configuration payment_action value + * + * @return string + */ + private function getIntent(): string + { + $paymentAction = $this->config->getValue('paymentAction'); + $mappedIntentValues = [ + Config::PAYMENT_ACTION_AUTH => 'authorize', + Config::PAYMENT_ACTION_SALE => 'capture', + Config::PAYMENT_ACTION_ORDER => 'order' + ]; + return $mappedIntentValues[$paymentAction]; + } + + /** + * Returns disallowed funding from configuration after updating values * * @return array */ @@ -103,18 +162,17 @@ private function getDisallowedFunding(): array array_push($result, 'CARD'); } - return $result; - } + // Map old configuration values to current ones + $result = array_map(function ($oldValue) { + return $this->disallowedFundingMap[$oldValue] ?? $oldValue; + }, + $result); - /** - * Returns allowed funding - * - * @param string $page - * @return array - */ - private function getAllowedFunding(string $page): array - { - return array_values(array_diff($this->allowedFunding[$page], $this->getDisallowedFunding())); + //disable unsupported payment methods + $result = array_combine($result, $result); + $result = array_merge($result, $this->unsupportedPaymentMethods); + + return $result; } /** @@ -165,7 +223,7 @@ private function updateStyles(array $styles, string $page): array // Installment label is only available for specific locales if ($styles['label'] === 'installment') { if (array_key_exists($locale, $installmentPeriodLocale)) { - $styles['installmentperiod'] = (int)$this->config->getValue( + $styles['period'] = (int)$this->config->getValue( $page .'_page_button_' . $installmentPeriodLocale[$locale] . '_installment_period' ); } else { diff --git a/app/code/Magento/Paypal/Model/System/Config/Source/ButtonStyles.php b/app/code/Magento/Paypal/Model/System/Config/Source/ButtonStyles.php index 8ad55d045ff1a..d55fd216c06b5 100644 --- a/app/code/Magento/Paypal/Model/System/Config/Source/ButtonStyles.php +++ b/app/code/Magento/Paypal/Model/System/Config/Source/ButtonStyles.php @@ -53,20 +53,6 @@ public function getShape(): array ]; } - /** - * Button size source getter - * - * @return array - */ - public function getSize(): array - { - return [ - 'medium' => __('Medium'), - 'large' => __('Large'), - 'responsive' => __('Responsive') - ]; - } - /** * Button label source getter * @@ -80,7 +66,6 @@ public function getLabel(): array 'buynow' => __('Buy Now'), 'paypal' => __('PayPal'), 'installment' => __('Installment'), - 'credit' => __('Credit') ]; } diff --git a/app/code/Magento/Paypal/Model/System/Config/Source/DisableFundingOptions.php b/app/code/Magento/Paypal/Model/System/Config/Source/DisableFundingOptions.php index 1a9cfe0998fb8..a06949402956d 100644 --- a/app/code/Magento/Paypal/Model/System/Config/Source/DisableFundingOptions.php +++ b/app/code/Magento/Paypal/Model/System/Config/Source/DisableFundingOptions.php @@ -12,24 +12,35 @@ */ class DisableFundingOptions { + + /** + * @var array + */ + private $disallowedFundingOptions; + + /** + * DisableFundingOptions constructor. + * @param array $disallowedFundingOptions + */ + public function __construct($disallowedFundingOptions = []) + { + $this->disallowedFundingOptions = $disallowedFundingOptions; + } + /** * @inheritdoc */ public function toOptionArray(): array { - return [ - [ - 'value' => 'CREDIT', - 'label' => __('PayPal Credit') - ], - [ - 'value' => 'CARD', - 'label' => __('PayPal Guest Checkout Credit Card Icons') - ], - [ - 'value' => 'ELV', - 'label' => __('Elektronisches Lastschriftverfahren - German ELV') - ] - ]; + return array_map( + function ($key, $value) { + return [ + 'value' => $key, + 'label' => __($value) + ]; + }, + array_keys($this->disallowedFundingOptions), + $this->disallowedFundingOptions + ); } } diff --git a/app/code/Magento/Paypal/Setup/Patch/Data/UpdateSmartButtonLabel.php b/app/code/Magento/Paypal/Setup/Patch/Data/UpdateSmartButtonLabel.php new file mode 100644 index 0000000000000..2a8ed1fa07aee --- /dev/null +++ b/app/code/Magento/Paypal/Setup/Patch/Data/UpdateSmartButtonLabel.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Setup\Patch\Data; + +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +/** + * Update existing customization for the smart button label to be compatible with the new PayPal SDK + */ +class UpdateSmartButtonLabel implements DataPatchInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * PrepareInitialConfig constructor. + * @param ModuleDataSetupInterface $moduleDataSetup + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup + ) { + $this->moduleDataSetup = $moduleDataSetup; + } + + /** + * @inheritdoc + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + $this->moduleDataSetup->getConnection()->update( + $this->moduleDataSetup->getTable('core_config_data'), + ['value' => 'paypal'], + [ + 'path IN (?)' => [ + 'paypal/style/checkout_page_button_label', + 'paypal/style/cart_page_button_label', + 'paypal/style/mini_cart_page_button_label' + ], + 'value = ? ' => 'credit' + ] + ); + $this->moduleDataSetup->getConnection()->update( + $this->moduleDataSetup->getTable('core_config_data'), + ['value' => 'buynow'], + [ + 'path IN (?)' => ['paypal/style/product_page_button_label'], + 'value = ? ' => 'credit' + ] + ); + return $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Paypal/Setup/Patch/Data/UpdateSmartButtonSize.php b/app/code/Magento/Paypal/Setup/Patch/Data/UpdateSmartButtonSize.php new file mode 100644 index 0000000000000..349fa64bfcc71 --- /dev/null +++ b/app/code/Magento/Paypal/Setup/Patch/Data/UpdateSmartButtonSize.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Setup\Patch\Data; + +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +/** + * Update existing customization for the smart button size value to be compatible with the new PayPal SDK + */ +class UpdateSmartButtonSize implements DataPatchInterface +{ + /** + * @var array + */ + private $sizeSettingsToUpdate = [ + 'paypal/style/checkout_page_button_size', + 'paypal/style/cart_page_button_size', + 'paypal/style/mini_cart_page_button_size', + 'paypal/style/checkout_page_button_size' + ]; + + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * PrepareInitialConfig constructor. + * @param ModuleDataSetupInterface $moduleDataSetup + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup + ) { + $this->moduleDataSetup = $moduleDataSetup; + } + + /** + * @inheritdoc + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + $this->moduleDataSetup->getConnection()->update( + $this->moduleDataSetup->getTable('core_config_data'), + ['value' => 'responsive'], + [ + 'path IN (?)' => $this->sizeSettingsToUpdate, + 'value NOT IN (?) ' => ['responsive'] + ] + ); + return $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AssertPayPalButtonLayoutActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AssertPayPalButtonLayoutActionGroup.xml new file mode 100644 index 0000000000000..d8ad106e8c6da --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AssertPayPalButtonLayoutActionGroup.xml @@ -0,0 +1,27 @@ +<?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="AssertPayPalButtonLayoutActionGroup"> + <annotations> + <description>Assert Paypal button layout when user customize button</description> + </annotations> + <arguments> + <argument name="label" type="string" defaultValue="Buy Now"/> + <argument name="layout" type="string" defaultValue="Horizontal"/> + <argument name="shape" type="string" defaultValue="Pill"/> + <argument name="color" type="string" defaultValue="Gold"/> + </arguments> + + <seeElement selector="{{StorefrontPayPalSmartButtonStylesSection.labelText(label)}}" stepKey="seeButtonLabelText"/> + <seeElement selector="{{CheckoutPaymentSection.PayPalBtn}}{{StorefrontPayPalSmartButtonStylesSection.layout(layout)}}" stepKey="seeButtonLayout"/> + <seeElement selector="{{CheckoutPaymentSection.PayPalBtn}}{{StorefrontPayPalSmartButtonStylesSection.shape(shape)}}" stepKey="seeButtonShape"/> + <seeElement selector="{{CheckoutPaymentSection.PayPalBtn}}{{StorefrontPayPalSmartButtonStylesSection.color(color)}}" stepKey="seeButtonColor"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AssertPayPalButtonLayoutInPaypalLabelActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AssertPayPalButtonLayoutInPaypalLabelActionGroup.xml new file mode 100644 index 0000000000000..28aba7b833f8c --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/AssertPayPalButtonLayoutInPaypalLabelActionGroup.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="AssertPayPalButtonLayoutInPaypalLabelActionGroup" extends="AssertPayPalButtonLayoutActionGroup"> + <annotations> + <description>Assert Paypal button layout when layout is horizontal</description> + </annotations> + <arguments> + </arguments> + <dontSeeElement selector="{{StorefrontPayPalSmartButtonStylesSection.labelText(label)}}" stepKey="seeButtonLabelText"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml index 90f0a25a8cd69..01ec295d8bf63 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/ConfigPayPalExpressCheckoutActionGroup.xml @@ -28,6 +28,7 @@ <fillField selector ="{{PayPalExpressCheckoutConfigSection.signature(countryCode)}}" userInput="{{credentials.magento/paypal_express_checkout_us_api_signature}}" stepKey="inputAPISignature"/> <selectOption selector ="{{PayPalExpressCheckoutConfigSection.sandboxMode(countryCode)}}" userInput="Yes" stepKey="enableSandboxMode"/> <selectOption selector="{{PayPalExpressCheckoutConfigSection.enableSolution(countryCode)}}" userInput="Yes" stepKey="enableSolution"/> + <waitForElementVisible selector="{{PayPalExpressCheckoutConfigSection.merchantID(countryCode)}}" stepKey="waitForMerchantIdField"/> <fillField selector ="{{PayPalExpressCheckoutConfigSection.merchantID(countryCode)}}" userInput="{{credentials.magento/paypal_express_checkout_us_merchant_id}}" stepKey="inputMerchantID"/> <!--Save configuration--> <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml index 2b559085a83b5..bf4bce4a8fa09 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml @@ -15,7 +15,7 @@ <arguments> <argument name="countryCode" type="string" defaultValue="us"/> </arguments> - + <click selector="{{PayPalExpressCheckoutConfigSection.configureBtn(countryCode)}}" stepKey="clickPayPalConfigureBtn"/> <waitForElementVisible selector="{{PayPalAdvancedSettingConfigSection.advancedSettingTab(countryCode)}}" stepKey="waitForAdvancedSettingTab"/> <click selector="{{PayPalAdvancedSettingConfigSection.advancedSettingTab(countryCode)}}" stepKey="openAdvancedSettingTab"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalAssertTransferLineAndShippingMethodActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalAssertTransferLineAndShippingMethodActionGroup.xml new file mode 100644 index 0000000000000..f9cef514c2bf0 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalAssertTransferLineAndShippingMethodActionGroup.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"> + <actionGroup name="PayPalAssertTransferLineAndShippingMethodActionGroup"> + <annotations> + <description>Assert Transfer Cart Line and Shipping Method</description> + </annotations> + <arguments> + <argument name="shippingLabel" defaultValue="no_rate - " type="string"/> + <argument name="productName" defaultValue="Simple Product" type="string"/> + </arguments> + <see selector="{{PayPalPaymentSection.shippingMethod}}" userInput="{{shippingLabel}}" stepKey="transferShippingMethodAssertion"/> + <click selector="{{PayPalPaymentSection.paypalCart}}" stepKey="openPayPalCart"/> + <see selector="{{PayPalPaymentSection.productNamePosition}}" userInput="{{productName}}" stepKey="transferCartLineAssertion"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalAssertTransferLineAndShippingMethodNotExistActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalAssertTransferLineAndShippingMethodNotExistActionGroup.xml new file mode 100644 index 0000000000000..b90c3e70db097 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalAssertTransferLineAndShippingMethodNotExistActionGroup.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="PayPalAssertTransferLineAndShippingMethodNotExistActionGroup"> + <annotations> + <description>Assert Transfer Cart Line and Shipping Method not exist on paypal side</description> + </annotations> + <dontSeeElement selector="{{PayPalPaymentSection.shippingMethod}}" stepKey="noShippingMethodTransfer"/> + <dontSeeElement selector="{{PayPalPaymentSection.paypalCart}}" stepKey="noCartLineTransfer"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentAccountOneStepActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentAccountOneStepActionGroup.xml new file mode 100644 index 0000000000000..c4cad5f3d4cd4 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentAccountOneStepActionGroup.xml @@ -0,0 +1,16 @@ +<?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="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" extends="StorefrontLoginToPayPalPaymentAccountTwoStepActionGroup"> + <annotations> + <description>EXTENDS: StorefrontLoginToPayPalPaymentAccountOneStepActionGroup. Remove extra Continue step on PayPal site</description> + </annotations> + <remove keyForRemoval="clickNext"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentAccountTwoStepActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentAccountTwoStepActionGroup.xml new file mode 100644 index 0000000000000..5619aa27860ce --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentAccountTwoStepActionGroup.xml @@ -0,0 +1,30 @@ +<?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="StorefrontLoginToPayPalPaymentAccountTwoStepActionGroup"> + <arguments> + <argument name="payerName" defaultValue="MPI" type="string"/> + <argument name="credentials" defaultValue="_CREDS"/> + </arguments> + <!--Check in-context--> + <switchToNextTab stepKey="switchToInContentTab"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <seeCurrentUrlMatches regex="~\//www.sandbox.paypal.com/~" stepKey="seeCurrentUrlMatchesConfigPath1"/> + <conditionalClick selector="{{PayPalPaymentSection.notYouLink}}" dependentSelector="{{PayPalPaymentSection.notYouLink}}" visible="true" stepKey="selectNotYouSection"/> + <waitForElement selector="{{PayPalPaymentSection.email}}" stepKey="waitForLoginForm" /> + <fillField selector="{{PayPalPaymentSection.email}}" userInput="{{credentials.magento/paypal_sandbox_login_email}}" stepKey="fillEmail"/> + <click selector="{{PayPalPaymentSection.nextButton}}" stepKey="clickNext"/> + <waitForElementVisible selector="{{PayPalPaymentSection.password}}" stepKey="waitForPasswordField"/> + <click selector="{{PayPalPaymentSection.password}}" stepKey="focusOnPasswordField"/> + <fillField selector="{{PayPalPaymentSection.password}}" userInput="{{credentials.magento/paypal_sandbox_login_password}}" stepKey="fillPassword"/> + <click selector="{{PayPalPaymentSection.loginBtn}}" stepKey="login"/> + <waitForPageLoad stepKey="wait"/> + <see userInput="{{payerName}}" selector="{{PayPalPaymentSection.reviewUserInfo}}" stepKey="seePayerName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentFromCartActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentFromCartActionGroup.xml new file mode 100644 index 0000000000000..f627b9158f868 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontLoginToPayPalPaymentFromCartActionGroup.xml @@ -0,0 +1,14 @@ +<?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="StorefrontLoginToPayPalPaymentFromCartAccountActionGroup" extends="StorefrontLoginToPayPalPaymentAccountTwoStepActionGroup"> + <seeElement selector="{{PayPalCheckoutAsGuestSection.CreditDebitBtn}}" stepKey="assertCheckoutAsGuest" before="waitForLoginForm"/> + <see userInput="{{payerName}}" selector="{{PayPalPaymentSection.userName}}" stepKey="seePayerName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayOrderOnPayPalCheckoutActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayOrderOnPayPalCheckoutActionGroup.xml index b7ebf7dab1c8b..8e337abfb2610 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayOrderOnPayPalCheckoutActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayOrderOnPayPalCheckoutActionGroup.xml @@ -18,7 +18,7 @@ <click selector="{{PayPalPaymentSection.cartIcon}}" stepKey="openCart"/> <waitForPageLoad stepKey="waitForCartLoad"/> <seeElement selector="{{PayPalPaymentSection.itemName(productName)}}" stepKey="seeProductName"/> - <click selector="{{PayPalPaymentSection.PayPalSubmitBtn}}" stepKey="clickPayPalSubmitBtn"/> + <click selector="{{CheckoutPaymentSection.PayPalBtn}}" stepKey="clickPayPalBtn"/> <switchToPreviousTab stepKey="switchToPreviousTab"/> <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml index 331acc1de628a..655a93b2e5044 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml @@ -9,13 +9,12 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="LoginToPayPalPaymentAccount"> <arguments> - <argument name="userName" type="string" defaultValue="{{Payer.buyerEmail}}"/> - <argument name="password" type="string" defaultValue="{{Payer.buyerPassword}"/> + <argument name="credentials" defaultValue="_CREDS"/> </arguments> - <fillField selector="{{PayPalPaymentSection.email}}" userInput="{{userName}}" stepKey="fillEmail"/> + <fillField selector="{{PayPalPaymentSection.email}}" userInput="{{credentials.magento/paypal_sandbox_login_email}}" stepKey="fillEmail"/> <click selector="{{PayPalPaymentSection.nextButton}}" stepKey="clickNext"/> <waitForElementVisible selector="{{PayPalPaymentSection.password}}" stepKey="waitForPasswordField"/> - <fillField selector="{{PayPalPaymentSection.password}}" userInput="{{password}}" stepKey="fillPassword"/> + <fillField selector="{{PayPalPaymentSection.password}}" userInput="{{credentials.magento/paypal_sandbox_login_password}}" stepKey="fillPassword"/> <click selector="{{PayPalPaymentSection.loginBtn}}" stepKey="clickLogin"/> <waitForPageLoad stepKey="waitForLoginPageLoad"/> <click selector="{{PayPalPaymentSection.continueButton}}" stepKey="clickContinue"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPaypalSwitchBackToMagentoActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPaypalSwitchBackToMagentoActionGroup.xml new file mode 100644 index 0000000000000..5fff967ebdb7d --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPaypalSwitchBackToMagentoActionGroup.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"> + <actionGroup name="StorefrontPaypalSwitchBackToMagentoActionGroup"> + <annotations> + <description>Click continue button on Paypal site and go back to Magento site</description> + </annotations> + + <!--Click Continue button on PayPal site--> + <scrollTo selector="{{PayPalPaymentSection.continueButton}}" stepKey="scrollToContinueBtn"/> + <click selector="{{PayPalPaymentSection.continueButton}}" stepKey="clickContinue"/> + <!--Go back to Magento site--> + <switchToPreviousTab stepKey="switchToPreviousTab"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup.xml new file mode 100644 index 0000000000000..1053a08951d40 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup.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="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" extends="StorefrontPaypalSwitchBackToMagentoActionGroup"> + <annotations> + <description>Click submit button on Paypal site and go back to Magento site</description> + </annotations> + + <!--Click Continue button on PayPal site--> + <scrollTo selector="{{PayPalPaymentSection.paypalSubmitBtn}}" stepKey="scrollToContinueBtn"/> + <click selector="{{PayPalPaymentSection.paypalSubmitBtn}}" stepKey="clickContinue"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPlaceOrderOnOrderReviewPageActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPlaceOrderOnOrderReviewPageActionGroup.xml new file mode 100644 index 0000000000000..07b8302a941b0 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPlaceOrderOnOrderReviewPageActionGroup.xml @@ -0,0 +1,17 @@ +<?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="StorefrontPlaceOrderOnOrderReviewPageActionGroup"> + <annotations> + <description>Place order on Order Review Page after back from PayPal side</description> + </annotations> + <!--SubmitOrder--> + <click selector="{{StorefrontPayPalOrderReviewSection.placeOrderBtn}}" stepKey="clickPlaceOrderBtn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontSelectShippingMethodOnOrderReviewPageActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontSelectShippingMethodOnOrderReviewPageActionGroup.xml new file mode 100644 index 0000000000000..003ef5645c147 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontSelectShippingMethodOnOrderReviewPageActionGroup.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="StorefrontSelectShippingMethodOnOrderReviewPageActionGroup"> + <annotations> + <description>Select Shipping method on Order Review page</description> + </annotations> + <arguments> + <argument name="shippingMethod" defaultValue="Fixed - $5.00" type="string"/> + </arguments> + <waitForElementVisible selector="{{StorefrontPayPalOrderReviewSection.shippingMethod}}" stepKey="waitForShippingMethodDropdown"/> + <selectOption selector="{{StorefrontPayPalOrderReviewSection.shippingMethod}}" userInput="{{shippingMethod}}" stepKey="selectShippingMethod" /> + <waitForPageLoad stepKey="waitForLoadShippingMethod"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontSwitchToPayPalButtonIframeActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontSwitchToPayPalButtonIframeActionGroup.xml new file mode 100644 index 0000000000000..bd7774465bb38 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontSwitchToPayPalButtonIframeActionGroup.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="StorefrontSwitchToPayPalButtonIframeActionGroup" extends="SwitchToPayPalGroupBtnActionGroup"> + <annotations> + <description>EXTENDS: SwitchToPayPalGroupBtnActionGroup. Switches to PayPal Smart Button iFrame.</description> + </annotations> + + <remove keyForRemoval="clickPayPalBtn"/> + <remove keyForRemoval="switchBackToMainFrame"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/SwitchToPayPalGroupBtnActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/SwitchToPayPalGroupBtnActionGroup.xml new file mode 100644 index 0000000000000..2e57bdf6265b1 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/SwitchToPayPalGroupBtnActionGroup.xml @@ -0,0 +1,26 @@ +<?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="SwitchToPayPalGroupBtnActionGroup"> + <annotations> + <description>Switch to Paypal group button</description> + </annotations> + <arguments> + <argument name="elementNumber" type="string" defaultValue="0"/> + </arguments> + <!--set ID for iframe of PayPal group button--> + <executeJS function="document.getElementsByClassName('component-frame')[{{elementNumber}}].setAttribute('name', 'myFrame');" stepKey="setIDForIframe"/> + <!--switch to iframe of PayPal group button--> + <switchToIFrame userInput="myFrame" stepKey="switchToIframe"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.PayPalBtn}}" stepKey="waitForPayPalBtn"/> + <click selector="{{CheckoutPaymentSection.PayPalBtn}}" stepKey="clickPayPalBtn"/> + <switchToIFrame stepKey="switchBackToMainFrame"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalConfigData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalConfigData.xml index 0744207494108..1ad7642f6408a 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalConfigData.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalConfigData.xml @@ -49,4 +49,268 @@ <data key="label">No</data> <data key="value">0</data> </entity> + <entity name="StorefrontPaypalEnableTransferCartLineConfigData"> + <data key="path">payment/paypal_express/line_items_enabled</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontPaypalDisableTransferCartLineConfigData"> + <data key="path">payment/paypal_express/line_items_enabled</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontPaypalEnableTransferShippingOptionConfigData"> + <data key="path">payment/paypal_express/transfer_shipping_options</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontPaypalDisableTransferShippingOptionConfigData"> + <data key="path">payment/paypal_express/transfer_shipping_options</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontPaypalExpressAuthorizationPaymentActionOptionConfigData"> + <data key="path">payment/paypal_express/payment_action</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">Authorization</data> + </entity> + <entity name="StorefrontPaypalExpressSalePaymentActionOptionConfigData"> + <data key="path">payment/paypal_express/payment_action</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">Sale</data> + </entity> + <entity name="StorefrontPaypalExpressOrderPaymentActionOptionConfigData"> + <data key="path">payment/paypal_express/payment_action</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">Order</data> + </entity> + <entity name="StorefrontPaypalExpressEnableCheckoutAsGuestConfigData"> + <data key="path">payment/paypal_express/solution_type</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">Sole</data> + </entity> + <entity name="StorefrontPaypalExpressDisableCheckoutAsGuestConfigData"> + <data key="path">payment/paypal_express/solution_type</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">Mark</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageEnableCustomizeButtonConfigData"> + <data key="path">paypal/style/checkout_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageDisableCustomizeButtonConfigData"> + <data key="path">paypal/style/checkout_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageButtonPayPalLabelConfigData"> + <data key="path">paypal/style/checkout_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">PayPal</data> + <data key="value">paypal</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageButtonPayLabelConfigData"> + <data key="path">paypal/style/checkout_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">Pay</data> + <data key="value">pay</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageButtonHorizontalLayoutConfigData"> + <data key="path">paypal/style/checkout_page_button_layout</data> + <data key="scope_id">1</data> + <data key="label">Horizontal</data> + <data key="value">horizontal</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageButtonVerticalLayoutConfigData"> + <data key="path">paypal/style/checkout_page_button_layout</data> + <data key="scope_id">1</data> + <data key="label">Vertical</data> + <data key="value">vertical</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageButtonPillShapeConfigData"> + <data key="path">paypal/style/checkout_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Pill</data> + <data key="value">pill</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageButtonRectangleShapeConfigData"> + <data key="path">paypal/style/checkout_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Rectangle</data> + <data key="value">rect</data> + </entity> + <entity name="StorefrontPaypalCheckoutPageButtonBlueColorConfigData"> + <data key="path">paypal/style/checkout_page_button_color</data> + <data key="scope_id">1</data> + <data key="label">Blue</data> + <data key="value">blue</data> + </entity> + <entity name="StorefrontPaypalProductPageEnableCustomizeButtonConfigData"> + <data key="path">paypal/style/product_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontPaypalProductPageDisableCustomizeButtonConfigData"> + <data key="path">paypal/style/product_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontPaypalProductPageButtonPayPalLabelConfigData"> + <data key="path">paypal/style/product_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">PayPal</data> + <data key="value">paypal</data> + </entity> + <entity name="StorefrontPaypalProductPageButtonBuyNowLabelConfigData"> + <data key="path">paypal/style/product_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">Buy Now</data> + <data key="value">buynow</data> + </entity> + <entity name="StorefrontPaypalProductPageButtonHorizontalLayoutConfigData"> + <data key="path">paypal/style/product_page_button_layout</data> + <data key="scope_id">1</data> + <data key="label">Horizontal</data> + <data key="value">horizontal</data> + </entity> + <entity name="StorefrontPaypalProductPageButtonVerticalLayoutConfigData"> + <data key="path">paypal/style/product_page_button_layout</data> + <data key="scope_id">1</data> + <data key="label">Vertical</data> + <data key="value">vertical</data> + </entity> + <entity name="StorefrontPaypalProductPageButtonPillShapeConfigData"> + <data key="path">paypal/style/product_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Pill</data> + <data key="value">pill</data> + </entity> + <entity name="StorefrontPaypalProductPageButtonRectangleShapeConfigData"> + <data key="path">paypal/style/product_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Rectangle</data> + <data key="value">rect</data> + </entity> + <entity name="StorefrontPaypalProductPageButtonSilverColorConfigData"> + <data key="path">paypal/style/product_page_button_color</data> + <data key="scope_id">1</data> + <data key="label">Silver</data> + <data key="value">silver</data> + </entity> + <entity name="StorefrontPaypalCartPageEnableCustomizeButtonConfigData"> + <data key="path">paypal/style/cart_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontPaypalCartPageDisableCustomizeButtonConfigData"> + <data key="path">paypal/style/cart_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontPaypalCartPageButtonCheckoutLabelConfigData"> + <data key="path">paypal/style/cart_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">Checkout</data> + <data key="value">checkout</data> + </entity> + <entity name="StorefrontPaypalCartPageButtonPayLabelConfigData"> + <data key="path">paypal/style/cart_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">Pay</data> + <data key="value">pay</data> + </entity> + <entity name="StorefrontPaypalCartPageButtonVerticalLayoutConfigData"> + <data key="path">paypal/style/cart_page_button_layout</data> + <data key="scope_id">1</data> + <data key="label">Vertical</data> + <data key="value">vertical</data> + </entity> + <entity name="StorefrontPaypalCartPageButtonResponsiveSizeConfigData"> + <data key="path">paypal/style/cart_page_button_size</data> + <data key="scope_id">1</data> + <data key="label">Responsive</data> + <data key="value">responsive</data> + </entity> + <entity name="StorefrontPaypalCartPageButtonPillShapeConfigData"> + <data key="path">paypal/style/cart_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Pill</data> + <data key="value">pill</data> + </entity> + <entity name="StorefrontPaypalCartPageButtonRectangleShapeConfigData"> + <data key="path">paypal/style/cart_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Rectangle</data> + <data key="value">rect</data> + </entity> + <entity name="StorefrontPaypalCartPageButtonGoldColorConfigData"> + <data key="path">paypal/style/cart_page_button_color</data> + <data key="scope_id">1</data> + <data key="label">Gold</data> + <data key="value">gold</data> + </entity> + <entity name="StorefrontPaypalMiniCartEnableCustomizeButtonConfigData"> + <data key="path">paypal/style/mini_cart_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontPaypalMiniCartDisableCustomizeButtonConfigData"> + <data key="path">paypal/style/mini_cart_page_button_customize</data> + <data key="scope_id">1</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontPaypalMiniCartButtonBuyNowLabelConfigData"> + <data key="path">paypal/style/mini_cart_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">Buy Now</data> + <data key="value">buynow</data> + </entity> + <entity name="StorefrontPaypalMiniCartButtonInstallmentLabelConfigData"> + <data key="path">paypal/style/mini_cart_page_button_label</data> + <data key="scope_id">1</data> + <data key="label">Installment</data> + <data key="value">installment</data> + </entity> + <entity name="StorefrontPaypalMiniCartButtonVerticalLayoutConfigData"> + <data key="path">paypal/style/mini_cart_page_button_layout</data> + <data key="scope_id">1</data> + <data key="label">Vertical</data> + <data key="value">vertical</data> + </entity> + <entity name="StorefrontPaypalMiniCartButtonPillShapeConfigData"> + <data key="path">paypal/style/mini_cart_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Pill</data> + <data key="value">pill</data> + </entity> + <entity name="StorefrontPaypalMiniCartButtonRectangleShapeConfigData"> + <data key="path">paypal/style/mini_cart_page_button_shape</data> + <data key="scope_id">1</data> + <data key="label">Rectangle</data> + <data key="value">rect</data> + </entity> + <entity name="StorefrontPaypalMiniCartButtonBlackColorConfigData"> + <data key="path">paypal/style/mini_cart_page_button_color</data> + <data key="scope_id">1</data> + <data key="label">Black</data> + <data key="value">black</data> + </entity> </entities> diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml index ba56243fdb391..f7d872bd43838 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml @@ -61,17 +61,12 @@ <entity name="DefaultApiSignature" type="api_signature"> <data key="value"/> </entity> - <entity name="Payer" type="paypal_buyer"> - <data key="buyerEmail">buyer.mpi@gmail.com</data> - <data key="buyerPassword">12345678</data> - </entity> <entity name="PayPalLabel" type="paypal"> - <data key="checkout">checkout</data> - <data key="credit">credit</data> - <data key="pay">pay</data> - <data key="buynow">buy now</data> - <data key="paypal">pay pal</data> - <data key="installment">installment</data> + <data key="checkout">Checkout</data> + <data key="pay">Pay</data> + <data key="buynow">Buy Now</data> + <data key="paypal">Paypal</data> + <data key="installment">Pagos en</data> </entity> <entity name="PayPalLayout" type="paypal"> <data key="horizontal">horizontal</data> @@ -84,7 +79,7 @@ </entity> <entity name="PayPalShape" type="paypal"> <data key="pill">pill</data> - <data key="rectangle">rectangle</data> + <data key="rectangle">rect</data> </entity> <entity name="PayPalColor" type="paypal"> <data key="gold">gold</data> diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalMerchantCountryData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalMerchantCountryData.xml new file mode 100644 index 0000000000000..fa8f936e89d6f --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalMerchantCountryData.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="MerchantUnitedStates"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">United States</data> + <data key="value">us</data> + </entity> + <entity name="MerchantUnitedKingdom"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">United Kingdon</data> + <data key="value">0</data> + </entity> + <entity name="MerchantCanada"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">Canada</data> + <data key="value">CA</data> + </entity> + <entity name="MerchantAustralia"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">Australia</data> + <data key="value">AU</data> + </entity> + <entity name="MerchantJapan"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">Japan</data> + <data key="value">JP</data> + </entity> + <entity name="MerchantFrance"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">France</data> + <data key="value">FR</data> + </entity> + <entity name="MerchantHongKong"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">HongKong</data> + <data key="value">HK</data> + </entity> + <entity name="MerchantNewZealand"> + <data key="path">paypal/general/merchant_country</data> + <data key="scope_id">1</data> + <data key="label">New Zealand</data> + <data key="value">NZ</data> + </entity> +</entities> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/CheckoutPaymentSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/CheckoutPaymentSection.xml index 9c22dd940890e..e6eb4d875c434 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/CheckoutPaymentSection.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/CheckoutPaymentSection.xml @@ -11,6 +11,7 @@ <element name="email" type="input" selector="#checkout-customer-email"/> <element name="payPalPaymentBraintree" type="radio" selector="#braintree_paypal"/> <element name="payPalFrame" type="iframe" selector="//iframe[contains(@class, 'zoid-component-frame zoid-visible')]" timeout="5"/> + <element name="smartButtonPayPalFrame" type="iframe" selector=".component-frame" timeout="10"/> <element name="PayPalPaymentRadio" type="radio" selector="input#paypal_express.radio" timeout="30"/> <element name="PayPalBtn" type="radio" selector=".paypal-button.paypal-button-number-0" timeout="30"/> </section> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalCheckoutAsGuestSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalCheckoutAsGuestSection.xml new file mode 100644 index 0000000000000..120ad4025ff09 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalCheckoutAsGuestSection.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="PayPalCheckoutAsGuestSection"> + <element name="CreditDebitBtn" type="button" selector="#createAccount"/> + </section> +</sections> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalPaymentSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalPaymentSection.xml index a64ff13e8f627..361016c40539c 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalPaymentSection.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalPaymentSection.xml @@ -16,8 +16,13 @@ <element name="reviewUserInfo" type="text" selector="[data-testid=personalized-banner-content]"/> <element name="cartIcon" type="text" selector="[data-testid='header-show-cart-dropdown-btn']"/> <element name="itemName" type="text" selector="//p[contains(@class,'CartDropdown_line') and text()='{{productName}}']" parameterized="true"/> - <element name="PayPalSubmitBtn" type="text" selector="#payment-submit-btn"/> + <element name="paypalSubmitBtn" type="text" selector="#payment-submit-btn"/> <element name="nextButton" type="button" selector="#btnNext"/> <element name="continueButton" type="button" selector=".continueButton"/> + <element name="userName" type="text" selector="#reviewUserInfo"/> + <element name="notYouLink" type="input" selector="#backToInputEmailLink"/> + <element name="shippingMethod" type="text" selector="#shippingMethodCharge > span.selectedShippingMethod"/> + <element name="paypalCart" type="text" selector="#transactionCart"/> + <element name="productNamePosition" type="text" selector=".itemName"/> </section> </sections> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalButtonOnStorefrontSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/StorefrontPayPalSmartButtonStylesSection.xml similarity index 52% rename from app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalButtonOnStorefrontSection.xml rename to app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/StorefrontPayPalSmartButtonStylesSection.xml index d97c0260adb0d..e9674a321ec0e 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalButtonOnStorefrontSection.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/StorefrontPayPalSmartButtonStylesSection.xml @@ -7,9 +7,10 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> - <section name="PayPalButtonOnStorefront"> - <element name="label" type="text" selector="[aria-label='{{label}}']" parameterized="true"/> - <element name="layout" type="text" selector="[data-layout='{{layout}}']" parameterized="true"/> + <section name="StorefrontPayPalSmartButtonStylesSection"> + <element name="label" type="text" selector="//div[contains(@class, 'paypal-button-number-0')]//div[@class='paypal-button-label-container']"/> + <element name="labelText" type="text" selector="//div[contains(@class, 'paypal-button-number-0')]//div[@class='paypal-button-label-container']//span[@class='paypal-button-text' and contains(text(), '{{label}}')]" parameterized="true"/> + <element name="layout" type="text" selector=".paypal-button-layout-{{layout}}" parameterized="true"/> <element name="size" type="text" selector="[data-size='{{size}}']" parameterized="true"/> <element name="shape" type="text" selector=".paypal-button-shape-{{shape}}" parameterized="true"/> <element name="color" type="text" selector=".paypal-button-color-{{color}}" parameterized="true"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/StorefrontPayPalOrderReviewSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/StorefrontPayPalOrderReviewSection.xml new file mode 100644 index 0000000000000..d739a5731e052 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Section/StorefrontPayPalOrderReviewSection.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="StorefrontPayPalOrderReviewSection"> + <element name="shippingMethod" type="select" selector="#shipping-method"/> + <element name="placeOrderBtn" type="button" selector="#review-button" timeout="30"/> + </section> +</sections> + diff --git a/app/code/Magento/Paypal/Test/Mftf/Suite/InContextPaypalSuite.xml b/app/code/Magento/Paypal/Test/Mftf/Suite/InContextPaypalSuite.xml new file mode 100644 index 0000000000000..b52fc05ca5a11 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Suite/InContextPaypalSuite.xml @@ -0,0 +1,30 @@ +<?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="InContextPaypalSuite"> + <before> + <!-- Login --> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + <!--Config PayPal Express Checkout--> + <actionGroup ref="ConfigPayPalExpressCheckoutActionGroup" stepKey="ConfigPayPalExpressCheckout"/> + <!-- Configure PayPal Express Checkout --> + <magentoCLI command="cache:clean" arguments="config full_page" stepKey="cleanFullPageCache"/> + </before> + <after> + <!-- Cleanup Paypal configurations --> + <magentoCLI command="config:set {{StorefrontPaypalMerchantAccountIdConfigData.path}} {{StorefrontPaypalMerchantAccountIdConfigData.value}}" stepKey="deleteMerchantId"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableInContextCheckoutConfigData.path}} {{StorefrontPaypalDisableInContextCheckoutConfigData.value}}" stepKey="disableInContextPayPal"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableConfigData.path}} {{StorefrontPaypalDisableConfigData.value}}" stepKey="disablePaypal"/> + <createData entity="SamplePaypalConfig" stepKey="setDefaultPaypalConfig"/> + <magentoCLI command="cache:clean" arguments="config full_page" stepKey="cleanFullPageCache"/> + </after> + <include> + <group name="paypalExpress"/> + </include> + </suite> +</suites> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckCreditButtonConfigurationTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckCreditButtonConfigurationTest.xml deleted file mode 100644 index c8089085b7ee5..0000000000000 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckCreditButtonConfigurationTest.xml +++ /dev/null @@ -1,80 +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="StorefrontCheckCreditButtonConfigurationTest"> - <annotations> - <features value="Paypal"/> - <stories value="Button Configuration"/> - <title value="Check Credit Button Configuration"/> - <description value="Admin is able to customize Credit button"/> - <severity value="AVERAGE"/> - <testCaseId value="MC-10900"/> - <group value="paypal"/> - <skip> - <issueId value="DEVOPS-3311"/> - </skip> - </annotations> - <before> - <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> - <createData entity="_defaultProduct" stepKey="createPreReqProduct"> - <requiredEntity createDataKey="createPreReqCategory"/> - </createData> - <!-- Create Customer --> - <createData entity="Simple_US_Customer" stepKey="createCustomer"/> - <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> - <!--Config PayPal Express Checkout--> - <comment userInput="config PayPal Express Checkout" stepKey="commemtConfigPayPalExpressCheckout"/> - <actionGroup ref="ConfigPayPalExpressCheckout" stepKey="ConfigPayPalExpressCheckout"/> - </before> - <after> - <deleteData stepKey="deleteCategory" createDataKey="createPreReqCategory"/> - <deleteData stepKey="deleteProduct" createDataKey="createPreReqProduct"/> - <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> - <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> - </after> - <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> - <!--Navigate to button configuration setting--> - <comment userInput="Navigate to button configuration setting in Admin site" stepKey="commentNavigateToButtonConfigurationInAdmin"/> - <actionGroup ref="OpenPayPalButtonCheckoutPage" stepKey="openPayPalButtonCheckoutPage"/> - <waitForElement selector="{{ButtonCustomization.customizeDrpDown}}" stepKey="seeCustomizeDropDown"/> - <selectOption selector="{{ButtonCustomization.customizeDrpDown}}" userInput="Yes" stepKey="enableButtonCustomization"/> - <!--Verify Credit Button value--> - <comment userInput="Verify Credit Button value" stepKey="commentVerifyDefaultValue2"/> - <selectOption selector="{{ButtonCustomization.label}}" userInput="{{PayPalLabel.credit}}" stepKey="selectCreditAsLabel"/> - <seeElement selector="{{ButtonCustomization.size}}" stepKey="seeSize"/> - <seeElement selector="{{ButtonCustomization.shape}}" stepKey="seeShape"/> - <dontSeeElement selector="{{ButtonCustomization.layout}}" stepKey="dontSeeLayout"/> - <dontSeeElement selector="{{ButtonCustomization.color}}" stepKey="dontSeeColor"/> - <!--Customize Credit Button--> - <selectOption selector="{{ButtonCustomization.size}}" userInput="{{PayPalSize.medium}}" stepKey="selectSize"/> - <selectOption selector="{{ButtonCustomization.shape}}" userInput="{{PayPalShape.pill}}" stepKey="selectShape"/> - <!--Save configuration--> - <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> - <waitForPageLoad stepKey="waitForConfigSave"/> - <openNewTab stepKey="openNewTab"/> - <amOnPage url="/" stepKey="openStorefront"/> - <!--Login to storefront as previously created customer--> - <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> - <argument name="Customer" value="$$createCustomer$$"/> - </actionGroup> - <actionGroup ref="AddProductToCheckoutPageActionGroup" stepKey="addProductToCheckoutPage"> - <argument name="Category" value="$$createPreReqCategory$$"/> - </actionGroup> - <!--set ID for iframe of PayPal group button--> - <executeJS function="jQuery('.zoid-component-frame.zoid-visible').attr('id', 'myIframe')" stepKey="clickOrderLink"/> - <!--switch to iframe of PayPal group button--> - <comment userInput="switch to iframe of PayPal group button" stepKey="commentSwitchToIframe"/> - <switchToIFrame userInput="myIframe" stepKey="clickPrintOrderLink"/> - <waitForElementVisible selector="{{CheckoutPaymentSection.PayPalBtn}}" stepKey="waitForPayPalBtn"/> - <seeElement selector="{{PayPalButtonOnStorefront.label(PayPalLabel.credit)}}{{PayPalButtonOnStorefront.size(PayPalSize.medium)}}" stepKey="seeButtonInMediumSize"/> - <seeElement selector="{{PayPalButtonOnStorefront.label(PayPalLabel.credit)}}{{PayPalButtonOnStorefront.shape(PayPalShape.pill)}}" stepKey="seeButtonInPillShape"/> - </test> -</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnMiniCartTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnMiniCartTest.xml new file mode 100644 index 0000000000000..19d73ae4d5277 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnMiniCartTest.xml @@ -0,0 +1,46 @@ +<?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="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnMiniCartTest" extends="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest"> + <annotations> + <features value="PayPal"/> + <stories value="PayPal Smart Button Configuration"/> + <title value="Check PayPal Smart Button configuration with Buy Now label on Mini Cart"/> + <description value="Admin is able to customize PayPal Smart Button with Buy Now label on Mini Cart"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28711"/> + <group value="paypal"/> + <group value="paypalExpress"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontPaypalMiniCartEnableCustomizeButtonConfigData.path}} {{StorefrontPaypalMiniCartEnableCustomizeButtonConfigData.value}}" stepKey="enableCustomizeButton"/> + <magentoCLI command="config:set {{StorefrontPaypalMiniCartButtonBuyNowLabelConfigData.path}} {{StorefrontPaypalMiniCartButtonBuyNowLabelConfigData.value}}" stepKey="setLabelForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalMiniCartButtonVerticalLayoutConfigData.path}} {{StorefrontPaypalMiniCartButtonVerticalLayoutConfigData.value}}" after="setLabelForPayPalSmartButton" stepKey="setLayoutForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalMiniCartButtonPillShapeConfigData.path}} {{StorefrontPaypalMiniCartButtonPillShapeConfigData.value}}" stepKey="setShapeForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalMiniCartButtonBlackColorConfigData.path}} {{StorefrontPaypalMiniCartButtonBlackColorConfigData.value}}" after="setShapeForPayPalSmartButton" stepKey="setColorForPayPalSmartButton"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalMiniCartDisableCustomizeButtonConfigData.path}} {{StorefrontPaypalMiniCartDisableCustomizeButtonConfigData.value}}" stepKey="disableCustomizeButton"/> + </after> + <!-- Add Product to Cart --> + <actionGroup ref="StorefrontAddSimpleProductToShoppingCartActionGroup" before="goToPayPalSmartButtonPage" stepKey="addProductToCart"> + <argument name="product" value="$createProduct$"/> + </actionGroup> + + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="goToPayPalSmartButtonPage"/> + <!-- Check PayPal smart button configurations --> + <actionGroup ref="AssertPayPalButtonLayoutActionGroup" stepKey="assertLayoutBtn"> + <argument name="label" value="{{PayPalLabel.buynow}}"/> + <argument name="layout" value="{{PayPalLayout.vertical}}"/> + <argument name="shape" value="{{PayPalShape.pill}}"/> + <argument name="color" value="{{PayPalColor.black}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest.xml new file mode 100644 index 0000000000000..f8e8e4b921779 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest.xml @@ -0,0 +1,57 @@ +<?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="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest"> + <annotations> + <features value="PayPal"/> + <stories value="PayPal Smart Button Configuration"/> + <title value="Check PayPal Smart Button configuration with Buy Now label on Product page"/> + <description value="Admin is able to customize PayPal Smart Button with Buy Now label on Product page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28711"/> + <group value="paypal"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!-- Create product --> + <createData entity="simpleProductWithoutCategory" stepKey="createProduct"/> + + <!-- Configure PayPal Smart Button --> + <magentoCLI command="config:set {{StorefrontPaypalProductPageEnableCustomizeButtonConfigData.path}} {{StorefrontPaypalProductPageEnableCustomizeButtonConfigData.value}}" stepKey="enableCustomizeButton"/> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonBuyNowLabelConfigData.path}} {{StorefrontPaypalProductPageButtonBuyNowLabelConfigData.value}}" stepKey="setLabelForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonVerticalLayoutConfigData.path}} {{StorefrontPaypalProductPageButtonVerticalLayoutConfigData.value}}" stepKey="setLayoutForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonRectangleShapeConfigData.path}} {{StorefrontPaypalProductPageButtonRectangleShapeConfigData.value}}" stepKey="setShapeForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonSilverColorConfigData.path}} {{StorefrontPaypalProductPageButtonSilverColorConfigData.value}}" stepKey="setColorForPayPalSmartButton"/> + </before> + <after> + <!-- Delete product --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + + <!-- Logout from Admin --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + <!-- Go to PayPal Smart Button page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToPayPalSmartButtonPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + + <!-- Switch to iframe of PayPal group button --> + <actionGroup ref="StorefrontSwitchToPayPalButtonIframeActionGroup" stepKey="switchToIframe"/> + + <!-- Check PayPal smart button configurations --> + <seeElement selector="{{StorefrontPayPalSmartButtonStylesSection.label}}" stepKey="seeButtonLabel"/> + <actionGroup ref="AssertPayPalButtonLayoutActionGroup" stepKey="assertLayoutBtn"> + <argument name="label" value="{{PayPalLabel.buynow}}"/> + <argument name="layout" value="{{PayPalLayout.vertical}}"/> + <argument name="shape" value="{{PayPalShape.rectangle}}"/> + <argument name="color" value="{{PayPalColor.silver}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithCheckoutLabelOnCartPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithCheckoutLabelOnCartPageTest.xml new file mode 100644 index 0000000000000..f49900337546b --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithCheckoutLabelOnCartPageTest.xml @@ -0,0 +1,51 @@ +<?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="StorefrontCheckPayPalSmartButtonWithCheckoutLabelOnCartPageTest" extends="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest"> + <annotations> + <features value="PayPal"/> + <stories value="PayPal Smart Button Configuration"/> + <title value="Check PayPal Smart Button configuration with Checkout label on Cart page"/> + <description value="Admin is able to customize PayPal Smart Button with Checkout label on Cart page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28711"/> + <group value="paypal"/> + <group value="paypalExpress"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontPaypalCartPageEnableCustomizeButtonConfigData.path}} {{StorefrontPaypalCartPageEnableCustomizeButtonConfigData.value}}" stepKey="enableCustomizeButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonCheckoutLabelConfigData.path}} {{StorefrontPaypalCartPageButtonCheckoutLabelConfigData.value}}" stepKey="setLabelForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonVerticalLayoutConfigData.path}} {{StorefrontPaypalCartPageButtonVerticalLayoutConfigData.value}}" stepKey="setLayoutForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonPillShapeConfigData.path}} {{StorefrontPaypalCartPageButtonPillShapeConfigData.value}}" stepKey="setShapeForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonGoldColorConfigData.path}} {{StorefrontPaypalCartPageButtonGoldColorConfigData.value}}" stepKey="setColorForPayPalSmartButton"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalCartPageDisableCustomizeButtonConfigData.path}} {{StorefrontPaypalCartPageDisableCustomizeButtonConfigData.value}}" stepKey="disableCustomizeButton"/> + </after> + <!-- Add Product to Cart --> + <actionGroup ref="StorefrontAddSimpleProductToShoppingCartActionGroup" before="goToPayPalSmartButtonPage" stepKey="addProductToCart"> + <argument name="product" value="$createProduct$"/> + </actionGroup> + + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToPayPalSmartButtonPage"/> + + <actionGroup ref="StorefrontSwitchToPayPalButtonIframeActionGroup" stepKey="switchToIframe"> + <argument name="elementNumber" value="1"/> + </actionGroup> + <!-- Check PayPal smart button configurations --> + <seeElement selector="{{StorefrontPayPalSmartButtonStylesSection.labelText(PayPalLabel.checkout)}}" stepKey="seeButtonLabelText"/> + <actionGroup ref="AssertPayPalButtonLayoutActionGroup" stepKey="assertLayoutBtn"> + <argument name="label" value="{{PayPalLabel.checkout}}"/> + <argument name="layout" value="{{PayPalLayout.vertical}}"/> + <argument name="shape" value="{{PayPalShape.pill}}"/> + <argument name="color" value="{{PayPalColor.gold}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayLabelOnCartPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayLabelOnCartPageTest.xml new file mode 100644 index 0000000000000..5564476596b1e --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayLabelOnCartPageTest.xml @@ -0,0 +1,48 @@ +<?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="StorefrontCheckPayPalSmartButtonWithPayLabelOnCartPageTest" extends="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest"> + <annotations> + <features value="PayPal"/> + <stories value="PayPal Smart Button Configuration"/> + <title value="Check PayPal Smart Button configuration with Pay label on Cart page"/> + <description value="Admin is able to customize PayPal Smart Button with Pay label on Cart page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28711"/> + <group value="paypal"/> + <group value="paypalExpress"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontPaypalCartPageEnableCustomizeButtonConfigData.path}} {{StorefrontPaypalCartPageEnableCustomizeButtonConfigData.value}}" stepKey="enableCustomizeButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonPayLabelConfigData.path}} {{StorefrontPaypalCartPageButtonPayLabelConfigData.value}}" stepKey="setLabelForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonVerticalLayoutConfigData.path}} {{StorefrontPaypalCartPageButtonVerticalLayoutConfigData.value}}" stepKey="setLayoutForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonRectangleShapeConfigData.path}} {{StorefrontPaypalCartPageButtonRectangleShapeConfigData.value}}" stepKey="setShapeForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCartPageButtonGoldColorConfigData.path}} {{StorefrontPaypalCartPageButtonGoldColorConfigData.value}}" stepKey="setColorForPayPalSmartButton"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalCartPageDisableCustomizeButtonConfigData.path}} {{StorefrontPaypalCartPageDisableCustomizeButtonConfigData.value}}" stepKey="disableCustomizeButton"/> + </after> + <!-- Add Product to Cart --> + <actionGroup ref="StorefrontAddSimpleProductToShoppingCartActionGroup" before="goToPayPalSmartButtonPage" stepKey="addProductToCart"> + <argument name="product" value="$createProduct$"/> + </actionGroup> + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToPayPalSmartButtonPage"/> + <actionGroup ref="StorefrontSwitchToPayPalButtonIframeActionGroup" stepKey="switchToIframe"> + <argument name="elementNumber" value="1"/> + </actionGroup> + <!-- Check PayPal smart button configurations --> + <actionGroup ref="AssertPayPalButtonLayoutActionGroup" stepKey="assertLayoutBtn"> + <argument name="label" value="{{PayPalLabel.pay}}"/> + <argument name="layout" value="{{PayPalLayout.vertical}}"/> + <argument name="shape" value="{{PayPalShape.rectangle}}"/> + <argument name="color" value="{{PayPalColor.gold}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayLabelOnCheckoutPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayLabelOnCheckoutPageTest.xml new file mode 100644 index 0000000000000..7b877d66f27ea --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayLabelOnCheckoutPageTest.xml @@ -0,0 +1,62 @@ +<?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="StorefrontCheckPayPalSmartButtonWithPayLabelOnCheckoutPageTest" extends="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest"> + <annotations> + <features value="PayPal"/> + <stories value="PayPal Smart Button Configuration"/> + <title value="Check PayPal Smart Button configuration with Pay label on Checkout page"/> + <description value="Admin is able to customize PayPal Smart Button with Pay label on Checkout page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28711"/> + <group value="paypal"/> + <group value="paypalExpress"/> + </annotations> + <before> + <createData entity="_defaultCategory" before="createProduct" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create Customer --> + <createData entity="Simple_US_Customer" after="createProduct" stepKey="createCustomer"/> + + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageEnableCustomizeButtonConfigData.path}} {{StorefrontPaypalCheckoutPageEnableCustomizeButtonConfigData.value}}" stepKey="enableCustomizeButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonPayLabelConfigData.path}} {{StorefrontPaypalCheckoutPageButtonPayLabelConfigData.value}}" stepKey="setLabelForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonRectangleShapeConfigData.path}} {{StorefrontPaypalCheckoutPageButtonRectangleShapeConfigData.value}}" stepKey="setShapeForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonVerticalLayoutConfigData.path}} {{StorefrontPaypalCheckoutPageButtonVerticalLayoutConfigData.value}}" stepKey="setLayoutForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonBlueColorConfigData.path}} {{StorefrontPaypalCheckoutPageButtonBlueColorConfigData.value}}" stepKey="setColorForPayPalSmartButton"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageDisableCustomizeButtonConfigData.path}} {{StorefrontPaypalCheckoutPageDisableCustomizeButtonConfigData.value}}" stepKey="disableCustomizeButton"/> + <!-- Delete Category --> + <deleteData createDataKey="createCategory" after="deleteProduct" stepKey="deleteCategory"/> + <!--Logout from customer account--> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" before="deleteCustomer" stepKey="logoutCustomer"/> + <!--Delete customer --> + <deleteData createDataKey="createCustomer" after="deleteCategory" stepKey="deleteCustomer"/> + </after> + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" before="goToPayPalSmartButtonPage" stepKey="loginAsCustomer"> + <argument name="Customer" value="$createCustomer$"/> + </actionGroup> + + <actionGroup ref="AddProductToCheckoutPageActionGroup" stepKey="goToPayPalSmartButtonPage"> + <argument name="Category" value="$createCategory$"/> + </actionGroup> + <!-- Check PayPal smart button configurations --> + <actionGroup ref="AssertPayPalButtonLayoutActionGroup" stepKey="assertLayoutBtn"> + <argument name="label" value="{{PayPalLabel.pay}}"/> + <argument name="shape" value="{{PayPalShape.rectangle}}"/> + <argument name="layout" value="{{PayPalLayout.vertical}}"/> + <argument name="color" value="{{PayPalColor.blue}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayPalLabelOnCheckoutPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayPalLabelOnCheckoutPageTest.xml new file mode 100644 index 0000000000000..400e0cfe3cc13 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayPalLabelOnCheckoutPageTest.xml @@ -0,0 +1,67 @@ +<?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="StorefrontCheckPayPalSmartButtonWithPayPalLabelOnCheckoutPageTest" extends="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest"> + <annotations> + <features value="PayPal"/> + <stories value="PayPal Smart Button Configuration"/> + <title value="Check PayPal Smart Button configuration with PayPal label on Checkout page"/> + <description value="Admin is able to customize PayPal Smart Button with PayPal label on Checkout page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28711"/> + <group value="paypal"/> + <group value="paypalExpress"/> + </annotations> + <before> + <createData entity="_defaultCategory" before="createProduct" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create Customer --> + <createData entity="Simple_US_Customer" after="createProduct" stepKey="createCustomer"/> + + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageEnableCustomizeButtonConfigData.path}} {{StorefrontPaypalCheckoutPageEnableCustomizeButtonConfigData.value}}" stepKey="enableCustomizeButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonPayPalLabelConfigData.path}} {{StorefrontPaypalCheckoutPageButtonPayPalLabelConfigData.value}}" stepKey="setLabelForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonVerticalLayoutConfigData.path}} {{StorefrontPaypalCheckoutPageButtonVerticalLayoutConfigData.value}}" stepKey="setLayoutForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonPillShapeConfigData.path}} {{StorefrontPaypalCheckoutPageButtonPillShapeConfigData.value}}" stepKey="setShapeForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageButtonBlueColorConfigData.path}} {{StorefrontPaypalCheckoutPageButtonBlueColorConfigData.value}}" stepKey="setColorForPayPalSmartButton"/> + <magentoCLI command="cache:clean" arguments="full_page" stepKey="cleanFullPageCache"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalCheckoutPageDisableCustomizeButtonConfigData.path}} {{StorefrontPaypalCheckoutPageDisableCustomizeButtonConfigData.value}}" stepKey="disableCustomizeButton"/> + + <!-- Delete Category --> + <deleteData createDataKey="createCategory" after="deleteProduct" stepKey="deleteCategory"/> + + <!--Logout from customer account--> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" before="deleteCustomer" stepKey="logoutCustomer"/> + + <!--Delete customer --> + <deleteData createDataKey="createCustomer" after="deleteCategory" stepKey="deleteCustomer"/> + </after> + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" before="goToPayPalSmartButtonPage" stepKey="loginAsCustomer"> + <argument name="Customer" value="$createCustomer$"/> + </actionGroup> + + <actionGroup ref="AddProductToCheckoutPageActionGroup" stepKey="goToPayPalSmartButtonPage"> + <argument name="Category" value="$createCategory$"/> + </actionGroup> + <!-- Check PayPal smart button configurations --> + <remove keyForRemoval="seeButtonLabelText"/> + <actionGroup ref="AssertPayPalButtonLayoutInPaypalLabelActionGroup" stepKey="assertLayoutBtn"> + <argument name="label" value="{{PayPalLabel.paypal}}"/> + <argument name="layout" value="{{PayPalLayout.vertical}}"/> + <argument name="shape" value="{{PayPalShape.pill}}"/> + <argument name="color" value="{{PayPalColor.blue}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayPalLabelOnProductPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayPalLabelOnProductPageTest.xml new file mode 100644 index 0000000000000..6e84ffa25871b --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontCheckPayPalSmartButtonWithPayPalLabelOnProductPageTest.xml @@ -0,0 +1,37 @@ +<?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="StorefrontCheckPayPalSmartButtonWithPayPalLabelOnProductPageTest" extends="StorefrontCheckPayPalSmartButtonWithBuyNowLabelOnProductPageTest"> + <annotations> + <features value="PayPal"/> + <stories value="PayPal Smart Button Configuration"/> + <title value="Check PayPal Smart Button configuration with PayPal label on Product page"/> + <description value="Admin is able to customize PayPal Smart Button with PayPal label on Product page"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28711"/> + <group value="paypal"/> + <group value="paypalExpress"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonPayPalLabelConfigData.path}} {{StorefrontPaypalProductPageButtonPayPalLabelConfigData.value}}" stepKey="setLabelForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonHorizontalLayoutConfigData.path}} {{StorefrontPaypalProductPageButtonHorizontalLayoutConfigData.value}}" stepKey="setLayoutForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonPillShapeConfigData.path}} {{StorefrontPaypalProductPageButtonPillShapeConfigData.value}}" stepKey="setShapeForPayPalSmartButton"/> + <magentoCLI command="config:set {{StorefrontPaypalProductPageButtonSilverColorConfigData.path}} {{StorefrontPaypalProductPageButtonSilverColorConfigData.value}}" stepKey="setColorForPayPalSmartButton"/> + </before> + <!-- Check PayPal smart button configurations --> + <remove keyForRemoval="seeButtonLabelText"/> + <actionGroup ref="AssertPayPalButtonLayoutInPaypalLabelActionGroup" stepKey="assertLayoutBtn"> + <argument name="label" value="{{PayPalLabel.paypal}}"/> + <argument name="layout" value="{{PayPalLayout.horizontal}}"/> + <argument name="shape" value="{{PayPalShape.pill}}"/> + <argument name="color" value="{{PayPalColor.silver}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml index b95d645787fed..d27ac4c4a92f5 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml @@ -11,46 +11,38 @@ <test name="StorefrontPaypalSmartButtonInCheckoutPageTest"> <annotations> <features value="Paypal"/> - <stories value="Generic checkout skeleton flow"/> - <title value="Mainflow of Paypal Smart Button"/> - <description value="Users are able to place order using Paypal Smart Button"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button In-Context on Checkout Page"/> + <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"/> - <group value="paypal"/> + <group value="paypalExpress"/> </annotations> <before> - + <!-- Login --> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> <!-- Create Product --> <createData entity="_defaultCategory" stepKey="createCategory"/> <createData entity="_defaultProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> + <requiredEntity createDataKey="createCategory"/> </createData> - <!-- Create Customer --> <createData entity="Simple_US_Customer" stepKey="createCustomer"/> - - <!-- Set Paypal express config --> - <magentoCLI command="config:set {{StorefrontPaypalEnableConfigData.path}} {{StorefrontPaypalEnableConfigData.value}}" stepKey="enablePaypal"/> - <magentoCLI command="config:set {{StorefrontPaypalEnableInContextCheckoutConfigData.path}} {{StorefrontPaypalEnableInContextCheckoutConfigData.value}}" stepKey="enableInContextPayPal"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableTransferCartLineConfigData.path}} {{StorefrontPaypalDisableTransferCartLineConfigData.value}}" stepKey="disableTransferCartLine"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableTransferShippingOptionConfigData.path}} {{StorefrontPaypalDisableTransferShippingOptionConfigData.value}}" stepKey="disableTransferShippingOption"/> + <magentoCLI command="config:set {{StorefrontPaypalExpressSalePaymentActionOptionConfigData.path}} {{StorefrontPaypalExpressSalePaymentActionOptionConfigData.value}}" stepKey="setPaymentAction"/> <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> - <magentoCLI command="config:set {{StorefrontPaypalMerchantAccountIdConfigData.path}} {{_CREDS.magento/paypal_express_checkout_us_merchant_id}}" stepKey="setMerchantId"/> - <createData entity="PaypalConfig" stepKey="createPaypalExpressConfig"/> - - <!-- Login --> - <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + <!--Enable Free Shipping--> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> </before> <after> - <!-- Cleanup Paypal configurations --> - <magentoCLI command="config:set {{StorefrontPaypalMerchantAccountIdConfigData.path}} {{StorefrontPaypalMerchantAccountIdConfigData.value}}" stepKey="deleteMerchantId"/> - <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> - <magentoCLI command="config:set {{StorefrontPaypalDisableInContextCheckoutConfigData.path}} {{StorefrontPaypalDisableInContextCheckoutConfigData.value}}" stepKey="disableInContextPayPal"/> - <magentoCLI command="config:set {{StorefrontPaypalDisableConfigData.path}} {{StorefrontPaypalDisableConfigData.value}}" stepKey="disablePaypal"/> - <createData entity="SamplePaypalConfig" stepKey="setDefaultPaypalConfig"/> - + <magentoCLI command="config:set {{StorefrontPaypalEnableTransferCartLineConfigData.path}} {{StorefrontPaypalEnableTransferCartLineConfigData.value}}" stepKey="enableTransferCartLine"/> + <magentoCLI command="config:set {{StorefrontPaypalExpressAuthorizationPaymentActionOptionConfigData.path}} {{StorefrontPaypalExpressAuthorizationPaymentActionOptionConfigData.value}}" stepKey="setPaymentAction"/> + <magentoCLI command="config:set {{DisableFreeShippingMethod.path}} {{DisableFreeShippingMethod.value}}" stepKey="disableFreeShipping"/> <!-- Delete product --> <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> - + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> <!--Delete customer --> <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> @@ -62,19 +54,57 @@ <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> <argument name="Customer" value="$$createCustomer$$"/> </actionGroup> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage"/> + <actionGroup ref="StorefrontAddProductToCartFromCategoryActionGroup" stepKey="addProductToCart"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Free Shipping')}}" stepKey="selectFlatShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> - <!-- Place an order using PayPal payment method --> - <actionGroup ref="CreatePayPalOrderWithSelectedPaymentMethodActionGroup" stepKey="createPayPalOrder"> - <argument name="Category" value="$$createCategory$$"/> - <argument name="payerName" value="{{Payer.firstName}}"/> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + + <!--Assert grand total--> + <actionGroup ref="VerifyCheckoutPaymentOrderSummaryActionGroup" stepKey="verifyCheckoutPaymentOrderSummary"> + <argument name="orderSummarySubTotal" value="$123.00"/> + <argument name="orderSummaryShippingTotal" value="$0.00"/> + <argument name="orderSummaryTotal" value="$123.00"/> </actionGroup> - <!-- PayPal checkout --> - <actionGroup ref="StorefrontPayOrderOnPayPalCheckoutActionGroup" stepKey="payOrderOnPayPalCheckout"> - <argument name="productName" value="$$createProduct.name$$"/> + <dontSeeElement selector="{{StorefrontOrderReviewSection.taxCost}}" stepKey="taxAssertion"/> + + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" stepKey="clickPlaceOrder"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" stepKey="clickPayPalBtn"/> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="clickPayPalBtn" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> </actionGroup> + <!--Transfer Cart Line and Shipping Method assertion--> + <actionGroup ref="PayPalAssertTransferLineAndShippingMethodNotExistActionGroup" stepKey="assertPayPalSettings"/> + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + <!-- I see order successful Page instead of Order Review Page --> <actionGroup ref="AssertStorefrontCheckoutSuccessActionGroup" stepKey="assertCheckoutSuccess"/> + + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!--Go to Admin and check order information--> + <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGrid"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <actionGroup ref="AdminOrderGridClickFirstRowActionGroup" stepKey="clickOrderRow"/> + <actionGroup ref="AdminOrderViewCheckStatusActionGroup" stepKey="seeAdminOrderStatus"> + <argument name="status" value="Processing"/> + </actionGroup> + <actionGroup ref="AdminAssertNoAuthorizeButtonOnOrderPageActionGroup" stepKey="dontSeeOrderWaitingForAuthorize"/> </test> </tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInMiniCartPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInMiniCartPageTest.xml new file mode 100644 index 0000000000000..f50d2fc9ad9a4 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInMiniCartPageTest.xml @@ -0,0 +1,92 @@ +<?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="StorefrontPaypalSmartButtonInMiniCartPageTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button In-Context on Mini Cart Page"/> + <description value="Users are able to place order using Paypal Smart Button on Mini Cart Page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-27604"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!-- Login --> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + + <!-- Create Product --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create Customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <magentoCLI command="config:set {{StorefrontPaypalEnableTransferCartLineConfigData.path}} {{StorefrontPaypalEnableTransferCartLineConfigData.value}}" stepKey="enableTransferCartLine"/> + <magentoCLI command="config:set {{StorefrontPaypalEnableTransferShippingOptionConfigData.path}} {{StorefrontPaypalEnableTransferShippingOptionConfigData.value}}" stepKey="enableTransferShippingOption"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableTransferShippingOptionConfigData.path}} {{StorefrontPaypalDisableTransferShippingOptionConfigData.value}}" stepKey="disableTransferShippingOption"/> + + <!-- Delete Product --> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + + <!--Delete Customer --> + <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> + + <!-- Logout --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage"/> + <actionGroup ref="StorefrontAddProductToCartFromCategoryActionGroup" stepKey="addProductToCart"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + <actionGroup ref="StorefrontOpenMiniCartActionGroup" stepKey="openMiniShoppingCart"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" stepKey="clickPayPalBtn"/> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentFromCartAccountActionGroup" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Transfer Cart Line and Shipping Method assertion--> + <actionGroup ref="PayPalAssertTransferLineAndShippingMethodActionGroup" stepKey="assertPayPalSettings"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + + <!--Click PayPal button and go back to Magento site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoActionGroup" stepKey="goBackToMagentoSite"/> + + <actionGroup ref="StorefrontSelectShippingMethodOnOrderReviewPageActionGroup" stepKey="selectShippingMethod"/> + + <!--Assert grand total--> + <actionGroup ref="StorefrontAssertOrderReviewSummaryWithTaxActionGroup" stepKey="verifyCheckoutPaymentOrderSummary"> + <argument name="orderSummarySubTotal" value="$123.00"/> + <argument name="orderSummaryShippingTotal" value="$5.00"/> + <argument name="orderSummaryTax" value="$0.00"/> + <argument name="orderSummaryTotal" value="$128.00"/> + </actionGroup> + + <!--SubmitOrder--> + <actionGroup ref="StorefrontPlaceOrderOnOrderReviewPageActionGroup" stepKey="clickPlaceOrderBtn"/> + + <!-- I see order successful Page instead of Order Review Page --> + <actionGroup ref="AssertStorefrontCheckoutSuccessActionGroup" stepKey="assertCheckoutSuccess"/> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInProductPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInProductPageTest.xml new file mode 100644 index 0000000000000..3e4f5c8b4da30 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInProductPageTest.xml @@ -0,0 +1,115 @@ +<?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="StorefrontPaypalSmartButtonInProductPageTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button In-Context on Product Page"/> + <description value="Users are able to place order using Paypal Smart Button on Product Pag, payment action is Order"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-26167"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!-- Login --> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + <!-- Create Product --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create Customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <magentoCLI command="config:set {{StorefrontPaypalExpressOrderPaymentActionOptionConfigData.path}} {{StorefrontPaypalExpressOrderPaymentActionOptionConfigData.value}}" stepKey="setPaymentAction"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableTransferCartLineConfigData.path}} {{StorefrontPaypalDisableTransferCartLineConfigData.value}}" stepKey="disableTransferCartLine"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableTransferShippingOptionConfigData.path}} {{StorefrontPaypalDisableTransferShippingOptionConfigData.value}}" stepKey="disableTransferShippingOption"/> + <!-- Enable free shipping method --> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShippingMethod"/> + + <!-- Create Tax Rule --> + <createData entity="US_CA_Rate_1" stepKey="initialTaxRate"/> + <actionGroup ref="AdminCreateTaxRuleActionGroup" stepKey="createTaxRule"> + <argument name="taxRate" value="$$initialTaxRate$$"/> + <argument name="taxRule" value="SimpleTaxRule"/> + </actionGroup> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> + <magentoCLI command="config:set {{StorefrontPaypalExpressAuthorizationPaymentActionOptionConfigData.path}} {{StorefrontPaypalExpressAuthorizationPaymentActionOptionConfigData.value}}" stepKey="returnPaymentActionDefaultValue"/> + <magentoCLI command="config:set {{StorefrontPaypalEnableTransferCartLineConfigData.path}} {{StorefrontPaypalEnableTransferCartLineConfigData.value}}" stepKey="enableTransferCartLine"/> + <!-- Disable free shipping method --> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + + <!-- Delete product --> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + + <!--Delete customer --> + <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> + + <!--Delete Tax Rule--> + <actionGroup ref="AdminDeleteTaxRule" stepKey="deleteTaxRule"> + <argument name="taxRuleCode" value="{{SimpleTaxRule.code}}" /> + </actionGroup> + + <!-- Logout --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrlKey" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" stepKey="clickPayPalBtn"/> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountTwoStepActionGroup" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + <!--Transfer Cart Line and Shipping Method assertion--> + <actionGroup ref="PayPalAssertTransferLineAndShippingMethodNotExistActionGroup" stepKey="assertPayPalSettings"/> + + <!--Click PayPal button and go back to Magento site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" stepKey="goBackToMagentoSite"/> + + <actionGroup ref="StorefrontSelectShippingMethodOnOrderReviewPageActionGroup" stepKey="selectShippingMethod"> + <argument name="shippingMethod" value="Free - $0.00"/> + </actionGroup> + + <!--Assert grand total--> + <actionGroup ref="StorefrontAssertOrderReviewSummaryWithTaxActionGroup" stepKey="verifyCheckoutPaymentOrderSummary"> + <argument name="orderSummarySubTotal" value="$123.00"/> + <argument name="orderSummaryShippingTotal" value="$0.00"/> + <argument name="orderSummaryTotal" value="$133.15"/> + <argument name="orderSummaryTax" value="$10.15"/> + </actionGroup> + + <actionGroup ref="StorefrontPlaceOrderOnOrderReviewPageActionGroup" stepKey="clickPlaceOrderBtn"/> + <!-- I see order successful Page instead of Order Review Page --> + <actionGroup ref="AssertStorefrontCheckoutSuccessActionGroup" stepKey="assertCheckoutSuccess"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!--Go to Admin and check order information--> + <actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGrid"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <actionGroup ref="AdminOrderGridClickFirstRowActionGroup" stepKey="clickOrderRow"/> + <actionGroup ref="AdminOrderViewCheckStatusActionGroup" stepKey="seeAdminOrderStatus"> + <argument name="status" value="Processing"/> + </actionGroup> + <actionGroup ref="AdminAssertAuthorizeButtonOnOrderPageActionGroup" stepKey="seeOrderWaitingForAuthorize"/> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInShoppingCartPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInShoppingCartPageTest.xml new file mode 100644 index 0000000000000..62ebeea2d65be --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInShoppingCartPageTest.xml @@ -0,0 +1,120 @@ +<?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="StorefrontPaypalSmartButtonInShoppingCartPageTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button In-Context on Cart Page"/> + <description value="Users are able to perform PayPal Express Checkout method using PayPal Smart Button on Shopping Cart, payment action is Sale, make sure checkout as guest is not available"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-27605"/> + </annotations> + <before> + <!-- Login --> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + + <!--Config PayPal Express Checkout--> + <actionGroup ref="ConfigPayPalExpressCheckoutActionGroup" stepKey="ConfigPayPalExpressCheckout"/> + <!-- Create Product --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create Customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + + <!--Advanced Settings--> + <magentoCLI command="config:set {{StorefrontPaypalEnableTransferCartLineConfigData.path}} {{StorefrontPaypalEnableTransferCartLineConfigData.value}}" stepKey="enableTransferCartLine"/> + <magentoCLI command="config:set {{StorefrontPaypalEnableTransferShippingOptionConfigData.path}} {{StorefrontPaypalEnableTransferShippingOptionConfigData.value}}" stepKey="enableTransferShippingOption"/> + <magentoCLI command="config:set {{StorefrontPaypalExpressSalePaymentActionOptionConfigData.path}} {{StorefrontPaypalExpressSalePaymentActionOptionConfigData.value}}" stepKey="setPaymentActionSale"/> + <magentoCLI command="config:set {{StorefrontPaypalExpressEnableCheckoutAsGuestConfigData.path}} {{StorefrontPaypalExpressEnableCheckoutAsGuestConfigData.value}}" stepKey="enableCheckoutAsGuest"/> + </before> + <after> + <!-- Cleanup Paypal configurations --> + <magentoCLI command="config:set {{StorefrontPaypalMerchantAccountIdConfigData.path}} {{StorefrontPaypalMerchantAccountIdConfigData.value}}" stepKey="deleteMerchantId"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableInContextCheckoutConfigData.path}} {{StorefrontPaypalDisableInContextCheckoutConfigData.value}}" stepKey="disableInContextPayPal"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableConfigData.path}} {{StorefrontPaypalDisableConfigData.value}}" stepKey="disablePaypal"/> + <createData entity="SamplePaypalConfig" stepKey="setDefaultPaypalConfig"/> + + <!--Clean Advanced Settings--> + <magentoCLI command="config:set {{StorefrontPaypalDisableTransferShippingOptionConfigData.path}} {{StorefrontPaypalDisableTransferShippingOptionConfigData.value}}" stepKey="disableTransferShippingOption"/> + <magentoCLI command="config:set {{StorefrontPaypalExpressAuthorizationPaymentActionOptionConfigData.path}} {{StorefrontPaypalExpressAuthorizationPaymentActionOptionConfigData.value}}" stepKey="setPaymentActionAuthorization"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> + + <!-- Delete Product --> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + + <!--Delete Customer --> + <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> + + <!-- Logout --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage"/> + <actionGroup ref="StorefrontAddProductToCartFromCategoryActionGroup" stepKey="addProductToCart"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> + + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="openShoppingCart"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" stepKey="clickPayPalBtn"> + <argument name="elementNumber" value="1"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentFromCartAccountActionGroup" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Transfer Cart Line and Shipping Method assertion--> + <actionGroup ref="PayPalAssertTransferLineAndShippingMethodActionGroup" stepKey="assertPayPalSettings"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + + <!--Click PayPal button and go back to Magento site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoActionGroup" stepKey="goBackToMagentoSite"/> + + <actionGroup ref="StorefrontSelectShippingMethodOnOrderReviewPageActionGroup" stepKey="selectShippingMethod"/> + + <!--Assert grand total--> + <actionGroup ref="StorefrontAssertOrderReviewSummaryWithTaxActionGroup" stepKey="verifyCheckoutPaymentOrderSummary"> + <argument name="orderSummarySubTotal" value="$123.00"/> + <argument name="orderSummaryShippingTotal" value="$5.00"/> + <argument name="orderSummaryTotal" value="$128.00"/> + <argument name="orderSummaryTax" value="$0.00"/> + </actionGroup> + + <!--SubmitOrder--> + <actionGroup ref="StorefrontPlaceOrderOnOrderReviewPageActionGroup" stepKey="clickPlaceOrderBtn"/> + + <!-- I see order successful Page instead of Order Review Page --> + <actionGroup ref="AssertStorefrontCheckoutSuccessActionGroup" stepKey="assertCheckoutSuccess"/> + + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="getOrderNumber"/> + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="addFilterToGridAndOpenOrder"> + <argument name="orderId" value="{$getOrderNumber}"/> + </actionGroup> + <actionGroup ref="AdminAssertNoAuthorizeButtonOnOrderPageActionGroup" stepKey="dontSeeOrderWaitingForAuthorize"/> + <actionGroup ref="AdminOrderViewCheckStatusActionGroup" stepKey="checkOrderStatus"> + <argument name="status" value="Processing"/> + </actionGroup> + <actionGroup ref="AdminOpenInvoiceFromOrderPageActionGroup" stepKey="openInvoiceFromOrder"/> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithAUDCurrencyTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithAUDCurrencyTest.xml new file mode 100644 index 0000000000000..4d4f4e8b89957 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithAUDCurrencyTest.xml @@ -0,0 +1,68 @@ +<?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="StorefrontPaypalSmartButtonWithAUDCurrencyTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button with Australian currency"/> + <description value="Users are able to place order using Paypal Smart Button using Australian currency"/> + <severity value="MAJOR"/> + <testCaseId value="MC-33274"/> + <group value="paypalExpress"/> + </annotations> + <before> + + <!--Enable Advanced Setting--> + <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantAustralia.path}} {{MerchantAustralia.value}}" stepKey="setMerchantCountryUK"/> + <!--Set Currency options for Default Config--> + <magentoCLI command="config:set {{SetCurrencyAUDBaseConfig.path}} {{SetCurrencyAUDBaseConfig.value}}" stepKey="setCurrencyBaseEUR"/> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForAUD.value}}" stepKey="setAllowedCurrencyEURandUSD"/> + <magentoCLI command="config:set {{SetDefaultCurrencyAUDConfig.path}} {{SetDefaultCurrencyAUDConfig.value}}" stepKey="setCurrencyDefaultEUR"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseEURWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForAUD.value}}" stepKey="setAllowedCurrencyWebsitesForEURandUSD"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyAUDConfig.scope}} --scope-code={{SetDefaultCurrencyAUDConfig.scope_code}} {{SetDefaultCurrencyAUDConfig.path}} {{SetDefaultCurrencyAUDConfig.value}}" stepKey="setCurrencyDefaultEURWebsites"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantUnitedStates.path}} {{MerchantUnitedStates.value}}" stepKey="setMerchantCountryDefault"/> + </after> + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" after="guestCheckoutFillingShippingSection" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" after="waitForPlaceOrderButton" stepKey="guestSelectCheckMoneyOrderPayment"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" after="guestSelectCheckMoneyOrderPayment" stepKey="guestPlaceOrder"> + <argument name="elementNumber" value="0"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="guestPlaceOrder" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + <waitForElement after="submitPayment" selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="waitForOrderNumber"/> + + <actionGroup ref="AdminAssertCurrencyInOrderActionGroup" stepKey="seeEURandUSDRate"> + <argument name="rate" value="AUD / USD rate"/> + </actionGroup> + + <assertEquals after ="grabRate" stepKey="assertSelectedCategories"> + <actualResult type="variable">grabRate</actualResult> + <expectedResult type="array">[AUD / USD rate:]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithCADCurrencyTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithCADCurrencyTest.xml new file mode 100644 index 0000000000000..aa35f36268270 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithCADCurrencyTest.xml @@ -0,0 +1,68 @@ +<?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="StorefrontPaypalSmartButtonWithCADCurrencyTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button with Canadian currency"/> + <description value="Users are able to place order using Paypal Smart Button using Canadian currency"/> + <severity value="MAJOR"/> + <testCaseId value="MC-33274"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!--Enable Advanced Setting--> + <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantCanada.path}} {{MerchantCanada.value}}" stepKey="setMerchantCountryUK"/> + <!--Set Currency options for Default Config--> + <magentoCLI command="config:set {{SetCurrencyCADBaseConfig.path}} {{SetCurrencyCADBaseConfig.value}}" stepKey="setCurrencyBaseEUR"/> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForCAD.value}}" stepKey="setAllowedCurrencyEURandUSD"/> + <magentoCLI command="config:set {{SetDefaultCurrencyCADConfig.path}} {{SetDefaultCurrencyCADConfig.value}}" stepKey="setCurrencyDefaultEUR"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseEURWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForCAD.value}}" stepKey="setAllowedCurrencyWebsitesForEURandUSD"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyCADConfig.scope}} --scope-code={{SetDefaultCurrencyCADConfig.scope_code}} {{SetDefaultCurrencyCADConfig.path}} {{SetDefaultCurrencyCADConfig.value}}" stepKey="setCurrencyDefaultEURWebsites"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <!--Set default merchant country--> + <magentoCLI command="config:set {{MerchantUnitedStates.path}} {{MerchantUnitedStates.value}}" stepKey="setMerchantCountryDefault"/> + </after> + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" after="guestCheckoutFillingShippingSection" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" after="waitForPlaceOrderButton" stepKey="guestSelectCheckMoneyOrderPayment"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" after="guestSelectCheckMoneyOrderPayment" stepKey="guestPlaceOrder"> + <argument name="elementNumber" value="0"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="guestPlaceOrder" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + + <!-- I see order successful Page instead of Order Review Page --> + <waitForElement after="submitPayment" selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="waitForOrderNumber"/> + <actionGroup ref="AdminAssertCurrencyInOrderActionGroup" stepKey="seeEURandUSDRate"> + <argument name="rate" value="CAD / USD rate"/> + </actionGroup> + + <assertEquals after ="grabRate" stepKey="assertSelectedCategories"> + <actualResult type="variable">grabRate</actualResult> + <expectedResult type="array">[CAD / USD rate:]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithEuroCurrencyTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithEuroCurrencyTest.xml new file mode 100644 index 0000000000000..1adfe3b5d3a70 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithEuroCurrencyTest.xml @@ -0,0 +1,54 @@ +<?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="StorefrontPaypalSmartButtonWithEuroCurrencyTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button with Euro currency"/> + <description value="Users are able to place order using Paypal Smart Button using Euro currency"/> + <severity value="MAJOR"/> + <testCaseId value="MC-33274"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantUnitedKingdom.path}} {{MerchantUnitedKingdom.value}}" stepKey="setMerchantCountryUK"/> + <!--Enable Advanced Setting--> + <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> + <!--Set default merchant country--> + <magentoCLI command="config:set {{MerchantUnitedStates.path}} {{MerchantUnitedStates.value}}" stepKey="setMerchantCountryDefault"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + </after> + + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" after="guestCheckoutFillingShippingSection" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" stepKey="guestSelectCheckMoneyOrderPayment"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" after="guestSelectCheckMoneyOrderPayment" stepKey="guestPlaceOrder"> + <argument name="elementNumber" value="0"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="guestPlaceOrder" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + + <waitForElement after="submitPayment" selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="waitForOrderNumber"/> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml new file mode 100644 index 0000000000000..0efb3f33739fa --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml @@ -0,0 +1,53 @@ +<?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="StorefrontPaypalSmartButtonWithFranceMerchantCountryTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button with Euro currency and merchant country is France"/> + <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"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantFrance.path}} {{MerchantFrance.value}}" stepKey="setMerchantCountryUK"/> + <!--Enable Advanced Setting--> + <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> + <!--Set default merchant country--> + <magentoCLI command="config:set {{MerchantUnitedStates.path}} {{MerchantUnitedStates.value}}" stepKey="setMerchantCountryDefault"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + </after> + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" after="guestCheckoutFillingShippingSection" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" stepKey="guestSelectCheckMoneyOrderPayment"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" after="guestSelectCheckMoneyOrderPayment" stepKey="guestPlaceOrder"> + <argument name="elementNumber" value="0"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="guestPlaceOrder" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + + <waitForElement after="submitPayment" selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="waitForOrderNumber"/> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithHKDCurrencyTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithHKDCurrencyTest.xml new file mode 100644 index 0000000000000..75a4a5f084b49 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithHKDCurrencyTest.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="StorefrontPaypalSmartButtonWithHKDCurrencyTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button with HKD currency and merchant country HongKong"/> + <description value="Users are able to place order using Paypal Smart Button using HKD currency and merchant country HongKong"/> + <severity value="MAJOR"/> + <testCaseId value="MC-33274"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!--Enable Advanced Setting--> + <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantHongKong.path}} {{MerchantHongKong.value}}" stepKey="setMerchantCountryUK"/> + <!--Set Currency options for Default Config--> + <magentoCLI command="config:set {{SetCurrencyHKDBaseConfig.path}} {{SetCurrencyHKDBaseConfig.value}}" stepKey="setCurrencyBaseEUR"/> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForHKD.value}}" stepKey="setAllowedCurrencyEURandUSD"/> + <magentoCLI command="config:set {{SetDefaultCurrencyHKDConfig.path}} {{SetDefaultCurrencyHKDConfig.value}}" stepKey="setCurrencyDefaultEUR"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseEURWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForHKD.value}}" stepKey="setAllowedCurrencyWebsitesForEURandUSD"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyHKDConfig.scope}} --scope-code={{SetDefaultCurrencyHKDConfig.scope_code}} {{SetDefaultCurrencyHKDConfig.path}} {{SetDefaultCurrencyHKDConfig.value}}" stepKey="setCurrencyDefaultEURWebsites"/> + <!--Enable Free Shipping--> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + </before> + <after> + <createData entity="SamplePaypalConfig" stepKey="setDefaultPaypalConfig"/> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> + <!--Set default merchant country--> + <magentoCLI command="config:set {{MerchantUnitedStates.path}} {{MerchantUnitedStates.value}}" stepKey="setMerchantCountryDefault"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + </after> + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" after="guestCheckoutFillingShippingSection" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" after="waitForPlaceOrderButton" stepKey="guestSelectCheckMoneyOrderPayment"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" after="guestSelectCheckMoneyOrderPayment" stepKey="guestPlaceOrder"> + <argument name="elementNumber" value="0"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="guestPlaceOrder" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + + <waitForElement after="submitPayment" selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="waitForOrderNumber"/> + <actionGroup ref="AdminAssertCurrencyInOrderActionGroup" stepKey="seeEURandUSDRate"> + <argument name="rate" value="HKD / USD rate"/> + </actionGroup> + <assertEquals after ="grabRate" stepKey="assertSelectedCategories"> + <actualResult type="variable">grabRate</actualResult> + <expectedResult type="array">[HKD / USD rate:]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithNZDCurrencyTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithNZDCurrencyTest.xml new file mode 100644 index 0000000000000..4cddf674c9bfe --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithNZDCurrencyTest.xml @@ -0,0 +1,69 @@ +<?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="StorefrontPaypalSmartButtonWithNZDCurrencyTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button with NZD currency and New Zealand merchant country"/> + <description value="Users are able to place order using Paypal Smart Button using NZD currency and New Zealand merchant country"/> + <severity value="MAJOR"/> + <testCaseId value="MC-33274"/> + <group value="paypalExpress"/> + </annotations> + <before> + <!--Enable Advanced Setting--> + <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantNewZealand.path}} {{MerchantNewZealand.value}}" stepKey="setMerchantCountryUK"/> + <!--Set Currency options for Default Config--> + <magentoCLI command="config:set {{SetCurrencyNZDBaseConfig.path}} {{SetCurrencyNZDBaseConfig.value}}" stepKey="setCurrencyBaseEUR"/> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForNZD.value}}" stepKey="setAllowedCurrencyEURandUSD"/> + <magentoCLI command="config:set {{SetDefaultCurrencyNZDConfig.path}} {{SetDefaultCurrencyNZDConfig.value}}" stepKey="setCurrencyDefaultEUR"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseEURWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForNZD.value}}" stepKey="setAllowedCurrencyWebsitesForEURandUSD"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyNZDConfig.scope}} --scope-code={{SetDefaultCurrencyNZDConfig.scope_code}} {{SetDefaultCurrencyNZDConfig.path}} {{SetDefaultCurrencyNZDConfig.value}}" stepKey="setCurrencyDefaultEURWebsites"/> + <!--Enable Free Shipping--> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> + <!--Set default merchant country--> + <magentoCLI command="config:set {{MerchantUnitedStates.path}} {{MerchantUnitedStates.value}}" stepKey="setMerchantCountryDefault"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + </after> + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" after="guestCheckoutFillingShippingSection" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" after="waitForPlaceOrderButton" stepKey="guestSelectCheckMoneyOrderPayment"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" after="guestSelectCheckMoneyOrderPayment" stepKey="guestPlaceOrder"> + <argument name="elementNumber" value="0"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="guestPlaceOrder" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + + <waitForElement after="submitPayment" selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="waitForOrderNumber"/> + <actionGroup ref="AdminAssertCurrencyInOrderActionGroup" stepKey="seeEURandUSDRate"> + <argument name="rate" value="NZD / USD rate"/> + </actionGroup> + <assertEquals after ="grabRate" stepKey="assertSelectedCategories"> + <actualResult type="variable">grabRate</actualResult> + <expectedResult type="array">[NZD / USD rate:]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithYENCurrencyTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithYENCurrencyTest.xml new file mode 100644 index 0000000000000..b5b42b2a6bcbd --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithYENCurrencyTest.xml @@ -0,0 +1,68 @@ +<?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="StorefrontPaypalSmartButtonWithYENCurrencyTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="Paypal"/> + <stories value="PayPal Express Checkout"/> + <title value="Mainflow of Paypal Smart Button with YEN currency and Japan merchant country"/> + <description value="Users are able to place order using Paypal Smart Button using YEN currency and Japan merchant country"/> + <severity value="MAJOR"/> + <testCaseId value="MC-33274"/> + <group value="paypalExpress"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalEnableSkipOrderReviewStepConfigData.value}}" stepKey="enableSkipOrderReview"/> + <!--Set merchant country--> + <magentoCLI command="config:set {{MerchantJapan.path}} {{MerchantJapan.value}}" stepKey="setMerchantCountryUK"/> + <!--Set Currency options for Default Config--> + <magentoCLI command="config:set {{SetCurrencyYENBaseConfig.path}} {{SetCurrencyYENBaseConfig.value}}" stepKey="setCurrencyBaseEUR"/> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForYEN.value}}" stepKey="setAllowedCurrencyEURandUSD"/> + <magentoCLI command="config:set {{SetDefaultCurrencyYENConfig.path}} {{SetDefaultCurrencyYENConfig.value}}" stepKey="setCurrencyDefaultEUR"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseEURWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForYEN.value}}" stepKey="setAllowedCurrencyWebsitesForEURandUSD"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyYENConfig.scope}} --scope-code={{SetDefaultCurrencyYENConfig.scope_code}} {{SetDefaultCurrencyYENConfig.path}} {{SetDefaultCurrencyYENConfig.value}}" stepKey="setCurrencyDefaultEURWebsites"/> + <!--Enable Free Shipping--> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.path}} {{StorefrontPaypalDisableSkipOrderReviewStepConfigData.value}}" stepKey="disableSkipOrderReview"/> + <!--Set default merchant country--> + <magentoCLI command="config:set {{MerchantUnitedStates.path}} {{MerchantUnitedStates.value}}" stepKey="setMerchantCountryDefault"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + </after> + <!-- click on PayPal payment radio button --> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" after="guestCheckoutFillingShippingSection" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" after="waitForPlaceOrderButton" stepKey="guestSelectCheckMoneyOrderPayment"/> + + <!--Click Paypal button--> + <actionGroup ref="SwitchToPayPalGroupBtnActionGroup" after="guestSelectCheckMoneyOrderPayment" stepKey="guestPlaceOrder"> + <argument name="elementNumber" value="0"/> + </actionGroup> + + <!--Login to Paypal in-context--> + <actionGroup ref="StorefrontLoginToPayPalPaymentAccountOneStepActionGroup" after="guestPlaceOrder" stepKey="LoginToPayPal"> + <argument name="payerName" value="{{Payer.firstName}}"/> + </actionGroup> + + <!--Submit payment on PayPal site--> + <actionGroup ref="StorefrontPaypalSwitchBackToMagentoFromCheckoutPageActionGroup" after="LoginToPayPal" stepKey="submitPayment"/> + + <waitForElement after="submitPayment" selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="waitForOrderNumber"/> + <actionGroup ref="AdminAssertCurrencyInOrderActionGroup" stepKey="seeEURandUSDRate"> + <argument name="rate" value="JPY / USD rate"/> + </actionGroup> + <assertEquals after ="grabRate" stepKey="assertSelectedCategories"> + <actualResult type="variable">grabRate</actualResult> + <expectedResult type="array">[JPY / USD rate:]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Express/LocaleResolverTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Express/LocaleResolverTest.php index b9e2c959b4f7d..ccd991aa3ff0c 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Express/LocaleResolverTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Express/LocaleResolverTest.php @@ -13,12 +13,12 @@ use Magento\Paypal\Model\Express\LocaleResolver as ExpressLocaleResolver; /** - * Class LocaleResolverTest + * Test for PayPal express checkout resolver */ class LocaleResolverTest extends \PHPUnit\Framework\TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject|ResolverInterface + * @var \PHPUnit\Framework\MockObject\MockObject|ResolverInterface */ private $resolver; @@ -27,18 +27,20 @@ class LocaleResolverTest extends \PHPUnit\Framework\TestCase */ private $model; + /** + * @var Config + */ + private $config; + protected function setUp() { $this->resolver = $this->createMock(ResolverInterface::class); /** @var Config $config */ - $config = $this->createMock(Config::class); - $config->method('getValue') - ->with('supported_locales') - ->willReturn('zh_CN,zh_HK,zh_TW,fr_FR'); + $this->config = $this->createMock(Config::class); /** @var ConfigFactory $configFactory */ $configFactory = $this->createPartialMock(ConfigFactory::class, ['create']); - $configFactory->method('create')->willReturn($config); + $configFactory->method('create')->willReturn($this->config); $this->model = new ExpressLocaleResolver($this->resolver, $configFactory); } @@ -54,7 +56,14 @@ public function testGetLocale(string $locale, string $expectedLocale) { $this->resolver->method('getLocale') ->willReturn($locale); - + $this->config->method('getValue')->will( + $this->returnValueMap( + [ + ['in_context', null, false], + ['supported_locales', null, 'zh_CN,zh_HK,zh_TW,fr_FR'], + ] + ) + ); $this->assertEquals($expectedLocale, $this->model->getLocale()); } @@ -71,4 +80,23 @@ public function getLocaleDataProvider(): array ['locale' => 'unknown', 'expectedLocale' => 'en_US'], ]; } + + /** + * Tests retrieving locales for PayPal Express Smart Buttons. + * + */ + public function testGetLocaleForSmartButtons() + { + $this->resolver->method('getLocale') + ->willReturn('zh_Hans_CN'); + $this->config->method('getValue')->will( + $this->returnValueMap( + [ + ['in_context', null, true], + ['smart_buttons_supported_locales', null, 'zh_CN,zh_HK,zh_TW,fr_FR'], + ] + ) + ); + $this->assertEquals('zh_CN', $this->model->getLocale()); + } } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php index 4002c78bb0ecc..063f98bf3d3ea 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php @@ -61,7 +61,8 @@ protected function setUp() $configFactoryMock, $scopeConfigMock, $this->getDefaultStyles(), - $this->getAllowedFundings() + $this->getDisallowedFundingMap(), + $this->getUnsupportedPaymentMethods() ); } @@ -73,7 +74,6 @@ protected function setUp() * @param bool $isCustomize * @param string $disallowedFundings * @param string $layout - * @param string $size * @param string $shape * @param string $label * @param string $color @@ -90,7 +90,6 @@ public function testGetConfig( bool $isCustomize, ?string $disallowedFundings, string $layout, - string $size, string $shape, string $label, string $color, @@ -103,6 +102,7 @@ public function testGetConfig( $this->configMock->method('getValue')->will( $this->returnValueMap( [ + ['sandbox_client_id', null, 'sb'], ['merchant_id', null, 'merchant'], [ 'solution_type', @@ -110,10 +110,10 @@ public function testGetConfig( $isPaypalGuestCheckoutEnabled ? Config::EC_SOLUTION_TYPE_SOLE : Config::EC_SOLUTION_TYPE_MARK ], ['sandbox_flag', null, true], + ['paymentAction', null, 'Authorization'], ['disable_funding_options', null, $disallowedFundings], ["{$page}_page_button_customize", null, $isCustomize], ["{$page}_page_button_layout", null, $layout], - ["{$page}_page_button_size", null, $size], ["{$page}_page_button_color", null, $color], ["{$page}_page_button_shape", null, $shape], ["{$page}_page_button_label", null, $label], @@ -150,12 +150,22 @@ private function getDefaultStyles() } /** - * Get allowed fundings + * Get disallowed funding map * * @return array */ - private function getAllowedFundings() + private function getDisallowedFundingMap() { - return include __DIR__ . '/_files/allowed_fundings.php'; + return include __DIR__ . '/_files/disallowed_funding_map.php'; + } + + /** + * Get unsupported payment methods + * + * @return array + */ + private function getUnsupportedPaymentMethods() + { + return include __DIR__ . '/_files/unsupported_payment_methods.php'; } } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/allowed_fundings.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/allowed_fundings.php deleted file mode 100644 index 6b6f8ccb87e14..0000000000000 --- a/app/code/Magento/Paypal/Test/Unit/Model/_files/allowed_fundings.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -return [ - 'checkout' => [ - 'CREDIT', - 'ELV' - ], - 'cart' => [ - 'CREDIT', - 'ELV' - ], - 'mini_cart' => [ - 'CREDIT', - 'ELV' - ], - 'product' => [ - 'CREDIT' - ] -]; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/disallowed_funding_map.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/disallowed_funding_map.php new file mode 100644 index 0000000000000..ea543454975f4 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/disallowed_funding_map.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + "CREDIT" => 'credit', + "CARD" => 'card', + "ELV" => 'sepa' +]; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php index f17b9f9fb4979..6089b8b20b1ac 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php @@ -5,6 +5,16 @@ */ declare(strict_types=1); +/** + * Generates expected PayPal SDK url + * @param array $params + * @return String + */ +function generateExpectedPaypalSdkUrl(array $params) : String +{ + return 'https://www.paypal.com/sdk/js?' . http_build_query($params); +} + return [ 'cart' => [ 'cart', @@ -12,29 +22,36 @@ true, 'CREDIT', 'horizontal', - 'small', - 'pillow', + 'pill', 'installment', 'blue', 'my_label', 'mx', true, [ - 'merchantId' => 'merchant', - 'environment' => 'sandbox', - 'locale' => 'es_MX', - 'allowedFunding' => ['ELV'], - 'disallowedFunding' => ['CREDIT'], 'styles' => [ 'layout' => 'horizontal', - 'size' => 'small', + 'size' => null, 'color' => 'blue', - 'shape' => 'pillow', + 'shape' => 'pill', 'label' => 'installment', - 'installmentperiod' => 0 + 'period' => 0 ], 'isVisibleOnProductPage' => false, - 'isGuestCheckoutAllowed' => true + 'isGuestCheckoutAllowed' => true, + 'sdkUrl' => generateExpectedPaypalSdkUrl( + [ + 'client-id' => 'sb', + 'commit' => 'false', + 'merchant-id' => 'merchant', + 'locale' => 'es_MX', + 'intent' => 'authorize', + 'disable-funding' => implode( + ',', + ['credit', 'venmo', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort'] + ) + ] + ) ] ], 'checkout' => [ @@ -43,50 +60,51 @@ true, null, 'horizontal', - 'small', - 'pillow', + 'pill', 'installment', 'blue', 'my_label', 'br', true, [ - 'merchantId' => 'merchant', - 'environment' => 'sandbox', - 'locale' => 'en_BR', - 'allowedFunding' => ['CREDIT', 'ELV'], - 'disallowedFunding' => [], 'styles' => [ 'layout' => 'horizontal', - 'size' => 'small', + 'size' => null, 'color' => 'blue', - 'shape' => 'pillow', + 'shape' => 'pill', 'label' => 'installment', - 'installmentperiod' => 0 + 'period' => 0 ], 'isVisibleOnProductPage' => false, - 'isGuestCheckoutAllowed' => true + 'isGuestCheckoutAllowed' => true, + 'sdkUrl' => generateExpectedPaypalSdkUrl( + [ + 'client-id' => 'sb', + 'commit' => 'false', + 'merchant-id' => 'merchant', + 'locale' => 'en_BR', + 'intent' => 'authorize', + 'disable-funding' => implode( + ',', + ['venmo', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort'] + ) + ] + ) ] ], 'mini_cart' => [ 'cart', - 'en', + 'en_US', false, null, 'horizontal', - 'small', - 'pillow', + 'pill', 'installment', 'blue', 'my_label', 'br', true, [ - 'merchantId' => 'merchant', - 'environment' => 'sandbox', - 'locale' => 'en', - 'allowedFunding' => ['CREDIT', 'ELV'], - 'disallowedFunding' => [], 'styles' => [ 'layout' => 'vertical', 'size' => 'responsive', @@ -95,28 +113,35 @@ 'label' => 'paypal' ], 'isVisibleOnProductPage' => false, - 'isGuestCheckoutAllowed' => true + 'isGuestCheckoutAllowed' => true, + 'sdkUrl' => generateExpectedPaypalSdkUrl( + [ + 'client-id' => 'sb', + 'commit' => 'false', + 'merchant-id' => 'merchant', + 'locale' => 'en_US', + 'intent' => 'authorize', + 'disable-funding' => implode( + ',', + ['venmo', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort'] + ) + ] + ) ] ], 'product' => [ 'cart', - 'en', + 'en_US', false, 'CREDIT', 'horizontal', - 'small', - 'pillow', + 'pill', 'installment', 'blue', 'my_label', 'br', true, [ - 'merchantId' => 'merchant', - 'environment' => 'sandbox', - 'locale' => 'en', - 'allowedFunding' => ['ELV'], - 'disallowedFunding' => ['CREDIT'], 'styles' => [ 'layout' => 'vertical', 'size' => 'responsive', @@ -125,7 +150,20 @@ 'label' => 'paypal', ], 'isVisibleOnProductPage' => false, - 'isGuestCheckoutAllowed' => true + 'isGuestCheckoutAllowed' => true, + 'sdkUrl' => generateExpectedPaypalSdkUrl( + [ + 'client-id' => 'sb', + 'commit' => 'false', + 'merchant-id' => 'merchant', + 'locale' => 'en_US', + 'intent' => 'authorize', + 'disable-funding' => implode( + ',', + ['credit','venmo', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort'] + ) + ] + ) ] ], 'checkout_with_paypal_guest_checkout_disabled' => [ @@ -134,29 +172,36 @@ true, null, 'horizontal', - 'small', - 'pillow', + 'pill', 'installment', 'blue', 'my_label', 'br', false, [ - 'merchantId' => 'merchant', - 'environment' => 'sandbox', - 'locale' => 'en_BR', - 'allowedFunding' => ['CREDIT', 'ELV'], - 'disallowedFunding' => ['CARD'], 'styles' => [ 'layout' => 'horizontal', - 'size' => 'small', + 'size' => null, 'color' => 'blue', - 'shape' => 'pillow', + 'shape' => 'pill', 'label' => 'installment', - 'installmentperiod' => 0 + 'period' => 0 ], 'isVisibleOnProductPage' => false, - 'isGuestCheckoutAllowed' => true + 'isGuestCheckoutAllowed' => true, + 'sdkUrl' => generateExpectedPaypalSdkUrl( + [ + 'client-id' => 'sb', + 'commit' => 'false', + 'merchant-id' => 'merchant', + 'locale' => 'en_BR', + 'intent' => 'authorize', + 'disable-funding' => implode( + ',', + ['card','venmo', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort'] + ) + ] + ) ] ], ]; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/unsupported_payment_methods.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/unsupported_payment_methods.php new file mode 100644 index 0000000000000..f4b4c72762d93 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/unsupported_payment_methods.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + 'venmo'=> 'venmo', + 'bancontact' => 'bancontact', + 'eps' => 'eps', + 'giropay' => 'giropay', + 'ideal' => 'ideal', + 'mybank' => 'mybank', + 'p24' => 'p24', + 'sofort' => 'sofort' +]; diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 04d5fae435816..fe3ed6ce5e00e 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -155,10 +155,10 @@ <tooltip>You can look up your merchant ID by logging into https://www.paypal.com/. Click the profile icon on the top right side of the page and then select Profile and settings in the Business Profile menu. (If you do not see the profile icon at the top of the page, click Profile, which appears in the top menu when the My Account tab is selected.) Click My business info on the left, and the Merchant account ID is displayed in the list of profile items on the right.</tooltip> <config_path>payment/paypal_express/merchant_id</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\MerchantId</frontend_model> + <validate>required-entry</validate> <depends> <field id="enable_in_context_checkout">1</field> </depends> - <validate>required-entry</validate> </field> <field id="enable_express_checkout_bml" translate="label comment" type="select" sortOrder="23" showInDefault="0" showInWebsite="0"> <label>Enable PayPal Credit</label> @@ -703,16 +703,6 @@ </depends> <attribute type="shared">1</attribute> </field> - <field id="checkout_page_button_size" translate="label tooltip" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> - <label>Size</label> - <config_path>paypal/style/checkout_page_button_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getSize</source_model> - <tooltip>Select Responsive to ensure the PayPal button renders correctly on mobile devices.</tooltip> - <depends> - <field id="checkout_page_button_customize">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> <field id="checkout_page_button_shape" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> <label>Shape</label> <config_path>paypal/style/checkout_page_button_shape</config_path> @@ -765,12 +755,6 @@ <field id="product_page_button_label" negative="1">credit</field> </depends> </field> - <field id="product_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> - <config_path>paypal/style/product_page_button_size</config_path> - <depends> - <field id="product_page_button_customize">1</field> - </depends> - </field> <field id="product_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> <config_path>paypal/style/product_page_button_shape</config_path> <depends> @@ -817,12 +801,6 @@ <field id="cart_page_button_label" negative="1">credit</field> </depends> </field> - <field id="cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> - <config_path>paypal/style/cart_page_button_size</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - </depends> - </field> <field id="cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> <config_path>paypal/style/cart_page_button_shape</config_path> <depends> @@ -869,12 +847,6 @@ <field id="mini_cart_page_button_label" negative="1">credit</field> </depends> </field> - <field id="mini_cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> - <config_path>paypal/style/mini_cart_page_button_size</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - </depends> - </field> <field id="mini_cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> <config_path>paypal/style/mini_cart_page_button_shape</config_path> <depends> diff --git a/app/code/Magento/Paypal/etc/config.xml b/app/code/Magento/Paypal/etc/config.xml index 24bb54a86103d..f93572537ffd7 100644 --- a/app/code/Magento/Paypal/etc/config.xml +++ b/app/code/Magento/Paypal/etc/config.xml @@ -69,6 +69,10 @@ <verify_peer>1</verify_peer> <skip_order_review_step>1</skip_order_review_step> <supported_locales>ar_EG,cs_CZ,da_DK,de_DE,el_GR,en_AU,en_GB,en_IN,en_US,es_ES,es_XC,fi_FI,fr_CA,fr_FR,fr_XC,he_IL,hu_HU,id_ID,it_IT,ja_JP,ko_KR,nl_NL,no_NO,pl_PL,pt_BR,pt_PT,ru_RU,sk_SK,sv_SE,th_TH,zh_CN,zh_HK,zh_TW,zh_XC</supported_locales> + <!--For more information see https://developer.paypal.com/docs/checkout/reference/customize-sdk/#locale --> + <smart_buttons_supported_locales>en_AD,fr_AD,es_AD,zh_AD,en_AE,fr_AE,es_AE,zh_AE,ar_AE,en_AG,fr_AG,es_AG,zh_AG,en_AI,fr_AI,es_AI,zh_AI,en_AL,en_AM,fr_AM,es_AM,zh_AM,en_AN,fr_AN,es_AN,zh_AN,en_AO,fr_AO,es_AO,zh_AO,es_AR,en_AR, de_AT,en_AT,en_AU,en_AW,fr_AW,es_AW,zh_AW,en_AZ,fr_AZ,es_AZ,zh_AZ,en_BA,en_BB,fr_BB,es_BB,zh_BB,en_BE,nl_BE,fr_BE,fr_BF,en_BF,es_BF,zh_BF,en_BG,ar_BH,en_BH,fr_BH,es_BH,zh_BH,fr_BI,en_BI,es_BI,zh_BI,fr_BJ,en_BJ,es_BJ,zh_BJ,en_BM,fr_BM,es_BM,zh_BM,en_BN,es_BO,en_BO,fr_BO,zh_BO,pt_BR,en_BR,en_BS,fr_BS,es_BS,zh_BS,en_BT,en_BW,fr_BW,es_BW,zh_BW,en_BY,en_BZ,es_BZ,fr_BZ,zh_BZ,en_CA,fr_CA,fr_CD,en_CD,es_CD,zh_CD,en_CG,fr_CG,es_CG,zh_CG,de_CH,fr_CH,en_CH,fr_CI,en_CI,en_CK,fr_CK,es_CK,zh_CK,es_CL,en_CL,fr_CL,zh_CL,fr_CM,en_CM,zh_CN,es_CO,en_CO,fr_CO,zh_CO,es_CR,en_CR,fr_CR,zh_CR,en_CV,fr_CV,es_CV,zh_CV,en_CY,cs_CZ,en_CZ,fr_CZ,es_CZ,zh_CZ,de_DE,en_DE,fr_DJ,en_DJ,es_DJ,zh_DJ,da_DK,en_DK,en_DM,fr_DM,es_DM,zh_DM,es_DO,en_DO,fr_DO,zh_DO,ar_DZ,en_DZ,fr_DZ,es_DZ,zh_DZ,es_EC,en_EC,fr_EC,zh_EC,en_EE,ru_EE,fr_EE,es_EE,zh_EE,ar_EG,en_EG,fr_EG,es_EG,zh_EG,en_ER,fr_ER,es_ER,zh_ER,es_ES,en_ES,en_ET,fr_ET,es_ET,zh_ET,fi_FI,en_FI,fr_FI,es_FI,zh_FI,en_FJ,fr_FJ,es_FJ,zh_FJ,en_FK,fr_FK,es_FK,zh_FK,en_FM,da_FO,en_FO,fr_FO,es_FO,zh_FO,fr_FR,en_FR,fr_GA,en_GA,es_GA,zh_GA,en_GB,en_GD,fr_GD,es_GD,zh_GD,en_GE,fr_GE,es_GE,zh_GE,en_GF,fr_GF,es_GF,zh_GF,en_GI,fr_GI,es_GI,zh_GI,da_GL,en_GL,fr_GL,es_GL,zh_GL,en_GM,fr_GM,es_GM,zh_GM,fr_GN,en_GN,es_GN,zh_GN,en_GP,fr_GP,es_GP,zh_GP,el_GR,en_GR,fr_GR,es_GR,zh_GR,es_GT,en_GT,fr_GT,zh_GT,en_GW,fr_GW,es_GW,zh_GW,en_GY,fr_GY,es_GY,zh_GY,en_HK,zh_HK,es_HN,en_HN,fr_HN,zh_HN,en_HR,hu_HU,en_HU,fr_HU,es_HU,zh_HU,id_ID,en_ID,en_IE,fr_IE,es_IE,zh_IE,he_IL,en_IL,en_IN,en_IS,it_IT,en_IT,en_JM,es_JM,fr_JM,zh_JM,ar_JO,en_JO,fr_JO,es_JO,zh_JO,ja_JP,en_JP,en_KE,fr_KE,es_KE,zh_KE,en_KG,fr_KG,es_KG,zh_KG,en_KH,en_KI,fr_KI,es_KI,zh_KI,fr_KM,en_KM,es_KM,zh_KM,en_KN,fr_KN,es_KN,zh_KN,ko_KR,en_KR,ar_KW,en_KW,fr_KW,es_KW,zh_KW,en_KY,fr_KY,es_KY,zh_KY,en_KZ,fr_KZ,es_KZ,zh_KZ,en_LA,en_LC,fr_LC,es_LC,zh_LC,en_LI,fr_LI,es_LI,zh_LI,en_LK,en_LS,fr_LS,es_LS,zh_LS,en_LT,ru_LT,fr_LT,es_LT,zh_LT,en_LU,de_LU,fr_LU,es_LU,zh_LU,en_LV,ru_LV,fr_LV,es_LV,zh_LV,ar_MA,en_MA,fr_MA,es_MA,zh_MA,fr_MC,en_MC,en_MD,en_ME,en_MG,fr_MG,es_MG,zh_MG,en_MH,fr_MH,es_MH,zh_MH,en_MK,fr_ML,en_ML,es_ML,zh_ML,en_MN,en_MQ,fr_MQ,es_MQ,zh_MQ,en_MR,fr_MR,es_MR,zh_MR,en_MS,fr_MS,es_MS,zh_MS,en_MT,en_MU,fr_MU,es_MU,zh_MU,en_MV,en_MW,fr_MW,es_MW,zh_MW,es_MX,en_MX,en_MY,en_MZ,fr_MZ,es_MZ,zh_MZ,en_NA,fr_NA,es_NA,zh_NA,en_NC,fr_NC,es_NC,zh_NC,fr_NE,en_NE,es_NE,zh_NE,en_NF,fr_NF,es_NF,zh_NF,en_NG,es_NI,en_NI,fr_NI,zh_NI,nl_NL,en_NL,no_NO,en_NO,en_NP,en_NR,fr_NR,es_NR,zh_NR,en_NU,fr_NU,es_NU,zh_NU,en_NZ,fr_NZ,es_NZ,zh_NZ,ar_OM,en_OM,fr_OM,es_OM,zh_OM,es_PA,en_PA,fr_PA,zh_PA,es_PE,en_PE,fr_PE,zh_PE,en_PF,fr_PF,es_PF,zh_PF,en_PG,fr_PG,es_PG,zh_PG,en_PH,pl_PL,en_PL,en_PM,fr_PM,es_PM,zh_PM,en_PN,fr_PN,es_PN,zh_PN,pt_PT,en_PT,en_PW,fr_PW,es_PW,zh_PW,es_PY,en_PY,en_QA,fr_QA,es_QA,zh_QA,ar_QA,en_RE,fr_RE,es_RE,zh_RE,en_RO,fr_RO,es_RO,zh_RO,en_RS,fr_RS,es_RS,zh_RS,ru_RU,en_RU,fr_RW,en_RW,es_RW,zh_RW,ar_SA,en_SA,fr_SA,es_SA,zh_SA,en_SB,fr_SB,es_SB,zh_SB,fr_SC,en_SC,es_SC,zh_SC,sv_SE,en_SE,en_SG,en_SH,fr_SH,es_SH,zh_SH,en_SI,fr_SI,es_SI,zh_SI,en_SJ,fr_SJ,es_SJ,zh_SJ,sk_SK,en_SK,fr_SK,es_SK,zh_SK,en_SL,fr_SL,es_SL,zh_SL,en_SM,fr_SM,es_SM,zh_SM,fr_SN,en_SN,es_SN,zh_SN,en_SO,fr_SO,es_SO,zh_SO,en_SR,fr_SR,es_SR,zh_SR,en_ST,fr_ST,es_ST,zh_ST,es_SV,en_SV,fr_SV,zh_SV,en_SZ,fr_SZ,es_SZ,zh_SZ,en_TC,fr_TC,es_TC,zh_TC,fr_TD,en_TD,es_TD,zh_TD,fr_TG,en_TG,es_TG,zh_TG,th_TH,en_TH,en_TJ,fr_TJ,es_TJ,zh_TJ,en_TM,fr_TM,es_TM,zh_TM,ar_TN,en_TN,fr_TN,es_TN,zh_TN,en_TO,tr_TR,en_TR,en_TT,fr_TT,es_TT,zh_TT,en_TV,fr_TV,es_TV,zh_TV,zh_TW,en_TW,en_TZ,fr_TZ,es_TZ,zh_TZ,en_UA,ru_UA,fr_UA,es_UA,zh_UA,en_UG,fr_UG,es_UG,zh_UG,en_US,fr_US,es_US,zh_US,es_UY,en_UY,fr_UY,zh_UY,en_VA,fr_VA,es_VA,zh_VA,en_VC,fr_VC,es_VC,zh_VC,es_VE,en_VE,fr_VE,zh_VE,en_VG,fr_VG,es_VG,zh_VG,en_VN,en_VU,fr_VU,es_VU,zh_VU,en_WF,fr_WF,es_WF,zh_WF,en_WS,ar_YE,en_YE,fr_YE,es_YE,zh_YE,en_YT,fr_YT,es_YT,zh_YT,en_ZA,fr_ZA,es_ZA,zh_ZA,en_ZM,fr_ZM,es_ZM,zh_ZM,en_ZW</smart_buttons_supported_locales> + <client_id>ATDZ9_ECFh-fudesZo4kz3fGTSO1pzuWCS4IjZMq4JKdRK7hQR3Rxyafx39H2fP363WtmlQNYXjUiAae</client_id> + <sandbox_client_id>AUZfbDQ_4m8ibp82qV9pi9wxGkGrdGILVYWbWaTWreW9mmTm6LjQorLZxpP7kjymXc7flRnepHBFSQWp</sandbox_client_id> </paypal_express> <paypal_express_bml> <model>Magento\Paypal\Model\Bml</model> diff --git a/app/code/Magento/Paypal/etc/di.xml b/app/code/Magento/Paypal/etc/di.xml index e148320fdaf17..9ea451a213af2 100644 --- a/app/code/Magento/Paypal/etc/di.xml +++ b/app/code/Magento/Paypal/etc/di.xml @@ -258,4 +258,13 @@ <type name="Magento\Sales\Model\Order\Payment"> <plugin name="paypal_transparent" type="Magento\Paypal\Plugin\TransparentOrderPayment"/> </type> + <type name="Magento\Paypal\Model\System\Config\Source\DisableFundingOptions"> + <arguments> + <argument name="disallowedFundingOptions" xsi:type="array"> + <item name="CREDIT" xsi:type="string">PayPal Credit</item> + <item name="CARD" xsi:type="string">PayPal Guest Checkout Credit Card Icons</item> + <item name="ELV" xsi:type="string">Elektronisches Lastschriftverfahren - German ELV</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Paypal/etc/frontend/di.xml b/app/code/Magento/Paypal/etc/frontend/di.xml index a728f5583a8d6..ac3fccff39f75 100644 --- a/app/code/Magento/Paypal/etc/frontend/di.xml +++ b/app/code/Magento/Paypal/etc/frontend/di.xml @@ -158,22 +158,20 @@ <item name="label" xsi:type="string">buynow</item> </item> </argument> - <argument name="allowedFunding" xsi:type="array"> - <item name="checkout" xsi:type="array"> - <item name="0" xsi:type="string">CREDIT</item> - <item name="1" xsi:type="string">ELV</item> - </item> - <item name="cart" xsi:type="array"> - <item name="0" xsi:type="string">CREDIT</item> - <item name="1" xsi:type="string">ELV</item> - </item> - <item name="mini_cart" xsi:type="array"> - <item name="0" xsi:type="string">CREDIT</item> - <item name="1" xsi:type="string">ELV</item> - </item> - <item name="product" xsi:type="array"> - <item name="0" xsi:type="string">CREDIT</item> - </item> + <argument name="disallowedFundingMap" xsi:type="array"> + <item name="CREDIT" xsi:type="string">credit</item> + <item name="CARD" xsi:type="string">card</item> + <item name="ELV" xsi:type="string">sepa</item> + </argument> + <argument name="unsupportedPaymentMethods" xsi:type="array"> + <item name="venmo" xsi:type="string">venmo</item> + <item name="bancontact" xsi:type="string">bancontact</item> + <item name="eps" xsi:type="string">eps</item> + <item name="giropay" xsi:type="string">giropay</item> + <item name="ideal" xsi:type="string">ideal</item> + <item name="mybank" xsi:type="string">mybank</item> + <item name="p24" xsi:type="string">p24</item> + <item name="sofort" xsi:type="string">sofort</item> </argument> <argument name="localeResolver" xsi:type="object">Magento\Paypal\Model\Express\LocaleResolver</argument> </arguments> diff --git a/app/code/Magento/Paypal/i18n/en_US.csv b/app/code/Magento/Paypal/i18n/en_US.csv index 912b99e2e6427..852bf39c57966 100644 --- a/app/code/Magento/Paypal/i18n/en_US.csv +++ b/app/code/Magento/Paypal/i18n/en_US.csv @@ -707,7 +707,6 @@ User,User "Label","Label" "The installment feature is available only in these locales: en_MX, es_MX, en_BR, pt_BR.","The installment feature is available only in these locales: en_MX, es_MX, en_BR, pt_BR." "Checkout","Checkout" -"Credit","Credit" "Pay","Pay" "Buy Now","Buy Now" "PayPal","PayPal" @@ -718,8 +717,6 @@ User,User "Vertical","Vertical" "Horizontal","Horizontal" "Size","Size" -"Medium","Medium" -"Large","Large" "Responsive","Responsive" "Shape","Shape" "Pill","Pill" diff --git a/app/code/Magento/Paypal/view/frontend/layout/default.xml b/app/code/Magento/Paypal/view/frontend/layout/default.xml index cb2126cec718f..aa47b43fa153a 100644 --- a/app/code/Magento/Paypal/view/frontend/layout/default.xml +++ b/app/code/Magento/Paypal/view/frontend/layout/default.xml @@ -9,8 +9,7 @@ <body> <referenceContainer name="after.body.start"> <block class="Magento\Paypal\Block\Express\InContext\Component" - name="paypal.express-in-context.component" - template="Magento_Paypal::express/in-context/component.phtml"> + name="paypal.express-in-context.component"> <arguments> <argument name="is_button_context" xsi:type="boolean">true</argument> </arguments> diff --git a/app/code/Magento/Paypal/view/frontend/requirejs-config.js b/app/code/Magento/Paypal/view/frontend/requirejs-config.js index 223ade43d86e5..1f318a717f8aa 100644 --- a/app/code/Magento/Paypal/view/frontend/requirejs-config.js +++ b/app/code/Magento/Paypal/view/frontend/requirejs-config.js @@ -10,13 +10,5 @@ var config = { 'Magento_Paypal/order-review': 'Magento_Paypal/js/order-review', paypalCheckout: 'Magento_Paypal/js/paypal-checkout' } - }, - paths: { - paypalInContextExpressCheckout: 'https://www.paypalobjects.com/api/checkout' - }, - shim: { - paypalInContextExpressCheckout: { - exports: 'paypal' - } } }; diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/component.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/in-context/component.phtml deleted file mode 100644 index c102b21830de8..0000000000000 --- a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/component.phtml +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -use Magento\Paypal\Block\Express\InContext\Minicart\SmartButton; - -/** @var \Magento\Paypal\Block\Express\InContext\Component $block */ - -$configuration = [ - 'id' => SmartButton::PAYPAL_BUTTON_ID, - 'path' => $block->getUrl( - 'paypal/express/gettoken', - [ - '_secure' => $block->getRequest()->isSecure() - ] - ), - 'merchantId' => $block->getMerchantId(), - 'button' => $block->isButtonContext(), - 'clientConfig' => [ - 'locale' => $block->getLocale(), - 'environment' => $block->getEnvironment(), - 'button' => [ - SmartButton::PAYPAL_BUTTON_ID, - ] - ] -]; - -?> -<div style="display: none;" id="<?= /* @noEscape */ SmartButton::PAYPAL_BUTTON_ID ?>"></div> -<script type="text/x-magento-init"> - { - "*": { - "Magento_Paypal/js/in-context/express-checkout": - <?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($configuration) ?> - } - } -</script> diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml index 76d034f462a7a..ce07ba2293bb8 100644 --- a/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml +++ b/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ +// phpcs:disable Magento2.Templates.ThisInTemplate /** * @var \Magento\Paypal\Block\Express\InContext\SmartButton $block */ @@ -12,4 +13,5 @@ $widgetConfig = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonE $widget['Magento_Paypal/js/in-context/product-express-checkout'] ); ?> -<div data-mage-init='{"Magento_Paypal/js/in-context/product-express-checkout":<?= /* @noEscape */ $widgetConfig ?>}'></div> +<div id ="paypal-smart-button" data-mage-init='{"Magento_Paypal/js/in-context/product-express-checkout" +:<?= /* @noEscape */ $widgetConfig ?>}'></div> diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js index 012a1f18f9ae5..719be7b2590a9 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js @@ -45,7 +45,6 @@ define([ /** @inheritdoc */ prepareClientConfig: function () { this._super(); - this.clientConfig.commit = false; return this.clientConfig; } diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-smart-buttons.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-smart-buttons.js index ad7e86f2e99e0..9100236490848 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-smart-buttons.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-smart-buttons.js @@ -2,122 +2,126 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +/* eslint-disable max-nested-callbacks */ define([ 'underscore', - 'paypalInContextExpressCheckout' -], function (_, paypal) { + 'jquery', + 'Magento_Paypal/js/in-context/paypal-sdk', + 'domReady!' +], function (_, $, paypalSdk) { 'use strict'; /** - * Returns array of allowed funding + * Triggers beforePayment action on PayPal buttons * - * @param {Object} config - * @return {Array} + * @param {Object} clientConfig + * @returns {Object} jQuery promise */ - function getFunding(config) { - return _.map(config, function (name) { - return paypal.FUNDING[name]; - }); - } - - return function (clientConfig, element) { - paypal.Button.render({ - env: clientConfig.environment, - client: clientConfig.client, - locale: clientConfig.locale, - funding: { - allowed: getFunding(clientConfig.allowedFunding), - disallowed: getFunding(clientConfig.disallowedFunding) - }, - style: clientConfig.styles, - - // Enable Pay Now checkout flow (optional) - commit: clientConfig.commit, + function performCreateOrder(clientConfig) { + var params = { + 'quote_id': clientConfig.quoteId, + 'customer_id': clientConfig.customerId || '', + 'form_key': clientConfig.formKey, + button: clientConfig.button + }; - /** - * Validate payment method - * - * @param {Object} actions - */ - validate: function (actions) { - clientConfig.rendererComponent.validate(actions); - }, - - /** - * Execute logic on Paypal button click - */ - onClick: function () { - clientConfig.rendererComponent.onClick(); - }, + return $.Deferred(function (deferred) { + clientConfig.rendererComponent.beforePayment(deferred.resolve, deferred.reject).then(function () { + $.post(clientConfig.getTokenUrl, params).done(function (res) { + clientConfig.rendererComponent.afterPayment(res, deferred.resolve, deferred.reject); + }).fail(function (jqXHR, textStatus, err) { + clientConfig.rendererComponent.catchPayment(err, deferred.resolve, deferred.reject); + }); + }); + }).promise(); + } - /** - * Set up a payment - * - * @return {*} - */ - payment: function () { - var params = { - 'quote_id': clientConfig.quoteId, - 'customer_id': clientConfig.customerId || '', - 'form_key': clientConfig.formKey, - button: clientConfig.button - }; + /** + * Triggers beforeOnAuthorize action on PayPal buttons + * @param {Object} clientConfig + * @param {Object} data + * @param {Object} actions + * @returns {Object} jQuery promise + */ + function performOnApprove(clientConfig, data, actions) { + var params = { + paymentToken: data.orderID, + payerId: data.payerID, + quoteId: clientConfig.quoteId || '', + customerId: clientConfig.customerId || '', + 'form_key': clientConfig.formKey + }; - return new paypal.Promise(function (resolve, reject) { - clientConfig.rendererComponent.beforePayment(resolve, reject).then(function () { - paypal.request.post(clientConfig.getTokenUrl, params).then(function (res) { - return clientConfig.rendererComponent.afterPayment(res, resolve, reject); - }).catch(function (err) { - return clientConfig.rendererComponent.catchPayment(err, resolve, reject); - }); + return $.Deferred(function (deferred) { + clientConfig.rendererComponent.beforeOnAuthorize(deferred.resolve, deferred.reject, actions) + .then(function () { + $.post(clientConfig.onAuthorizeUrl, params).done(function (res) { + clientConfig.rendererComponent + .afterOnAuthorize(res, deferred.resolve, deferred.reject, actions); + }).fail(function (jqXHR, textStatus, err) { + clientConfig.rendererComponent.catchOnAuthorize(err, deferred.resolve, deferred.reject); }); }); - }, + }).promise(); + } - /** - * Execute the payment - * - * @param {Object} data - * @param {Object} actions - * @return {*} - */ - onAuthorize: function (data, actions) { - var params = { - paymentToken: data.paymentToken, - payerId: data.payerID, - quoteId: clientConfig.quoteId || '', - customerId: clientConfig.customerId || '', - 'form_key': clientConfig.formKey - }; + return function (clientConfig, element) { + paypalSdk(clientConfig.sdkUrl).done(function (paypal) { + paypal.Buttons({ + style: clientConfig.styles, - return new paypal.Promise(function (resolve, reject) { - clientConfig.rendererComponent.beforeOnAuthorize(resolve, reject, actions).then(function () { - paypal.request.post(clientConfig.onAuthorizeUrl, params).then(function (res) { - clientConfig.rendererComponent.afterOnAuthorize(res, resolve, reject, actions); - }).catch(function (err) { - return clientConfig.rendererComponent.catchOnAuthorize(err, resolve, reject); - }); - }); - }); + /** + * onInit is called when the button first renders + * @param {Object} data + * @param {Object} actions + */ + onInit: function (data, actions) { + clientConfig.rendererComponent.validate(actions); + }, + + /** + * Triggers beforePayment action on PayPal buttons + * @returns {Object} jQuery promise + */ + createOrder: function () { + return performCreateOrder(clientConfig); + }, - }, + /** + * Triggers beforeOnAuthorize action on PayPal buttons + * @param {Object} data + * @param {Object} actions + */ + onApprove: function (data, actions) { + performOnApprove(clientConfig, data, actions); + }, - /** - * Process cancel action - * - * @param {Object} data - * @param {Object} actions - */ - onCancel: function (data, actions) { - clientConfig.rendererComponent.onCancel(data, actions); - }, + /** + * Execute logic on Paypal button click + */ + onClick: function () { + clientConfig.rendererComponent.validate(); + clientConfig.rendererComponent.onClick(); + }, - /** - * Process errors - */ - onError: function (err) { - clientConfig.rendererComponent.onError(err); - } - }, element); + /** + * Process cancel action + * @param {Object} data + * @param {Object} actions + */ + onCancel: function (data, actions) { + clientConfig.rendererComponent.onCancel(data, actions); + }, + + /** + * Process errors + * + * @param {Error} err + */ + onError: function (err) { + clientConfig.rendererComponent.onError(err); + } + }).render(element); + }); }; }); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-wrapper.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-wrapper.js index 905f860fe2651..c5ec5d28ddd06 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-wrapper.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-wrapper.js @@ -7,8 +7,9 @@ define([ 'mage/translate', 'Magento_Customer/js/customer-data', 'Magento_Paypal/js/in-context/express-checkout-smart-buttons', + 'Magento_Ui/js/modal/alert', 'mage/cookies' -], function ($, $t, customerData, checkoutSmartButtons) { +], function ($, $t, customerData, checkoutSmartButtons, alert) { 'use strict'; return { @@ -59,12 +60,11 @@ define([ * @return {*} */ afterPayment: function (res, resolve, reject) { + if (res.success) { return resolve(res.token); } - this.addError(res['error_message']); - return reject(new Error(res['error_message'])); }, @@ -76,7 +76,7 @@ define([ * @param {Function} reject */ catchPayment: function (err, resolve, reject) { - this.addError(this.paymentActionError); + this.addAlert(this.paymentActionError); reject(err); }, @@ -90,6 +90,9 @@ define([ * @return {jQuery.Deferred} */ beforeOnAuthorize: function (resolve, reject, actions) { //eslint-disable-line no-unused-vars + //display loading widget. + $('body').trigger('processStart'); + return $.Deferred().resolve(); }, @@ -104,14 +107,14 @@ define([ * @return {*} */ afterOnAuthorize: function (res, resolve, reject, actions) { + $('body').trigger('processStop'); + if (res.success) { resolve(); - return actions.redirect(window, res.redirectUrl); + return actions.redirect(res.redirectUrl); } - this.addError(res['error_message']); - return reject(new Error(res['error_message'])); }, @@ -123,7 +126,8 @@ define([ * @param {Function} reject */ catchOnAuthorize: function (err, resolve, reject) { - this.addError(this.paymentActionError); + $('body').trigger('processStop'); + this.addAlert(this.paymentActionError); reject(err); }, @@ -134,7 +138,8 @@ define([ * @param {Object} actions */ onCancel: function (data, actions) { - actions.redirect(window, this.clientConfig.onCancelUrl); + $('body').trigger('processStop'); + actions.redirect(this.clientConfig.onCancelUrl); }, /** @@ -163,6 +168,17 @@ define([ }); }, + /** + * Add alert message + * + * @param {String} message + */ + addAlert: function (message) { + alert({ + content: message + }); + }, + /** * @returns {String} */ @@ -176,8 +192,6 @@ define([ * @return {Object} */ prepareClientConfig: function () { - this.clientConfig.client = {}; - this.clientConfig.client[this.clientConfig.environment] = this.clientConfig.merchantId; this.clientConfig.rendererComponent = this; this.clientConfig.formKey = $.mage.cookies.get('form_key'); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout.js deleted file mode 100644 index 80c1dfc44977b..0000000000000 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -define([ - 'underscore', - 'jquery', - 'uiComponent', - 'paypalInContextExpressCheckout', - 'Magento_Customer/js/customer-data', - 'domReady!' -], function (_, $, Component, paypalExpressCheckout, customerData) { - 'use strict'; - - return Component.extend({ - - defaults: { - clientConfig: { - - checkoutInited: false, - - /** - * @param {Object} event - */ - click: function (event) { - $('body').trigger('processStart'); - - event.preventDefault(); - - if (!this.clientConfig.checkoutInited) { - paypalExpressCheckout.checkout.initXO(); - this.clientConfig.checkoutInited = true; - } else { - paypalExpressCheckout.checkout.closeFlow(); - } - - $.getJSON(this.path, { - button: 1 - }).done(function (response) { - var message = response && response.message; - - if (message) { - customerData.set('messages', { - messages: [message] - }); - } - - if (response && response.url) { - paypalExpressCheckout.checkout.startFlow(response.url); - - return; - } - - paypalExpressCheckout.checkout.closeFlow(); - }).fail(function () { - paypalExpressCheckout.checkout.closeFlow(); - }).always(function () { - $('body').trigger('processStop'); - customerData.invalidate(['cart']); - }); - } - } - }, - - /** - * @returns {Object} - */ - initialize: function () { - this._super(); - - return this.initClient(); - }, - - /** - * @returns {Object} - */ - initClient: function () { - _.each(this.clientConfig, function (fn, name) { - if (typeof fn === 'function') { - this.clientConfig[name] = fn.bind(this); - } - }, this); - - paypalExpressCheckout.checkout.setup(this.merchantId, this.clientConfig); - - return this; - } - }); -}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/paypal-sdk.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/paypal-sdk.js new file mode 100644 index 0000000000000..bc2928fb623ba --- /dev/null +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/paypal-sdk.js @@ -0,0 +1,37 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery' +], function ($) { + 'use strict'; + + var dfd = $.Deferred(); + + /** + * Loads the PayPal SDK object + * @param {String} paypalUrl - the url of the PayPal SDK + */ + return function loadPaypalScript(paypalUrl) { + //configuration for loaded PayPal script + require.config({ + paths: { + paypalSdk: paypalUrl + }, + shim: { + paypalSdk: { + exports: 'paypal' + } + } + }); + + if (dfd.state() !== 'resolved') { + require(['paypalSdk'], function (paypalObject) { + dfd.resolve(paypalObject); + }); + } + + return dfd.promise(); + }; +}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js index b2be5fe2b3d2b..9469e168cdc6b 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js @@ -15,7 +15,8 @@ define([ defaults: { productFormSelector: '#product_addtocart_form', declinePayment: false, - formInvalid: false + formInvalid: false, + productAddedToCart: false }, /** @inheritdoc */ @@ -45,9 +46,10 @@ define([ onClick: function () { var $form = $(this.productFormSelector); - if (!this.declinePayment) { + if (!this.declinePayment && !this.productAddedToCart) { $form.submit(); this.formInvalid = !$form.validation('isValid'); + this.productAddedToCart = true; } }, @@ -74,14 +76,51 @@ define([ return promise; }, + /** + * After payment execute + * + * @param {Object} res + * @param {Function} resolve + * @param {Function} reject + * + * @return {*} + */ + afterPayment: function (res, resolve, reject) { + if (res.success) { + return resolve(res.token); + } + + this.addAlert(res['error_message']); + + return reject(new Error(res['error_message'])); + }, + /** @inheritdoc */ prepareClientConfig: function () { this._super(); this.clientConfig.quoteId = ''; this.clientConfig.customerId = ''; - this.clientConfig.commit = false; return this.clientConfig; + }, + + /** @inheritdoc */ + onError: function (err) { + this.productAddedToCart = false; + this._super(err); + }, + + /** @inheritdoc */ + onCancel: function (data, actions) { + this.productAddedToCart = false; + this._super(data, actions); + }, + + /** @inheritdoc */ + afterOnAuthorize: function (res, resolve, reject, actions) { + this.productAddedToCart = false; + + return this._super(res, resolve, reject, actions); } }); }); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js index 5c509238fe5cc..206355f5a9839 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js @@ -75,9 +75,7 @@ define([ this._super(); this.clientConfig.quoteId = window.checkoutConfig.quoteData['entity_id']; this.clientConfig.customerId = window.customerData.id; - this.clientConfig.merchantId = this.merchantId; this.clientConfig.button = 0; - this.clientConfig.commit = true; return this.clientConfig; }, @@ -99,6 +97,47 @@ define([ messageList.addErrorMessage({ message: message }); + }, + + /** + * After payment execute + * + * @param {Object} res + * @param {Function} resolve + * @param {Function} reject + * + * @return {*} + */ + afterPayment: function (res, resolve, reject) { + if (res.success) { + return resolve(res.token); + } + + this.addError(res['error_message']); + + return reject(new Error(res['error_message'])); + }, + + /** + * After onAuthorize execute + * + * @param {Object} res + * @param {Function} resolve + * @param {Function} reject + * @param {Object} actions + * + * @return {*} + */ + afterOnAuthorize: function (res, resolve, reject, actions) { + if (res.success) { + resolve(); + + return actions.redirect(res.redirectUrl); + } + + this.addError(res['error_message']); + + return reject(new Error(res['error_message'])); } }); }); diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest.xml index 159f0c295a5b9..e6fae229d29b1 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyThatInformationAboutViewingComparisonWishlistIsPersistedUnderLongTermCookieTest.xml @@ -128,7 +128,7 @@ </actionGroup> <!--Place the order--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToShoppingCartPage"/> <actionGroup ref="PlaceOrderWithLoggedUserActionGroup" stepKey="placeOrder"> <argument name="shippingMethod" value="Flat Rate"/> </actionGroup> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AddProductVideoActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AddProductVideoActionGroup.xml index bf76b7c11acfd..d39f45c0c1cca 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AddProductVideoActionGroup.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AddProductVideoActionGroup.xml @@ -14,7 +14,7 @@ <description>Expands the 'Images And Videos' section on the Admin Product creation/edit page. Adds the provided Video to the Product. Clicks on Save.</description> </annotations> <arguments> - <argument name="video" defaultValue="mftfTestProductVideo"/> + <argument name="video" defaultValue="YoutubeProductVideo"/> </arguments> <scrollTo selector="{{AdminProductImagesSection.productImagesToggle}}" x="0" y="-100" stepKey="scrollToArea"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoAdminProductPageActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoAdminProductPageActionGroup.xml index bda0b9532f2a1..9745e308d9c2c 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoAdminProductPageActionGroup.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoAdminProductPageActionGroup.xml @@ -13,7 +13,7 @@ <description>Validates that the provided Video is present on the Admin Product creation/edit page.</description> </annotations> <arguments> - <argument name="video" defaultValue="mftfTestProductVideo"/> + <argument name="video" defaultValue="YoutubeProductVideo"/> </arguments> <scrollTo selector="{{AdminProductImagesSection.productImagesToggle}}" x="0" y="-100" stepKey="scrollToArea"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNotInAdminProductPageActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNotInAdminProductPageActionGroup.xml index 3a10c096222b8..01cf58b62a554 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNotInAdminProductPageActionGroup.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNotInAdminProductPageActionGroup.xml @@ -13,7 +13,7 @@ <description>Validates that the provided Video is NOT present on the Admin Product creation/edit page.</description> </annotations> <arguments> - <argument name="video" defaultValue="mftfTestProductVideo"/> + <argument name="video" defaultValue="YoutubeProductVideo"/> </arguments> <scrollTo selector="{{AdminProductImagesSection.productImagesToggle}}" x="0" y="-100" stepKey="scrollToArea"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoStorefrontProductPageActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoStorefrontProductPageActionGroup.xml index c2bb4e016147a..61747c331247f 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoStorefrontProductPageActionGroup.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoStorefrontProductPageActionGroup.xml @@ -14,9 +14,9 @@ <description>Validates that the provided Video is present on the Storefront Product page.</description> </annotations> <arguments> - <argument name="dataTypeAttribute" defaultValue="'youtube'"/> + <argument name="videoType" type="string" defaultValue="youtube"/> </arguments> - <seeElement selector="{{StorefrontProductInfoMainSection.productVideo(dataTypeAttribute)}}" stepKey="seeProductVideoDataType"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productVideo(videoType)}}" stepKey="seeProductVideoDataType"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml index 8fe5899e91ef8..01e343b26778c 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml @@ -13,7 +13,7 @@ <requiredEntity type="youtube_api_key_config">YouTubeApiKey</requiredEntity> </entity> <entity name="YouTubeApiKey" type="youtube_api_key_config"> - <data key="value">AIzaSyDwqDWuw1lra-LnpJL2Mr02DYuFmkuRSns</data> + <data key="value">AIzaSyAzWKu17L5BcpPwtcHMdVDLma2hHoJQb5w</data> </entity> <!-- default configuration used to restore Magento config --> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml index 2b111b882394c..6ecbdf6d31deb 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml @@ -8,7 +8,7 @@ <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="mftfTestProductVideo" type="product_video"> + <entity name="YoutubeProductVideo" type="product_video"> <data key="videoUrl">https://youtu.be/bpOSxM0rNPM</data> <data key="videoTitle">Arctic Monkeys - Do I Wanna Know? (Official Video)</data> <data key="videoShortTitle">Arctic Monkeys</data> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml deleted file mode 100644 index 766d8027bbb89..0000000000000 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml +++ /dev/null @@ -1,36 +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="AdminAddDefaultVideoSimpleProductTest"> - <annotations> - <group value="ProductVideo"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <!-- Set product video Youtube api key configuration --> - <createData entity="ProductVideoYoutubeApiKeyConfig" stepKey="setStoreConfig" after="loginAsAdmin"/> - </before> - <after> - <!-- Set product video configuration to default --> - <createData entity="DefaultProductVideoConfig" stepKey="setStoreDefaultConfig" before="amOnLogoutPage"/> - </after> - - <!-- Add product video --> - <actionGroup ref="AddProductVideoActionGroup" stepKey="addProductVideo" after="fillMainProductForm"/> - - <!-- Assert product video in admin product form --> - <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertProductVideoAdminProductPage" after="saveProductForm"/> - - <!-- Assert product video in storefront product page --> - <actionGroup ref="AssertProductVideoStorefrontProductPageActionGroup" stepKey="assertProductVideoStorefrontProductPage" after="AssertProductInStorefrontProductPage"/> - </test> -</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoBundleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoBundleProductTest.xml new file mode 100644 index 0000000000000..401abea51b2b7 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoBundleProductTest.xml @@ -0,0 +1,68 @@ +<?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="AdminAddRemoveDefaultVideoBundleProductTest" extends="AdminAddRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="ProductVideo"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add/remove default product video for a Bundle Product"/> + <description value="Admin should be able to add/remove default product video for a Bundle Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-110"/> + <testCaseId value="MC-205"/> + <group value="catalog"/> + <group value="bundle"/> + <group value="productVideo"/> + </annotations> + <before> + <remove keyForRemoval="setYoutubeApiKeyConfig"/> + <createData entity="SimpleProduct2" before="createProduct" stepKey="createFirstSimpleProduct"/> + <createData entity="SimpleProduct2" after="createFirstSimpleProduct" stepKey="createSecondSimpleProduct"/> + <createData entity="ApiBundleProduct" stepKey="createProduct"/> + <createData entity="CheckboxOption" after="createProduct" stepKey="createBundleCheckboxOption"> + <requiredEntity createDataKey="createProduct"/> + </createData> + <createData entity="ApiBundleLink" after="createBundleCheckboxOption" stepKey="linkFirstSimpleProductToOption"> + <requiredEntity createDataKey="createProduct"/> + <requiredEntity createDataKey="createBundleCheckboxOption"/> + <requiredEntity createDataKey="createFirstSimpleProduct"/> + </createData> + <createData entity="ApiBundleLink" after="linkFirstSimpleProductToOption" stepKey="linkSecondSimpleProductToOption"> + <requiredEntity createDataKey="createProduct"/> + <requiredEntity createDataKey="createBundleCheckboxOption"/> + <requiredEntity createDataKey="createSecondSimpleProduct"/> + </createData> + </before> + <after> + <remove keyForRemoval="setYoutubeApiKeyDefaultConfig"/> + <deleteData createDataKey="createFirstSimpleProduct" after="deleteProduct" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createSecondSimpleProduct" after="deleteFirstSimpleProduct" stepKey="deleteSecondSimpleProduct"/> + </after> + + <actionGroup ref="AddProductVideoActionGroup" stepKey="addProductVideo"> + <argument name="video" value="VimeoProductVideo"/> + </actionGroup> + <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertProductVideoPresentInProductEditPage"> + <argument name="video" value="VimeoProductVideo"/> + </actionGroup> + + <actionGroup ref="AssertProductVideoStorefrontProductPageActionGroup" stepKey="assertProductVideoPresentInStorefrontProductPage"> + <argument name="videoType" value="vimeo"/> + </actionGroup> + + <actionGroup ref="AssertProductVideoNotInAdminProductPageActionGroup" stepKey="assertProductVideoAbsentInProductEditPage"> + <argument name="video" value="VimeoProductVideo"/> + </actionGroup> + + <actionGroup ref="AssertProductVideoNotInStorefrontProductPageActionGroup" stepKey="assertProductVideoAbsentInStorefrontProductPage"> + <argument name="videoType" value="vimeo"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoDownloadableProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoDownloadableProductTest.xml new file mode 100644 index 0000000000000..0c32fc9fbd240 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoDownloadableProductTest.xml @@ -0,0 +1,38 @@ +<?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="AdminAddRemoveDefaultVideoDownloadableProductTest" extends="AdminAddRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="ProductVideo"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add/remove default product video for a Downloadable Product"/> + <description value="Admin should be able to add/remove default product video for a Downloadable Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-114"/> + <testCaseId value="MC-207"/> + <group value="catalog"/> + <group value="downloadable"/> + <group value="productVideo"/> + <skip> + <issueId value="MC-33903"/> + </skip> + </annotations> + <before> + <magentoCLI command="downloadable:domains:add" arguments="static.magento.com" before="setYoutubeApiKeyConfig" stepKey="addDownloadableDomain"/> + <createData entity="ApiDownloadableProduct" stepKey="createProduct"/> + <createData entity="ApiDownloadableLink" after="createProduct" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createProduct"/> + </createData> + </before> + <after> + <magentoCLI command="downloadable:domains:remove" arguments="static.magento.com" before="setYoutubeApiKeyDefaultConfig" stepKey="removeDownloadableDomain"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoGroupedProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoGroupedProductTest.xml new file mode 100644 index 0000000000000..3f324d8fd3d9f --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoGroupedProductTest.xml @@ -0,0 +1,45 @@ +<?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="AdminAddRemoveDefaultVideoGroupedProductTest" extends="AdminAddRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="ProductVideo"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add/remove default product video for a Grouped Product"/> + <description value="Admin should be able to add/remove default product video for a Grouped Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-108"/> + <testCaseId value="MC-203"/> + <group value="catalog"/> + <group value="groupedProduct"/> + <group value="productVideo"/> + <skip> + <issueId value="MC-33903"/> + </skip> + </annotations> + <before> + <createData entity="SimpleProduct2" after="setYoutubeApiKeyConfig" stepKey="createFirstSimpleProduct"/> + <createData entity="SimpleProduct2" after="createFirstSimpleProduct" stepKey="createSecondSimpleProduct"/> + <createData entity="ApiGroupedProduct" stepKey="createProduct"/> + <createData entity="OneSimpleProductLink" after="createProduct" stepKey="addFirstProduct"> + <requiredEntity createDataKey="createProduct"/> + <requiredEntity createDataKey="createFirstSimpleProduct"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addFirstProduct" after="addFirstProduct" stepKey="addSecondProduct"> + <requiredEntity createDataKey="createProduct"/> + <requiredEntity createDataKey="createSecondSimpleProduct"/> + </updateData> + </before> + <after> + <deleteData createDataKey="createFirstSimpleProduct" after="setYoutubeApiKeyDefaultConfig" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createSecondSimpleProduct" before="deleteProduct" stepKey="deleteSecondSimpleProduct"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoSimpleProductTest.xml new file mode 100644 index 0000000000000..87486178a859b --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoSimpleProductTest.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="AdminAddRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="ProductVideo"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add/remove default product video for a Simple Product"/> + <description value="Admin should be able to add/remove default product video for a Simple Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-111"/> + <testCaseId value="MC-206"/> + <group value="catalog"/> + <group value="productVideo"/> + <skip> + <issueId value="MC-33903"/> + </skip> + </annotations> + <before> + <createData entity="ProductVideoYoutubeApiKeyConfig" stepKey="setYoutubeApiKeyConfig"/> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <!-- Login to Admin page --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <createData entity="DefaultProductVideoConfig" stepKey="setYoutubeApiKeyDefaultConfig"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <!-- Logout from Admin page --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </after> + + <!-- Open product edit page --> + <amOnPage url="{{AdminProductEditPage.url($createProduct.id$)}}" stepKey="goToProductEditPage"/> + <!-- Add product video --> + <actionGroup ref="AddProductVideoActionGroup" stepKey="addProductVideo"/> + <!-- Save product form --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> + <!-- Assert product video present in product edit page --> + <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertProductVideoPresentInProductEditPage"/> + + <!-- Open storefront product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToStorefrontProductPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + <!-- Assert product video present in the storefront product page --> + <actionGroup ref="AssertProductVideoStorefrontProductPageActionGroup" stepKey="assertProductVideoPresentInStorefrontProductPage"/> + + <!-- Open product edit page to remove product video --> + <amOnPage url="{{AdminProductEditPage.url($createProduct.id$)}}" stepKey="goToProductEditPageToRemoveVideo"/> + <!-- Remove product video --> + <actionGroup ref="RemoveProductVideoActionGroup" stepKey="removeProductVideo"/> + <!-- Save product form --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductFormAfterRemoveVideo"/> + <!-- Assert product video absent in product edit page --> + <actionGroup ref="AssertProductVideoNotInAdminProductPageActionGroup" stepKey="assertProductVideoAbsentInProductEditPage"/> + + <!-- Open storefront product page after removing product video --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToStorefrontProductPageAfterRemoveVideo"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + <!-- Assert product video absent in the storefront product page --> + <actionGroup ref="AssertProductVideoNotInStorefrontProductPageActionGroup" stepKey="assertProductVideoAbsentInStorefrontProductPage"/> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoVirtualProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoVirtualProductTest.xml new file mode 100644 index 0000000000000..20737f75b4d5c --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddRemoveDefaultVideoVirtualProductTest.xml @@ -0,0 +1,50 @@ +<?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="AdminAddRemoveDefaultVideoVirtualProductTest" extends="AdminAddRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="ProductVideo"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add/remove default product video for a Virtual Product"/> + <description value="Admin should be able to add/remove default product video for a Virtual Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-109"/> + <testCaseId value="MC-204"/> + <group value="catalog"/> + <group value="productVideo"/> + </annotations> + <before> + <remove keyForRemoval="setYoutubeApiKeyConfig"/> + <createData entity="VirtualProduct" stepKey="createProduct"/> + </before> + <after> + <remove keyForRemoval="setYoutubeApiKeyDefaultConfig"/> + </after> + + <actionGroup ref="AddProductVideoActionGroup" stepKey="addProductVideo"> + <argument name="video" value="VimeoProductVideo"/> + </actionGroup> + <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertProductVideoPresentInProductEditPage"> + <argument name="video" value="VimeoProductVideo"/> + </actionGroup> + + <actionGroup ref="AssertProductVideoStorefrontProductPageActionGroup" stepKey="assertProductVideoPresentInStorefrontProductPage"> + <argument name="videoType" value="vimeo"/> + </actionGroup> + + <actionGroup ref="AssertProductVideoNotInAdminProductPageActionGroup" stepKey="assertProductVideoAbsentInProductEditPage"> + <argument name="video" value="VimeoProductVideo"/> + </actionGroup> + + <actionGroup ref="AssertProductVideoNotInStorefrontProductPageActionGroup" stepKey="assertProductVideoAbsentInStorefrontProductPage"> + <argument name="videoType" value="vimeo"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml deleted file mode 100644 index 2f47163cff9f6..0000000000000 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml +++ /dev/null @@ -1,39 +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="AdminRemoveDefaultVideoSimpleProductTest"> - <annotations> - <group value="ProductVideo"/> - <skip> - <issueId value="MC-32197"/> - </skip> - </annotations> - <before> - <!-- Set product video Youtube api key configuration --> - <createData entity="ProductVideoYoutubeApiKeyConfig" stepKey="setStoreConfig" after="loginAsAdmin"/> - </before> - <after> - <!-- Set product video configuration to default --> - <createData entity="DefaultProductVideoConfig" stepKey="setStoreDefaultConfig" before="amOnLogoutPage"/> - </after> - - <!-- Add product video --> - <actionGroup ref="AddProductVideoActionGroup" stepKey="addProductVideo" after="fillMainProductForm"/> - - <!-- Remove product video --> - <actionGroup ref="RemoveProductVideoActionGroup" stepKey="removeProductVideo" after="saveProductForm"/> - - <!-- Assert product video not in admin product form --> - <actionGroup ref="AssertProductVideoNotInAdminProductPageActionGroup" stepKey="assertProductVideoNotInAdminProductPage" after="saveProductFormAfterRemove"/> - - <!-- Assert product video not in storefront product page --> - <actionGroup ref="AssertProductVideoNotInStorefrontProductPageActionGroup" stepKey="assertProductVideoNotInStorefrontProductPage" after="AssertProductInStorefrontProductPage"/> - </test> -</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml index 96aa49a53081b..5d31292e3fd59 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml @@ -10,14 +10,12 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminValidateUrlOnGetVideoInformationTest"> <annotations> + <features value="ProductVideo"/> <stories value="Admin validates the url when getting video information"/> <title value="Admin validates the url when getting video information"/> <description value="Testing for a required video url when getting video information"/> <severity value="CRITICAL"/> - <group value="ProductVideo"/> - <skip> - <issueId value="MC-32197"/> - </skip> + <group value="productVideo"/> </annotations> <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> @@ -38,7 +36,7 @@ </actionGroup> <actionGroup ref="AdminFillProductVideoFieldActionGroup" stepKey="fillVideoUrlField"> <argument name="input" value="{{AdminProductNewVideoSection.videoUrlTextField}}"/> - <argument name="value" value="{{mftfTestProductVideo.videoUrl}}"/> + <argument name="value" value="{{YoutubeProductVideo.videoUrl}}"/> </actionGroup> <actionGroup ref="AdminGetVideoInformationActionGroup" stepKey="clickOnGetVideoInformation2"/> <actionGroup ref="AdminAssertVideoNoValidationErrorActionGroup" stepKey="dontSeeUrlValidationMessage"> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml index 92ea0aa86000f..3032f5208dd59 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml @@ -11,14 +11,15 @@ <test name="YoutubeVideoWindowOnProductPageTest"> <annotations> <features value="ProductVideo"/> - <stories value="MAGETWO-91707: [Sigma Beauty]Cannot pause Youtube video in IE 11"/> - <testCaseId value="MAGETWO-95254"/> + <stories value="Check product video on storefront"/> <title value="Youtube video window on the product page"/> <description value="Check Youtube video window on the product page"/> - <severity value="BLOCKER"/> - <group value="ProductVideo"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95254"/> + <useCaseId value="MAGETWO-91707"/> + <group value="productVideo"/> <skip> - <issueId value="MC-32197"/> + <issueId value="MC-33903"/> </skip> </annotations> diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js index 653434f1008ca..5756356d4ff24 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js @@ -520,7 +520,7 @@ define([ if (type === 'youtube') { googleapisUrl = 'https://www.googleapis.com/youtube/v3/videos?id=' + id + - '&part=snippet,contentDetails,statistics,status&key=' + + '&part=snippet,contentDetails&key=' + this.options.youtubeKey + '&alt=json&callback=?'; $.getJSON(googleapisUrl, { diff --git a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml index 12427c2caec25..904a07d72035f 100644 --- a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml +++ b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml @@ -125,15 +125,15 @@ <wait time="60" stepKey="waitForCartToBeUpdated"/> <reloadPage stepKey="reloadPage"/> <waitForPageLoad stepKey="waitForCheckoutPageReload"/> - <click selector="{{StorefrontMiniCartSection.show}}" stepKey="clickMiniCart"/> - <dontSeeElement selector="{{StorefrontMiniCartSection.quantity}}" stepKey="dontSeeCartItem"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickMiniCart"/> + <dontSeeElement selector="{{StorefrontMinicartSection.quantity}}" stepKey="dontSeeCartItem"/> <!-- Add simple product to shopping cart --> <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct2.name$$)}}" stepKey="amOnSimpleProductPage2"/> <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="cartAddSimpleProductToCart2"> <argument name="product" value="$$createSimpleProduct2$$"/> <argument name="productCount" value="1"/> </actionGroup> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckoutCartPage"/> <!-- Disabled via admin panel --> <openNewTab stepKey="openNewTab2"/> <!-- Find the first simple product that we just created using the product grid and go to its page --> @@ -153,7 +153,7 @@ <wait time="60" stepKey="waitForCartToBeUpdated2"/> <reloadPage stepKey="reloadPage2"/> <waitForPageLoad stepKey="waitForCheckoutPageReload2"/> - <click selector="{{StorefrontMiniCartSection.show}}" stepKey="clickMiniCart2"/> - <dontSeeElement selector="{{StorefrontMiniCartSection.quantity}}" stepKey="dontSeeCartItem2"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickMiniCart2"/> + <dontSeeElement selector="{{StorefrontMinicartSection.quantity}}" stepKey="dontSeeCartItem2"/> </test> </tests> diff --git a/app/code/Magento/ReleaseNotification/Ui/DataProvider/Modifier/Notifications.php b/app/code/Magento/ReleaseNotification/Ui/DataProvider/Modifier/Notifications.php index e82d61d499663..f42aedd9634b5 100644 --- a/app/code/Magento/ReleaseNotification/Ui/DataProvider/Modifier/Notifications.php +++ b/app/code/Magento/ReleaseNotification/Ui/DataProvider/Modifier/Notifications.php @@ -18,6 +18,8 @@ /** * Modifies the metadata returning to the Release Notification data provider + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Notifications implements ModifierInterface { @@ -91,7 +93,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -99,7 +101,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -149,6 +151,7 @@ private function buildNotificationMeta(array $meta, array $page, $isLastPage) 'actions' => [ [ 'targetName' => '${ $.name }', + '__disableTmpl' => ['targetName' => false], 'actionName' => 'closeReleaseNotes' ] ], @@ -232,6 +235,7 @@ private function unserializeContent($modalContent) /** * Returns the current Magento version used to retrieve the release notification content. + * * Version information after the dash (-) character is removed (ex. -dev or -rc). * * @return string diff --git a/app/code/Magento/Review/Model/ResourceModel/Review.php b/app/code/Magento/Review/Model/ResourceModel/Review.php index 2e08838e4c885..b017415418cf3 100644 --- a/app/code/Magento/Review/Model/ResourceModel/Review.php +++ b/app/code/Magento/Review/Model/ResourceModel/Review.php @@ -361,12 +361,12 @@ protected function aggregateReviewSummary($object, $ratingSummaryObject) ); $select = $connection->select()->from($this->_aggregateTable) ->where('entity_pk_value = :pk_value') - ->where('entity_type = :entity_type') - ->where('store_id = :store_id'); + ->where('store_id = :store_id') + ->where('entity_type = :entity_type'); $bind = [ ':pk_value' => $object->getEntityPkValue(), - ':entity_type' => $object->getEntityId(), ':store_id' => $ratingSummaryObject->getStoreId(), + ':entity_type' => $object->getEntityId(), ]; $oldData = $connection->fetchRow($select, $bind); $data = new \Magento\Framework\DataObject(); @@ -424,6 +424,7 @@ protected function _loadVotedRatingIds($reviewId) /** * Aggregate this review's ratings. + * * Useful, when changing the review. * * @param int[] $ratingIds @@ -471,6 +472,7 @@ public function getEntityIdByCode($entityCode) /** * Delete reviews by product id. + * * Better to call this method in transaction, because operation performed on two separated tables * * @param int $productId diff --git a/app/code/Magento/Review/Setup/Patch/Schema/AddUniqueConstraintToReviewEntitySummary.php b/app/code/Magento/Review/Setup/Patch/Schema/AddUniqueConstraintToReviewEntitySummary.php new file mode 100644 index 0000000000000..8139e3d3629db --- /dev/null +++ b/app/code/Magento/Review/Setup/Patch/Schema/AddUniqueConstraintToReviewEntitySummary.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Review\Setup\Patch\Schema; + +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Setup\Patch\SchemaPatchInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * Schema patch to add unique constraint (entity_pk_value, store_id, entity_type) to review_entity_summary. + */ +class AddUniqueConstraintToReviewEntitySummary implements SchemaPatchInterface +{ + /** + * Table name to modify + */ + private const TABLE = 'review_entity_summary'; + /** + * Columns to be unique + */ + private const COLUMNS = [ + 'entity_pk_value', + 'store_id', + 'entity_type', + ]; + + /** + * @var SchemaSetupInterface + */ + private $setup; + + /** + * @param SchemaSetupInterface $setup + */ + public function __construct( + SchemaSetupInterface $setup + ) { + $this->setup = $setup; + } + + /** + * @inheritDoc + */ + public function apply() + { + $this->setup->startSetup(); + $this->addUniqueKey(); + $this->setup->endSetup(); + return $this; + } + + /** + * Add unique constraint (entity_pk_value, store_id, entity_type) to review_entity_summary. + * + * Remove duplicate entries if any and retries until the unique key is successfully added. + */ + private function addUniqueKey(): void + { + $this->setup->getConnection()->addIndex( + $this->setup->getTable(self::TABLE), + $this->setup->getIdxName( + self::TABLE, + self::COLUMNS, + AdapterInterface::INDEX_TYPE_UNIQUE + ), + self::COLUMNS, + AdapterInterface::INDEX_TYPE_UNIQUE + ); + } + + /** + * @inheritDoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritDoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php b/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php index 5f1401a201e3f..13f726f711219 100644 --- a/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php +++ b/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php @@ -85,10 +85,12 @@ public function modifyMeta(array $meta) 'behaviourType' => 'simple', 'externalFilterMode' => true, 'imports' => [ - 'productId' => '${ $.provider }:data.product.current_product_id' + 'productId' => '${ $.provider }:data.product.current_product_id', + '__disableTmpl' => ['productId' => false], ], 'exports' => [ - 'productId' => '${ $.externalProvider }:params.current_product_id' + 'productId' => '${ $.externalProvider }:params.current_product_id', + '__disableTmpl' => ['productId' => false], ], ], ], diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 6a62ba7fbf0fd..e6a209b541198 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -6,7 +6,9 @@ namespace Magento\Sales\Block\Adminhtml\Order\Create\Form; +use Magento\Customer\Api\GroupManagementInterface; use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Data\Form\Element\AbstractElement; use Magento\Framework\Pricing\PriceCurrencyInterface; @@ -50,6 +52,7 @@ class Account extends AbstractForm * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter * @param array $data + * @param GroupManagementInterface|null $groupManagement * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -62,11 +65,13 @@ public function __construct( \Magento\Customer\Model\Metadata\FormFactory $metadataFormFactory, \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository, \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter, - array $data = [] + array $data = [], + ?GroupManagementInterface $groupManagement = null ) { $this->_metadataFormFactory = $metadataFormFactory; $this->customerRepository = $customerRepository; $this->_extensibleDataObjectConverter = $extensibleDataObjectConverter; + $this->groupManagement = $groupManagement ?: ObjectManager::getInstance()->get(GroupManagementInterface::class); parent::__construct( $context, $sessionQuote, @@ -78,6 +83,13 @@ public function __construct( ); } + /** + * Group Management + * + * @var GroupManagementInterface + */ + private $groupManagement; + /** * Return Header CSS Class * @@ -163,6 +175,7 @@ public function getFormValues() { try { $customer = $this->customerRepository->getById($this->getCustomerId()); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch } catch (\Exception $e) { $data = []; } @@ -179,6 +192,10 @@ public function getFormValues() } } + if (array_key_exists('group_id', $data) && empty($data['group_id'])) { + $data['group_id'] = $this->groupManagement->getDefaultGroup($this->getQuote()->getStoreId())->getId(); + } + if ($this->getQuote()->getCustomerEmail()) { $data['email'] = $this->getQuote()->getCustomerEmail(); } diff --git a/app/code/Magento/Sales/Controller/Guest/Form.php b/app/code/Magento/Sales/Controller/Guest/Form.php index 04bb66f3d5b6e..c1452801c4ad6 100644 --- a/app/code/Magento/Sales/Controller/Guest/Form.php +++ b/app/code/Magento/Sales/Controller/Guest/Form.php @@ -18,7 +18,7 @@ use Magento\Sales\Helper\Guest as GuestHelper; /** - * Class Form + * Class Form - display Sales and Returns form for guest users */ class Form extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface { @@ -63,7 +63,7 @@ public function __construct( public function execute() { if ($this->customerSession->isLoggedIn()) { - return $this->resultRedirectFactory->create()->setPath('customer/account/'); + return $this->resultRedirectFactory->create()->setPath('sales/order/history'); } $resultPage = $this->resultPageFactory->create(); diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index d1b3e67815098..67a533ea88550 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -1645,6 +1645,7 @@ public function setAccountData($accountData) $data, \Magento\Customer\Api\Data\CustomerInterface::class ); + $customer->setStoreId($this->getQuote()->getStoreId()); $this->getQuote()->updateCustomerData($customer); $data = []; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index ba82478c9cea1..63dff3660a1b5 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -7,7 +7,9 @@ namespace Magento\Sales\Model\Order\Pdf; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\MediaStorage\Helper\File\Storage\Database; +use Magento\Sales\Model\RtlTextHandler; /** * Sales Order PDF abstract model @@ -53,6 +55,11 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject */ protected $_pdf; + /** + * @var RtlTextHandler + */ + private $rtlTextHandler; + /** * Retrieve PDF * @@ -142,6 +149,7 @@ abstract public function getPdf(); * @param \Magento\Sales\Model\Order\Address\Renderer $addressRenderer * @param array $data * @param Database $fileStorageDatabase + * @param RtlTextHandler|null $rtlTextHandler * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -156,7 +164,8 @@ public function __construct( \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation, \Magento\Sales\Model\Order\Address\Renderer $addressRenderer, array $data = [], - Database $fileStorageDatabase = null + Database $fileStorageDatabase = null, + ?RtlTextHandler $rtlTextHandler = null ) { $this->addressRenderer = $addressRenderer; $this->_paymentData = $paymentData; @@ -169,8 +178,8 @@ public function __construct( $this->_pdfTotalFactory = $pdfTotalFactory; $this->_pdfItemsFactory = $pdfItemsFactory; $this->inlineTranslation = $inlineTranslation; - $this->fileStorageDatabase = $fileStorageDatabase ?: - \Magento\Framework\App\ObjectManager::getInstance()->get(Database::class); + $this->fileStorageDatabase = $fileStorageDatabase ?: ObjectManager::getInstance()->get(Database::class); + $this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class); parent::__construct($data); } @@ -501,7 +510,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = $_value; + $text[] = $this->rtlTextHandler->reverseRtlText($_value); } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8'); @@ -518,7 +527,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = $_value; + $text[] = $this->rtlTextHandler->reverseRtlText($_value); } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 285, $this->y, 'UTF-8'); diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index 23c2c00daadc3..253dbd43fa580 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -7,6 +7,9 @@ namespace Magento\Sales\Model\Order\Pdf\Items\Invoice; +use Magento\Framework\App\ObjectManager; +use Magento\Sales\Model\RtlTextHandler; + /** * Sales Order Invoice Pdf default items renderer */ @@ -19,6 +22,11 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems */ protected $string; + /** + * @var RtlTextHandler + */ + private $rtlTextHandler; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -29,6 +37,8 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param RtlTextHandler|null $rtlTextHandler + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\Model\Context $context, @@ -39,7 +49,8 @@ public function __construct( \Magento\Framework\Stdlib\StringUtils $string, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + ?RtlTextHandler $rtlTextHandler = null ) { $this->string = $string; parent::__construct( @@ -52,6 +63,7 @@ public function __construct( $resourceCollection, $data ); + $this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class); } /** @@ -70,16 +82,14 @@ public function draw() // draw Product name $lines[0] = [ [ - // phpcs:ignore Magento2.Functions.DiscouragedFunction - 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true), + 'text' => $this->string->split($this->prepareText((string)$item->getName()), 35, true, true), 'feed' => 35 ] ]; // draw SKU $lines[0][] = [ - // phpcs:ignore Magento2.Functions.DiscouragedFunction - 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17), + 'text' => $this->string->split($this->prepareText((string)$this->getSku($item)), 17), 'feed' => 290, 'align' => 'right', ]; @@ -156,4 +166,16 @@ public function draw() $page = $pdf->drawLineBlocks($page, [$lineBlock], ['table_header' => true]); $this->setPage($page); } + + /** + * Returns prepared for PDF text, reversed in case of RTL text + * + * @param string $string + * @return string + */ + private function prepareText(string $string): string + { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return $this->rtlTextHandler->reverseRtlText(html_entity_decode($string)); + } } diff --git a/app/code/Magento/Sales/Model/RtlTextHandler.php b/app/code/Magento/Sales/Model/RtlTextHandler.php new file mode 100644 index 0000000000000..cfb88dc63f58b --- /dev/null +++ b/app/code/Magento/Sales/Model/RtlTextHandler.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model; + +use Magento\Framework\Stdlib\StringUtils; + +class RtlTextHandler +{ + /** + * @var StringUtils + */ + private $stringUtils; + + /** + * @param StringUtils $stringUtils + */ + public function __construct(StringUtils $stringUtils) + { + $this->stringUtils = $stringUtils; + } + + /** + * Detect an input string is Arabic + * + * @param string $subject + * @return bool + */ + public function isRtlText(string $subject): bool + { + return (preg_match('/[\p{Arabic}\p{Hebrew}]/u', $subject) > 0); + } + + /** + * Reverse text with Arabic characters + * + * @param string $string + * @return string + */ + public function reverseRtlText(string $string): string + { + $splitText = explode(' ', $string); + $splitTextAmount = count($splitText); + + for ($i = 0; $i < $splitTextAmount; $i++) { + if ($this->isRtlText($splitText[$i])) { + for ($j = $i + 1; $j < $splitTextAmount; $j++) { + $tmp = $this->isRtlText($splitText[$j]) + ? $this->stringUtils->strrev($splitText[$j]) : $splitText[$j]; + $splitText[$j] = $this->isRtlText($splitText[$i]) + ? $this->stringUtils->strrev($splitText[$i]) : $splitText[$i]; + $splitText[$i] = $tmp; + } + } + } + + return implode(' ', $splitText); + } +} diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertAuthorizeButtonOnOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertAuthorizeButtonOnOrderPageActionGroup.xml new file mode 100644 index 0000000000000..c105a89509ef4 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertAuthorizeButtonOnOrderPageActionGroup.xml @@ -0,0 +1,17 @@ +<?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="AdminAssertAuthorizeButtonOnOrderPageActionGroup"> + <annotations> + <description>Assert that order waiting for authorization.</description> + </annotations> + <seeElement selector="{{AdminOrderDetailsMainActionsSection.authorize}}" stepKey="seeOrderWaitingForAuthorize"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertCurrencyInOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertCurrencyInOrderActionGroup.xml new file mode 100644 index 0000000000000..385799fb86dac --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertCurrencyInOrderActionGroup.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="AdminAssertCurrencyInOrderActionGroup"> + <annotations> + <description>Admin assert different currencies</description> + </annotations> + <arguments> + <argument name="rate" type="string" defaultValue="USD / USD rate"/> + </arguments> + <see selector="{{AdminOrderDetailsInformationSection.orderInformationTable}}" userInput="{{rate}}" stepKey="seeRate"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertNoAuthorizeButtonOnOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertNoAuthorizeButtonOnOrderPageActionGroup.xml new file mode 100644 index 0000000000000..e88e0cfac30b2 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertNoAuthorizeButtonOnOrderPageActionGroup.xml @@ -0,0 +1,17 @@ +<?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="AdminAssertNoAuthorizeButtonOnOrderPageActionGroup"> + <annotations> + <description>Assert that order not waiting for authorization.</description> + </annotations> + <dontSeeElement selector="{{AdminOrderDetailsMainActionsSection.authorize}}" stepKey="dontSeeOrderWaitingForAuthorize"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml index ecdf9e34de55a..9ce111663720d 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml @@ -25,5 +25,6 @@ <element name="invoiceBtn" type="button" selector="//button[@title='Invoice']"/> <element name="shipBtn" type="button" selector="//button[@title='Ship']"/> <element name="shipmentsTab" type="button" selector="#sales_order_view_tabs_order_shipments"/> + <element name="authorize" type="button" selector="#order_authorize"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml index 0a4445f338960..0ff5080bd8df2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml @@ -24,6 +24,7 @@ <!--Create product--> <createData entity="SimpleProduct2" stepKey="createSimpleProductApi"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <!--Create the catalog price rule --> <createData entity="CatalogRuleToPercent" stepKey="createCatalogRule"/> <!--Create order via API--> @@ -43,9 +44,7 @@ <after> <deleteData createDataKey="createSimpleProductApi" stepKey="deleteSimpleProductApi"/> - <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> - <argument name="ruleName" value="{{CatalogRuleToPercent.name}}"/> - </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </after> diff --git a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php index c642cdeb2d91e..4cda04f197f4c 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php @@ -30,13 +30,14 @@ use Magento\Sales\Model\Order\Item as OrderItem; use Magento\Sales\Model\ResourceModel\Order\Item\Collection as ItemCollection; use Magento\Store\Api\Data\StoreInterface; -use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) */ -class CreateTest extends \PHPUnit\Framework\TestCase +class CreateTest extends TestCase { const CUSTOMER_ID = 1; @@ -46,12 +47,12 @@ class CreateTest extends \PHPUnit\Framework\TestCase private $adminOrderCreate; /** - * @var CartRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CartRepositoryInterface|MockObject */ private $quoteRepository; /** - * @var QuoteFactory|\PHPUnit_Framework_MockObject_MockObject + * @var QuoteFactory|MockObject */ private $quoteFactory; @@ -111,7 +112,7 @@ protected function setUp() ->setMethods(['getForCustomer']) ->getMockForAbstractClass(); - $this->sessionQuote = $this->getMockBuilder(\Magento\Backend\Model\Session\Quote::class) + $this->sessionQuote = $this->getMockBuilder(SessionQuote::class) ->disableOriginalConstructor() ->setMethods( [ @@ -227,6 +228,7 @@ public function testSetAccountData() 'customer_tax_class_id' => $taxClassId ] ); + $quote->method('getStoreId')->willReturn(1); $this->dataObjectHelper->method('populateWithArray') ->with( $customer, @@ -245,6 +247,10 @@ public function testSetAccountData() $this->groupRepository->method('getById') ->willReturn($customerGroup); + $customer->expects($this->once()) + ->method('setStoreId') + ->with(1); + $this->adminOrderCreate->setAccountData(['group_id' => 1]); } diff --git a/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php new file mode 100644 index 0000000000000..2faeb17dc2395 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/RtlTextHandlerTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Model; + +use Magento\Framework\Stdlib\StringUtils; +use Magento\Sales\Model\RtlTextHandler; +use PHPUnit\Framework\TestCase; + +class RtlTextHandlerTest extends TestCase +{ + /** + * @var RtlTextHandler + */ + private $rtlTextHandler; + + /** + * @var StringUtils + */ + private $stringUtils; + + protected function setUp(): void + { + $this->stringUtils = new StringUtils(); + $this->rtlTextHandler = new RtlTextHandler($this->stringUtils); + } + + /** + * @param string $str + * @param bool $isRtl + * @dataProvider provideRtlTexts + */ + public function testIsRtlText(string $str, bool $isRtl): void + { + $this->assertEquals($isRtl, $this->rtlTextHandler->isRtlText($str)); + } + + /** + * @param string $str + * @param bool $isRtl + * @dataProvider provideRtlTexts + */ + public function testReverseRtlText(string $str, bool $isRtl): void + { + $expectedStr = $isRtl ? $this->stringUtils->strrev($str) : $str; + + $this->assertEquals($expectedStr, $this->rtlTextHandler->reverseRtlText($str)); + } + + public function provideRtlTexts(): array + { + return [ + ['Adeline Jacobson', false],//English + ['Odell Fisher', false],//English + ['Панов Аркадий Львович', false],//Russian + ['Вероника Сергеевна Игнатьева', false],//Russian + ['Mehmet Arnold-Döring', false],//German + ['Herr Prof. Dr. Gerald Schüler B.A.', false],//German + ['نديم مقداد نعمان القحطاني', true],//Arabic + ['شهاب الفرحان', true],//Arabic + ['צבר קרליבך', true],//Hebrew + ['גורי מייזליש', true],//Hebrew + ['اتابک بهشتی', true],//Persian + ['مهداد محمدی', true],//Persian + ]; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Ui/Component/Listing/Column/Status/OptionsTest.php b/app/code/Magento/Sales/Test/Unit/Ui/Component/Listing/Column/Status/OptionsTest.php index fe285d29d703b..a62d62f96c560 100644 --- a/app/code/Magento/Sales/Test/Unit/Ui/Component/Listing/Column/Status/OptionsTest.php +++ b/app/code/Magento/Sales/Test/Unit/Ui/Component/Listing/Column/Status/OptionsTest.php @@ -8,9 +8,10 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory; use Magento\Sales\Ui\Component\Listing\Column\Status\Options; +use PHPUnit\Framework\MockObject\MockObject; /** - * Class OptionsTest + * Class Options test for Listing Column Status */ class OptionsTest extends \PHPUnit\Framework\TestCase { @@ -20,7 +21,7 @@ class OptionsTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CollectionFactory|MockObject */ protected $collectionFactoryMock; @@ -54,7 +55,6 @@ public function testToOptionArray() [ 'value' => '1', 'label' => 'Label', - '__disableTmpl' => true ] ]; diff --git a/app/code/Magento/Sales/Ui/Component/Listing/Column/Status/Options.php b/app/code/Magento/Sales/Ui/Component/Listing/Column/Status/Options.php index 14964f16b701e..680064596d0a7 100644 --- a/app/code/Magento/Sales/Ui/Component/Listing/Column/Status/Options.php +++ b/app/code/Magento/Sales/Ui/Component/Listing/Column/Status/Options.php @@ -9,7 +9,7 @@ use Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory; /** - * Class Options + * Class Options for Listing Column Status */ class Options implements OptionSourceInterface { @@ -42,14 +42,6 @@ public function toOptionArray() { if ($this->options === null) { $options = $this->collectionFactory->create()->toOptionArray(); - - array_walk( - $options, - function (&$option) { - $option['__disableTmpl'] = true; - } - ); - $this->options = $options; } return $this->options; diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml index 67e75d63e016e..221f80b887fe5 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml @@ -86,8 +86,7 @@ <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> <waitForPageLoad stepKey="waitForAddToCart"/> <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <actionGroup ref="StorefrontApplyCouponActionGroup" stepKey="applyCoupon"> <argument name="coupon" value="_defaultCoupon"/> </actionGroup> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml index 2bf96cf0377d4..e2a65685bd97e 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml @@ -78,8 +78,7 @@ <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> <waitForPageLoad stepKey="waitForAddToCart"/> <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <actionGroup ref="StorefrontApplyCouponActionGroup" stepKey="applyCoupon"> <argument name="coupon" value="_defaultCoupon"/> </actionGroup> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml index 9833231f8ed84..24c3a7cd44bc8 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml @@ -80,8 +80,7 @@ <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> <waitForPageLoad stepKey="waitForAddToCart"/> <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <conditionalClick selector="{{StorefrontSalesRuleCartCouponSection.couponHeader}}" dependentSelector="{{StorefrontSalesRuleCartCouponSection.discountBlockActive}}" visible="false" stepKey="clickCouponHeader"/> <waitForElementVisible selector="{{StorefrontSalesRuleCartCouponSection.couponField}}" stepKey="waitForCouponField" /> <fillField selector="{{StorefrontSalesRuleCartCouponSection.couponField}}" userInput="{$grabCouponCode}" stepKey="fillCouponField"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml index 4cc331eebc4ee..51e25d3a7e255 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml @@ -71,8 +71,7 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!-- Should not see the discount yet because we have not set country --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <click selector="{{CheckoutCartSummarySection.shippingHeading}}" stepKey="openEstimateShippingSection"/> <checkOption selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectFlatRateShipping"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml index 4d987c00884e1..420bc37d5c1b2 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml @@ -75,8 +75,7 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!-- Should not see the discount yet because we have not filled in postcode --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <click selector="{{CheckoutCartSummarySection.shippingHeading}}" stepKey="openEstimateShippingSection"/> <checkOption selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectFlatRateShipping"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml index ff0e011210785..279747f87d66d 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml @@ -73,8 +73,7 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!-- Should not see the discount yet because we have only 1 item in our cart --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount"/> @@ -87,8 +86,7 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage2"/> <!-- Now we should see the discount because we have more than 1 item --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage2"/> - <waitForPageLoad stepKey="waitForCartPage2"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage2"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$246.00" stepKey="seeSubtotal2"/> <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$1.00" stepKey="seeDiscountTotal"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml index 036844562af50..a3f32c0781a52 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml @@ -71,8 +71,7 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!-- Should not see the discount yet because we have not filled in postcode --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <click selector="{{CheckoutCartSummarySection.shippingHeading}}" stepKey="expandShipping"/> <checkOption selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectFlatRateShipping"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml index efd82316f0b0a..39ac14315110e 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml @@ -71,8 +71,7 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> <!-- Should not see the discount yet because we have not exceeded $200 --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> - <waitForPageLoad stepKey="waitForCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$123.00" stepKey="seeSubtotal"/> <dontSeeElement selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="dontSeeDiscount"/> @@ -85,8 +84,7 @@ <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage2"/> <!-- Now we should see the discount because we exceeded $200 --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage2"/> - <waitForPageLoad stepKey="waitForCartPage2"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCartPage2"/> <see selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$246.00" stepKey="seeSubtotal2"/> <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForDiscountElement"/> <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$0.01" stepKey="seeDiscountTotal"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml index 172f6b6ba24c6..9b5f8fbb2912d 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartTotalValueWithFullDiscountUsingCartRuleTest.xml @@ -113,8 +113,7 @@ </actionGroup> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="6" stepKey="seeCartQuantity"/> <!-- Go to the shopping cart page --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> - <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="amOnPageShoppingCart"/> <waitForElementVisible selector="{{CheckoutCartSummarySection.orderTotal}}" stepKey="waitForOrderTotalVisible"/> <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectCountry"/> <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" stepKey="waitForOrderTotalUpdate"/> diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js b/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js index 0b8eba270b030..1ad0f0b9d70ff 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js @@ -19,6 +19,10 @@ define([ originalSelectPaymentMethodAction(paymentMethod); + if (paymentMethod === null) { + return; + } + $.when( setPaymentInformationExtended( messageContainer, diff --git a/app/code/Magento/Search/Test/Unit/Ui/Component/Listing/Column/SynonymActionsTest.php b/app/code/Magento/Search/Test/Unit/Ui/Component/Listing/Column/SynonymActionsTest.php index d5563ec1cb289..5cf11e9babc25 100644 --- a/app/code/Magento/Search/Test/Unit/Ui/Component/Listing/Column/SynonymActionsTest.php +++ b/app/code/Magento/Search/Test/Unit/Ui/Component/Listing/Column/SynonymActionsTest.php @@ -112,7 +112,6 @@ public function testPrepareDataSourceWithItems() self::STUB_SYNONYM_GROUP_ID ) ], - '__disableTmpl' => true ], 'edit' => [ 'href' => sprintf( @@ -120,7 +119,6 @@ public function testPrepareDataSourceWithItems() self::STUB_SYNONYM_GROUP_ID ), 'label' => (string)__('View/Edit'), - '__disableTmpl' => true ] ] ] diff --git a/app/code/Magento/Search/Ui/Component/Listing/Column/SynonymActions.php b/app/code/Magento/Search/Ui/Component/Listing/Column/SynonymActions.php index f42ce50d2804b..2fd569642375e 100644 --- a/app/code/Magento/Search/Ui/Component/Listing/Column/SynonymActions.php +++ b/app/code/Magento/Search/Ui/Component/Listing/Column/SynonymActions.php @@ -64,12 +64,10 @@ public function prepareDataSource(array $dataSource) 'title' => __('Delete'), 'message' => __('Are you sure you want to delete synonym group with id: %1?', $item['group_id']) ], - '__disableTmpl' => true ]; $item[$name]['edit'] = [ 'href' => $this->urlBuilder->getUrl(self::SYNONYM_URL_PATH_EDIT, ['group_id' => $item['group_id']]), 'label' => __('View/Edit'), - '__disableTmpl' => true ]; } } diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreGroupTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreGroupTest.xml index 349bcfe8f31e0..87e475b5431ad 100644 --- a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreGroupTest.xml +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreGroupTest.xml @@ -44,11 +44,10 @@ </actionGroup> <!--Go to backup index page and verify AssertBackupInGrid--> - <amOnPage url="{{BackupIndexPage.url}}" stepKey="goToBackupIndexPage"/> - <waitForPageLoad stepKey="waitForBackupIndexPageLoad"/> + <actionGroup ref="AdminBackupIndexPageOpenActionGroup" stepKey="navigateToBackupPage"/> <see selector="{{AdminGridTableSection.backupNameColumn}}" userInput="{{WebSetupWizardBackup.name}}" stepKey="seeBackupInGrid"/> <!--Delete database backup--> - <actionGroup ref="deleteBackup" stepKey="deleteDatabaseBackup"> + <actionGroup ref="AdminBackupDeleteActionGroup" stepKey="deleteDatabaseBackup"> <argument name="backup" value="WebSetupWizardBackup"/> </actionGroup> </test> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml index 305b4540668e4..7c0c668cc1209 100644 --- a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml @@ -42,11 +42,10 @@ </actionGroup> <!--Go to backup index page and verify AssertBackupInGrid--> - <amOnPage url="{{BackupIndexPage.url}}" stepKey="goToBackupIndexPage"/> - <waitForPageLoad stepKey="waitForBackupIndexPageLoad"/> + <actionGroup ref="AdminBackupIndexPageOpenActionGroup" stepKey="navigateToBackupPage"/> <see selector="{{AdminGridTableSection.backupNameColumn}}" userInput="{{WebSetupWizardBackup.name}}" stepKey="seeBackupInGrid"/> <!--Delete database backup--> - <actionGroup ref="deleteBackup" stepKey="deleteDatabaseBackup"> + <actionGroup ref="AdminBackupDeleteActionGroup" stepKey="deleteDatabaseBackup"> <argument name="backup" value="WebSetupWizardBackup"/> </actionGroup> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontCustomerCanChangeProductOptionsUsingSwatchesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontCustomerCanChangeProductOptionsUsingSwatchesTest.xml index 32e447f6463c0..ab532538cc3f3 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontCustomerCanChangeProductOptionsUsingSwatchesTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontCustomerCanChangeProductOptionsUsingSwatchesTest.xml @@ -44,7 +44,7 @@ </actionGroup> <!-- Go to shopping cart and update option of configurable product --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="openShoppingCartPage"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="openShoppingCartPage"/> <actionGroup ref="StorefrontUpdateCartConfigurableProductWithSwatchesActionGroup" stepKey="updateConfigurableProductInTheCart"> <argument name="product" value="_defaultProduct"/> <argument name="productOption" value="e74d3c"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchProductWithFileCustomOptionTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchProductWithFileCustomOptionTest.xml index e027a41cd9d2a..1b77e773ef283 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchProductWithFileCustomOptionTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchProductWithFileCustomOptionTest.xml @@ -76,8 +76,7 @@ <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{BaseConfigurableProduct.name}} to your shopping cart." stepKey="seeSuccessMessage"/> <!--Check item in cart--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCartPageLoad"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCart"/> <seeElement selector="{{CheckoutCartProductSection.ProductLinkByName(BaseConfigurableProduct.name)}}" stepKey="seeProductInCart"/> <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, visualSwatchAttribute.default_label)}}" userInput="{{visualSwatchOption2.default_label}}" stepKey="seeSelectedSwatch"/> <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, ProductOptionFile.title)}}" userInput="{{MagentoLogo.file}}" stepKey="seeCorrectOptionFile"/> diff --git a/app/code/Magento/Tax/Block/Sales/Order/Tax.php b/app/code/Magento/Tax/Block/Sales/Order/Tax.php index 9c992185b4e1d..a08a07260e8c9 100644 --- a/app/code/Magento/Tax/Block/Sales/Order/Tax.php +++ b/app/code/Magento/Tax/Block/Sales/Order/Tax.php @@ -82,7 +82,7 @@ public function initTotals() $this->_order = $parent->getOrder(); $this->_source = $parent->getSource(); - $store = $this->getStore(); + $store = $this->_order->getStore(); $allowTax = $this->_source->getTaxAmount() > 0 || $this->_config->displaySalesZeroTax($store); $grandTotal = (double)$this->_source->getGrandTotal(); if (!$grandTotal || $allowTax && !$this->_config->displaySalesTaxWithGrandTotal($store)) { @@ -131,7 +131,7 @@ public function getStore() */ protected function _initSubtotal() { - $store = $this->getStore(); + $store = $this->_order->getStore(); $parent = $this->getParentBlock(); $subtotal = $parent->getTotal('subtotal'); if (!$subtotal) { @@ -213,7 +213,7 @@ protected function _initSubtotal() */ protected function _initShipping() { - $store = $this->getStore(); + $store = $this->_order->getStore(); /** @var \Magento\Sales\Block\Order\Totals $parent */ $parent = $this->getParentBlock(); $shipping = $parent->getTotal('shipping'); @@ -290,7 +290,7 @@ protected function _initDiscount() */ protected function _initGrandTotal() { - $store = $this->getStore(); + $store = $this->_order->getStore(); $parent = $this->getParentBlock(); $grandototal = $parent->getTotal('grand_total'); if (!$grandototal || !(double)$this->_source->getGrandTotal()) { diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/StorefrontAssertOrderReviewSummaryWithTaxActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/StorefrontAssertOrderReviewSummaryWithTaxActionGroup.xml new file mode 100644 index 0000000000000..a6d16cf49d81f --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/StorefrontAssertOrderReviewSummaryWithTaxActionGroup.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="StorefrontAssertOrderReviewSummaryWithTaxActionGroup" extends="VerifyCheckoutPaymentOrderSummaryActionGroup"> + <annotations> + <description>Validates that the provided Subtotal, Shipping Total and Summary Total prices are present and correct on the Storefront Checkout page.</description> + </annotations> + <arguments> + <argument name="orderSummaryTax" type="string"/> + </arguments> + <see selector="{{StorefrontOrderReviewSection.taxCost}}" userInput="{{orderSummaryTax}}" stepKey="seeCorrectTax" after="seeCorrectOrderTotal"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/StorefrontOrderReviewSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/StorefrontOrderReviewSection.xml new file mode 100644 index 0000000000000..af9721d784862 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Section/StorefrontOrderReviewSection.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="StorefrontOrderReviewSection"> + <element name="taxCost" type="text" selector="//tr[@class='totals-tax']//span[@class='price']"/> + </section> +</sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml index 959aa323308be..b1e91886960c5 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestSimpleTest.xml @@ -82,8 +82,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for CA --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml index c1268638c7104..8a04156f3d857 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartGuestVirtualTest.xml @@ -82,8 +82,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <!-- Assert that taxes are applied correctly for CA --> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml index e43511318f137..b76f015679ae2 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInSimpleTest.xml @@ -96,8 +96,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml index 3b92ab3498442..5f98093ec874f 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest/StorefrontTaxQuoteCartLoggedInVirtualTest.xml @@ -95,8 +95,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> + <actionGroup ref="StorefrontCartPageOpenActionGroup" stepKey="goToCheckout"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> diff --git a/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/EditActionTest.php b/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/EditActionTest.php index 22cc1c9e89fbe..5cd27c58d6665 100644 --- a/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/EditActionTest.php +++ b/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/EditActionTest.php @@ -8,22 +8,23 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Store\Model\ScopeInterface; use Magento\Theme\Ui\Component\Listing\Column\EditAction; +use PHPUnit\Framework\MockObject\MockObject; /** - * Class EditActionTest + * Class EditAction test for Listing Column */ class EditActionTest extends \PHPUnit\Framework\TestCase { /** @var EditAction */ protected $component; - /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponent\ContextInterface|MockObject */ protected $context; - /** @var \Magento\Framework\View\Element\UiComponentFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponentFactory|MockObject */ protected $uiComponentFactory; - /** @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\UrlInterface|MockObject */ protected $urlBuilder; public function setup() @@ -67,7 +68,6 @@ public function testPrepareDataSource($dataSourceItem, $scope, $scopeId) 'edit' => [ 'href' => 'http://magento.com/theme/design_config/edit', 'label' => new \Magento\Framework\Phrase('Edit'), - '__disableTmpl' => true, ] ], ]; diff --git a/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/ViewActionTest.php b/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/ViewActionTest.php index 5e2fe51043885..a7b5624236b2f 100644 --- a/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/ViewActionTest.php +++ b/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/ViewActionTest.php @@ -11,6 +11,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\UrlInterface; use Magento\Theme\Ui\Component\Listing\Column\ViewAction; +use PHPUnit\Framework\MockObject\MockObject; /** * Class ViewActionTest contains unit tests for \Magento\Theme\Ui\Component\Listing\Column\ViewAction class @@ -25,7 +26,7 @@ class ViewActionTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var UrlInterface|\PHPUnit_Framework_MockObject_MockObject + * @var UrlInterface|MockObject */ protected $urlBuilder; @@ -112,7 +113,6 @@ public function getPrepareDataSourceDataProvider() 'view' => [ 'href' => 'url', 'label' => __('View'), - '__disableTmpl' => true, ] ], 'entity_id' => 1 @@ -139,7 +139,6 @@ public function getPrepareDataSourceDataProvider() 'view' => [ 'href' => 'url', 'label' => __('View'), - '__disableTmpl' => true, ] ], 'theme_id' => 2 diff --git a/app/code/Magento/Theme/Ui/Component/Design/Config/SearchRobots/ResetButton.php b/app/code/Magento/Theme/Ui/Component/Design/Config/SearchRobots/ResetButton.php index 5a8829851e8cf..4b71fc6faba15 100644 --- a/app/code/Magento/Theme/Ui/Component/Design/Config/SearchRobots/ResetButton.php +++ b/app/code/Magento/Theme/Ui/Component/Design/Config/SearchRobots/ResetButton.php @@ -76,6 +76,7 @@ public function prepare() [ 'actionName' => 'reset', 'targetName' => '${ $.name }', + '__disableTmpl' => ['targetName' => false], 'params' => [ json_encode($this->getRobotsDefaultCustomInstructions()) ] diff --git a/app/code/Magento/Theme/Ui/Component/Listing/Column/EditAction.php b/app/code/Magento/Theme/Ui/Component/Listing/Column/EditAction.php index 1eeeaccff88ce..821eee8114bae 100644 --- a/app/code/Magento/Theme/Ui/Component/Listing/Column/EditAction.php +++ b/app/code/Magento/Theme/Ui/Component/Listing/Column/EditAction.php @@ -13,7 +13,7 @@ use Magento\Ui\Component\Listing\Columns\Column; /** - * Class EditAction + * Class EditAction for Listing Column */ class EditAction extends Column { @@ -74,7 +74,6 @@ public function prepareDataSource(array $dataSource) ] ), 'label' => __('Edit'), - '__disableTmpl' => true, ] ]; } diff --git a/app/code/Magento/Theme/Ui/Component/Listing/Column/ViewAction.php b/app/code/Magento/Theme/Ui/Component/Listing/Column/ViewAction.php index 9e47e2c52bddf..a49e622b9d42a 100644 --- a/app/code/Magento/Theme/Ui/Component/Listing/Column/ViewAction.php +++ b/app/code/Magento/Theme/Ui/Component/Listing/Column/ViewAction.php @@ -14,7 +14,7 @@ use Magento\Ui\Component\Listing\Columns\Column; /** - * Class ViewAction + * Class ViewAction for Listing Column */ class ViewAction extends Column { @@ -66,7 +66,6 @@ public function prepareDataSource(array $dataSource) : array ] ), 'label' => __('View'), - '__disableTmpl' => true, ] ]; } diff --git a/app/code/Magento/Tinymce3/Model/Config/Gallery/Config.php b/app/code/Magento/Tinymce3/Model/Config/Gallery/Config.php index d11a3fa6e8a0c..e59aa2934e4ed 100644 --- a/app/code/Magento/Tinymce3/Model/Config/Gallery/Config.php +++ b/app/code/Magento/Tinymce3/Model/Config/Gallery/Config.php @@ -7,6 +7,8 @@ namespace Magento\Tinymce3\Model\Config\Gallery; +use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; + /** * Class Config adds information about required configurations to display media gallery of tinymce3 editor * @@ -19,13 +21,21 @@ class Config implements \Magento\Framework\Data\Wysiwyg\ConfigProviderInterface */ private $backendUrl; + /** + * @var OpednDialogUrl + */ + private $openDialogUrl; + /** * @param \Magento\Backend\Model\UrlInterface $backendUrl + * @param OpenDialogUrl $openDialogUrl */ public function __construct( - \Magento\Backend\Model\UrlInterface $backendUrl + \Magento\Backend\Model\UrlInterface $backendUrl, + OpenDialogUrl $openDialogUrl ) { $this->backendUrl = $backendUrl; + $this->openDialogUrl = $openDialogUrl; } /** @@ -39,7 +49,7 @@ public function getConfig(\Magento\Framework\DataObject $config) : \Magento\Fram $config->addData( [ 'add_images' => true, - 'files_browser_window_url' => $this->backendUrl->getUrl('cms/wysiwyg_images/index'), + 'files_browser_window_url' => $this->backendUrl->getUrl($this->openDialogUrl->get()), ] ); diff --git a/app/code/Magento/Tinymce3/composer.json b/app/code/Magento/Tinymce3/composer.json index 7b12851626cf6..2ed89a02cb8b8 100644 --- a/app/code/Magento/Tinymce3/composer.json +++ b/app/code/Magento/Tinymce3/composer.json @@ -7,8 +7,7 @@ "magento/module-backend": "*", "magento/module-ui": "*", "magento/module-variable": "*", - "magento/module-widget": "*", - "magento/module-cms": "*" + "magento/module-widget": "*" }, "suggest": { diff --git a/app/code/Magento/Translation/Test/Mftf/Test/AdminInlineTranslationOnCheckoutTest.xml b/app/code/Magento/Translation/Test/Mftf/Test/AdminInlineTranslationOnCheckoutTest.xml index ad0cf1a5a1bc2..8ed8e56d85c30 100644 --- a/app/code/Magento/Translation/Test/Mftf/Test/AdminInlineTranslationOnCheckoutTest.xml +++ b/app/code/Magento/Translation/Test/Mftf/Test/AdminInlineTranslationOnCheckoutTest.xml @@ -72,7 +72,7 @@ <!-- 3. Go to storefront and click on cart button on the top --> <reloadPage stepKey="reloadPage"/> <waitForPageLoad stepKey="waitForReload"/> - <click selector="{{StorefrontMiniCartSection.show}}" stepKey="showMiniCart"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="showMiniCart"/> <!-- Small cart popup appeared. --> <waitForElementVisible selector="{{StorefrontMinicartSection.blockMiniCart}}" stepKey="seePopUpAppeared"/> @@ -339,7 +339,7 @@ <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToCart"> <argument name="product" value="$$createProduct$$"/> </actionGroup> - <click selector="{{StorefrontMiniCartSection.show}}" stepKey="showCart"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="showCart"/> <!-- Small cart popup appeared. --> <waitForElementVisible selector="{{StorefrontMinicartSection.blockMiniCart}}" stepKey="waitPopUpAppeared"/> @@ -438,7 +438,7 @@ <magentoCLI command="cache:flush" stepKey="flushCache3"/> <reloadPage stepKey="reloadProductPage"/> <waitForPageLoad stepKey="waitForReloadPage"/> - <click selector="{{StorefrontMiniCartSection.show}}" stepKey="showMiniCartPopup"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="showMiniCartPopup"/> <waitForElementVisible selector="{{StorefrontMinicartSection.blockMiniCart}}" stepKey="waitForPopUpAppeared"/> <actionGroup ref="AdminTranslateElementActionGroup" stepKey="revertTranslateProceedToCheckout"> diff --git a/app/code/Magento/Ui/Component/Form/Element/AbstractOptionsField.php b/app/code/Magento/Ui/Component/Form/Element/AbstractOptionsField.php index 586d76828ba3a..f1ff9db2dfde6 100644 --- a/app/code/Magento/Ui/Component/Form/Element/AbstractOptionsField.php +++ b/app/code/Magento/Ui/Component/Form/Element/AbstractOptionsField.php @@ -7,6 +7,7 @@ use Magento\Framework\Data\OptionSourceInterface; use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\UiComponent\DataProvider\Sanitizer; /** * Base abstract form element. @@ -23,6 +24,11 @@ abstract class AbstractOptionsField extends AbstractElement */ protected $options; + /** + * @var Sanitizer + */ + private $sanitizer; + /** * Constructor * @@ -30,14 +36,17 @@ abstract class AbstractOptionsField extends AbstractElement * @param array|OptionSourceInterface|null $options * @param array $components * @param array $data + * @param Sanitizer|null $sanitizer */ public function __construct( ContextInterface $context, $options = null, array $components = [], - array $data = [] + array $data = [], + ?Sanitizer $sanitizer = null ) { $this->options = $options; + $this->sanitizer = $sanitizer ?? \Magento\Framework\App\ObjectManager::getInstance()->get(Sanitizer::class); parent::__construct($context, $components, $data); } @@ -62,13 +71,10 @@ public function prepare() if (empty($config['rawOptions'])) { $options = $this->convertOptionsValueToString($options); } - - array_walk( - $options, - function (&$item) { - $item['__disableTmpl'] = true; - } - ); + foreach ($options as &$option) { + //Options contain static or dynamic entity data that is not supposed to contain templates. + $option = $this->sanitizer->sanitize($option); + } $config['options'] = array_values(array_replace_recursive($config['options'], $options)); } diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php index aee81f65775bc..ba86b19963f69 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/Image.php @@ -31,10 +31,16 @@ class Image extends Media */ private $fileSize; + /** + * @var OpednDialogUrl + */ + private $openDialogUrl; + /** * @param ContextInterface $context * @param StoreManagerInterface $storeManager * @param Size $fileSize + * @param OpenDialogUrl $openDialogUrl * @param UiComponentInterface[] $components * @param array $data */ @@ -42,16 +48,18 @@ public function __construct( ContextInterface $context, StoreManagerInterface $storeManager, Size $fileSize, + OpenDialogUrl $openDialogUrl, array $components = [], array $data = [] ) { $this->storeManager = $storeManager; $this->fileSize = $fileSize; + $this->openDialogUrl = $openDialogUrl; parent::__construct($context, $components, $data); } /** - * {@inheritdoc} + * @inheritdoc */ public function getComponentName() { @@ -59,7 +67,7 @@ public function getComponentName() } /** - * {@inheritdoc} + * @inheritdoc */ public function prepare() { @@ -75,7 +83,10 @@ public function prepare() 'config' => [ 'maxFileSize' => $maxFileSize, 'mediaGallery' => [ - 'openDialogUrl' => $this->getContext()->getUrl('cms/wysiwyg_images/index', ['_secure' => true]), + 'openDialogUrl' => $this->getContext()->getUrl( + $this->openDialogUrl->get(), + ['_secure' => true] + ), 'openDialogTitle' => $this->getConfiguration()['openDialogTitle'] ?? __('Insert Images...'), 'initialOpenSubpath' => $this->getConfiguration()['initialMediaGalleryOpenSubpath'], 'storeId' => $this->storeManager->getStore()->getId(), 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 new file mode 100644 index 0000000000000..27370cbfbd68c --- /dev/null +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Ui\Component\Form\Element\DataType\Media; + +use Magento\Framework\DataObject; + +/** + * Basic configuration for OdenDialogUrl + */ +class OpenDialogUrl +{ + private const DEFAULT_OPEN_DIALOG_URL = 'cms/wysiwyg_images/index'; + + /** + * @var string + */ + private $openDialogUrl; + + /** + * @param DataObject $url + */ + public function __construct(DataObject $url = null) + { + $this->openDialogUrl = $url; + } + + /** + * Returns open dialog url for media browser + * + * @return string + */ + public function get(): string + { + if ($this->openDialogUrl) { + return $this->openDialogUrl->getUrl(); + } + return self::DEFAULT_OPEN_DIALOG_URL; + } +} diff --git a/app/code/Magento/Ui/Component/MassAction.php b/app/code/Magento/Ui/Component/MassAction.php index 5af263dd861ce..4cca8d4c012bb 100644 --- a/app/code/Magento/Ui/Component/MassAction.php +++ b/app/code/Magento/Ui/Component/MassAction.php @@ -28,7 +28,7 @@ public function prepare() if ($disabledAction) { continue; } - $config['actions'][] = array_merge($componentConfig, ['__disableTmpl' => true]); + $config['actions'][] = $componentConfig; } $origConfig = $this->getConfiguration(); diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml index e0f8408a1c30c..fcee31c0bd80c 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml @@ -21,5 +21,6 @@ <element name="dataGridEmpty" type="block" selector=".data-grid-tr-no-data td"/> <element name="rowTemplateStrict" type="block" selector="//tbody/tr[td[*[text()[normalize-space()='{{text}}']]]]" parameterized="true" /> <element name="rowTemplate" type="block" selector="//tbody/tr[td[*[contains(.,normalize-space('{{text}}'))]]]" parameterized="true" timeout="30" /> + <element name="firstNotEmptyRow" type="block" selector="table.data-grid tbody tr[data-role=row]:not(.data-grid-tr-no-data):nth-of-type(1)" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Ui/Test/Unit/Component/MassActionTest.php b/app/code/Magento/Ui/Test/Unit/Component/MassActionTest.php index c2e064bb3b069..5922055328fc1 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/MassActionTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/MassActionTest.php @@ -8,14 +8,15 @@ use Magento\Ui\Component\MassAction; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Element\UiComponent\ContextInterface; +use PHPUnit\Framework\MockObject\MockObject; /** - * Class MassActionTest + * Class MassAction test for Component */ class MassActionTest extends \PHPUnit\Framework\TestCase { /** - * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ContextInterface|MockObject */ protected $contextMock; @@ -105,7 +106,6 @@ public function getPrepareDataProvider() 'type' => 'first_action', 'label' => 'First Action', 'url' => '/module/controller/firstAction', - '__disableTmpl' => true ], ], [ @@ -125,7 +125,6 @@ public function getPrepareDataProvider() 'url' => '/module/controller/secondSubAction2' ], ], - '__disableTmpl' => true ], ], ]; diff --git a/app/code/Magento/Ui/composer.json b/app/code/Magento/Ui/composer.json index 9ec0810bf2d6c..a783ddca73efe 100644 --- a/app/code/Magento/Ui/composer.json +++ b/app/code/Magento/Ui/composer.json @@ -11,8 +11,7 @@ "magento/module-backend": "*", "magento/module-eav": "*", "magento/module-store": "*", - "magento/module-user": "*", - "magento/module-cms": "*" + "magento/module-user": "*" }, "suggest": { "magento/module-config": "*" diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js index b490ac557e71b..99329839bd913 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js @@ -81,7 +81,15 @@ define([ openDialogUrl += '¤t_tree_path=' + Base64.mageEncode(this.mediaGallery.initialOpenSubpath); } - browser.openDialog(openDialogUrl, null, null, this.mediaGallery.openDialogTitle); + browser.openDialog( + openDialogUrl, + null, + null, + this.mediaGallery.openDialogTitle, + { + targetElementId: $buttonEl.attr('id') + } + ); }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/modal/alert.js b/app/code/Magento/Ui/view/base/web/js/modal/alert.js index f36fe54a37a9e..0c7042952bc18 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/alert.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/alert.js @@ -39,18 +39,11 @@ define([ }] }, - /** - * Create widget. - */ - _create: function () { - this.options.actions.always(); - this._super(); - }, - /** * Close modal window. */ closeModal: function () { + this.options.actions.always(); this.element.bind('alertclosed', _.bind(this._remove, this)); return this._super(); diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesMultipleStoreviewsProductImportTest.xml similarity index 99% rename from app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml rename to app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesMultipleStoreviewsProductImportTest.xml index b9f726aec668e..4e46ed8e4fc79 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesMultipleStoreviewsProductImportTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest"> + <test name="AdminCheckUrlRewritesMultipleStoreviewsProductImportTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url Rewrites for Multiple Storeviews"/> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteGeneratedForMultipleStoreviewsDuringProductImportWithConfigTurnedOffTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest.xml similarity index 99% rename from app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteGeneratedForMultipleStoreviewsDuringProductImportWithConfigTurnedOffTest.xml rename to app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest.xml index f9003ce8c0897..1d604ef7648dc 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteGeneratedForMultipleStoreviewsDuringProductImportWithConfigTurnedOffTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminUrlRewriteGeneratedForMultipleStoreviewsDuringProductImportWithConfigTurnedOffTest"> + <test name="AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url Rewrites for Multiple Storeviews"/> diff --git a/app/code/Magento/Wishlist/Controller/Index/Cart.php b/app/code/Magento/Wishlist/Controller/Index/Cart.php index 870c4231f97c9..023d0756bae6f 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Cart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Cart.php @@ -6,93 +6,128 @@ namespace Magento\Wishlist\Controller\Index; +use Magento\Catalog\Helper\Product; use Magento\Catalog\Model\Product\Exception as ProductException; +use Magento\Checkout\Model\Cart as CheckoutCart; +use Magento\Checkout\Helper\Cart as CartHelper; use Magento\Framework\App\Action; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Data\Form\FormKey\Validator; +use Magento\Framework\Escaper; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; +use Magento\Framework\Stdlib\Cookie\PublicCookieMetadata; +use Magento\Framework\Stdlib\CookieManagerInterface; +use Magento\Wishlist\Controller\AbstractIndex; +use Magento\Wishlist\Controller\WishlistProviderInterface; +use Magento\Wishlist\Helper\Data; +use Magento\Wishlist\Model\Item\OptionFactory; +use Magento\Wishlist\Model\ItemFactory; +use Magento\Wishlist\Model\LocaleQuantityProcessor; +use Magento\Wishlist\Model\ResourceModel\Item\Option\Collection; /** * Add wishlist item to shopping cart and remove from wishlist controller. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Cart extends \Magento\Wishlist\Controller\AbstractIndex implements Action\HttpPostActionInterface +class Cart extends AbstractIndex implements Action\HttpPostActionInterface { /** - * @var \Magento\Wishlist\Controller\WishlistProviderInterface + * @var WishlistProviderInterface */ protected $wishlistProvider; /** - * @var \Magento\Wishlist\Model\LocaleQuantityProcessor + * @var LocaleQuantityProcessor */ protected $quantityProcessor; /** - * @var \Magento\Wishlist\Model\ItemFactory + * @var ItemFactory */ protected $itemFactory; /** - * @var \Magento\Checkout\Model\Cart + * @var CheckoutCart */ protected $cart; /** - * @var \Magento\Checkout\Helper\Cart + * @var CartHelper */ protected $cartHelper; /** - * @var \Magento\Wishlist\Model\Item\OptionFactory + * @var OptionFactory */ private $optionFactory; /** - * @var \Magento\Catalog\Helper\Product + * @var Product */ protected $productHelper; /** - * @var \Magento\Framework\Escaper + * @var Escaper */ protected $escaper; /** - * @var \Magento\Wishlist\Helper\Data + * @var Data */ protected $helper; /** - * @var \Magento\Framework\Data\Form\FormKey\Validator + * @var Validator */ protected $formKeyValidator; + /** + * @var CookieManagerInterface + */ + private $cookieManager; + + /** + * @var CookieMetadataFactory + */ + private $cookieMetadataFactory; + /** * @param Action\Context $context - * @param \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider - * @param \Magento\Wishlist\Model\LocaleQuantityProcessor $quantityProcessor - * @param \Magento\Wishlist\Model\ItemFactory $itemFactory - * @param \Magento\Checkout\Model\Cart $cart - * @param \Magento\Wishlist\Model\Item\OptionFactory $optionFactory - * @param \Magento\Catalog\Helper\Product $productHelper - * @param \Magento\Framework\Escaper $escaper - * @param \Magento\Wishlist\Helper\Data $helper - * @param \Magento\Checkout\Helper\Cart $cartHelper - * @param \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator + * @param WishlistProviderInterface $wishlistProvider + * @param LocaleQuantityProcessor $quantityProcessor + * @param ItemFactory $itemFactory + * @param CheckoutCart $cart + * @param OptionFactory $optionFactory + * @param Product $productHelper + * @param Escaper $escaper + * @param Data $helper + * @param CartHelper $cartHelper + * @param Validator $formKeyValidator + * @param CookieManagerInterface|null $cookieManager + * @param CookieMetadataFactory|null $cookieMetadataFactory + * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Action\Context $context, - \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider, - \Magento\Wishlist\Model\LocaleQuantityProcessor $quantityProcessor, - \Magento\Wishlist\Model\ItemFactory $itemFactory, - \Magento\Checkout\Model\Cart $cart, - \Magento\Wishlist\Model\Item\OptionFactory $optionFactory, - \Magento\Catalog\Helper\Product $productHelper, - \Magento\Framework\Escaper $escaper, - \Magento\Wishlist\Helper\Data $helper, - \Magento\Checkout\Helper\Cart $cartHelper, - \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator + WishlistProviderInterface $wishlistProvider, + LocaleQuantityProcessor $quantityProcessor, + ItemFactory $itemFactory, + CheckoutCart $cart, + OptionFactory $optionFactory, + Product $productHelper, + Escaper $escaper, + Data $helper, + CartHelper $cartHelper, + Validator $formKeyValidator, + ?CookieManagerInterface $cookieManager = null, + ?CookieMetadataFactory $cookieMetadataFactory = null ) { $this->wishlistProvider = $wishlistProvider; $this->quantityProcessor = $quantityProcessor; @@ -104,6 +139,9 @@ public function __construct( $this->helper = $helper; $this->cartHelper = $cartHelper; $this->formKeyValidator = $formKeyValidator; + $this->cookieManager = $cookieManager ?: ObjectManager::getInstance()->get(CookieManagerInterface::class); + $this->cookieMetadataFactory = $cookieMetadataFactory ?: + ObjectManager::getInstance()->get(CookieMetadataFactory::class); parent::__construct($context); } @@ -113,14 +151,14 @@ public function __construct( * If Product has required options - item removed from wishlist and redirect * to product view page with message about needed defined required options * - * @return \Magento\Framework\Controller\ResultInterface + * @return ResultInterface * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function execute() { - /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + /** @var Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!$this->formKeyValidator->validate($this->getRequest())) { return $resultRedirect->setPath('*/*/'); @@ -167,7 +205,7 @@ public function execute() ); try { - /** @var \Magento\Wishlist\Model\ResourceModel\Item\Option\Collection $options */ + /** @var Collection $options */ $options = $this->optionFactory->create()->getCollection()->addItemFilter([$itemId]); $item->setOptions($options->getOptionsByItem($itemId)); @@ -187,6 +225,27 @@ public function execute() $this->escaper->escapeHtml($item->getProduct()->getName()) ); $this->messageManager->addSuccessMessage($message); + + $productsToAdd = [ + [ + 'sku' => $item->getProduct()->getSku(), + 'name' => $item->getProduct()->getName(), + 'price' => $item->getProduct()->getFinalPrice(), + 'qty' => $item->getQty(), + ] + ]; + + /** @var PublicCookieMetadata $publicCookieMetadata */ + $publicCookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata() + ->setDuration(3600) + ->setPath('/') + ->setHttpOnly(false); + + $this->cookieManager->setPublicCookie( + 'add_to_cart', + \rawurlencode(\json_encode($productsToAdd)), + $publicCookieMetadata + ); } if ($this->cartHelper->getShouldRedirectToCart()) { @@ -199,7 +258,7 @@ public function execute() } } catch (ProductException $e) { $this->messageManager->addErrorMessage(__('This product(s) is out of stock.')); - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $this->messageManager->addNoticeMessage($e->getMessage()); $redirectUrl = $configureUrl; } catch (\Exception $e) { @@ -209,7 +268,7 @@ public function execute() $this->helper->calculate(); if ($this->getRequest()->isAjax()) { - /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + /** @var Json $resultJson */ $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); $resultJson->setData(['backUrl' => $redirectUrl]); return $resultJson; diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php index 093f0d8d6b8f8..e4b2e735dde24 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php @@ -5,15 +5,45 @@ */ namespace Magento\Wishlist\Test\Unit\Controller\Index; -use Magento\Wishlist\Controller\Index\Cart; +use Magento\Catalog\Helper\Product as ProductHelper; +use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Exception as ProductException; +use Magento\Checkout\Model\Cart as CheckoutCart; +use Magento\Checkout\Helper\Cart as CartHelper; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Data\Form\FormKey\Validator; +use Magento\Framework\DataObject; +use Magento\Framework\Escaper; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; +use Magento\Framework\Stdlib\CookieManagerInterface; +use Magento\Framework\UrlInterface; +use Magento\Quote\Model\Quote; +use Magento\Wishlist\Controller\Index\Cart; +use Magento\Wishlist\Controller\WishlistProviderInterface; +use Magento\Wishlist\Helper\Data; +use Magento\Wishlist\Model\Item; +use Magento\Wishlist\Model\Item\Option; +use Magento\Wishlist\Model\Item\OptionFactory; +use Magento\Wishlist\Model\ItemFactory; +use Magento\Wishlist\Model\LocaleQuantityProcessor; +use Magento\Wishlist\Model\ResourceModel\Item\Option\Collection; +use Magento\Wishlist\Model\Wishlist; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CartTest extends \PHPUnit\Framework\TestCase +class CartTest extends TestCase { /** * @var Cart @@ -21,178 +51,188 @@ class CartTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var \Magento\Framework\App\Action\Context|\PHPUnit\Framework\MockObject\MockObject + * @var Context|MockObject */ protected $contextMock; /** - * @var \Magento\Wishlist\Controller\WishlistProviderInterface|\PHPUnit\Framework\MockObject\MockObject + * @var WishlistProviderInterface|MockObject */ protected $wishlistProviderMock; /** - * @var \Magento\Wishlist\Model\LocaleQuantityProcessor|\PHPUnit\Framework\MockObject\MockObject + * @var LocaleQuantityProcessor|MockObject */ protected $quantityProcessorMock; /** - * @var \Magento\Wishlist\Model\ItemFactory|\PHPUnit\Framework\MockObject\MockObject + * @var ItemFactory|MockObject */ protected $itemFactoryMock; /** - * @var \Magento\Checkout\Model\Cart|\PHPUnit\Framework\MockObject\MockObject + * @var CheckoutCart|MockObject */ protected $checkoutCartMock; /** - * @var \Magento\Wishlist\Model\Item\OptionFactory|\PHPUnit\Framework\MockObject\MockObject + * @var OptionFactory|MockObject */ protected $optionFactoryMock; /** - * @var \Magento\Catalog\Helper\Product|\PHPUnit\Framework\MockObject\MockObject + * @var ProductHelper|MockObject */ protected $productHelperMock; /** - * @var \Magento\Framework\Escaper|\PHPUnit\Framework\MockObject\MockObject + * @var Escaper|MockObject */ protected $escaperMock; /** - * @var \Magento\Wishlist\Helper\Data|\PHPUnit\Framework\MockObject\MockObject + * @var Data|MockObject */ protected $helperMock; /** - * @var \Magento\Framework\App\RequestInterface|\PHPUnit\Framework\MockObject\MockObject + * @var RequestInterface|MockObject */ protected $requestMock; /** - * @var \Magento\Framework\App\Response\RedirectInterface|\PHPUnit\Framework\MockObject\MockObject + * @var RedirectInterface|MockObject */ protected $redirectMock; /** - * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit\Framework\MockObject\MockObject + * @var ObjectManagerInterface|MockObject */ protected $objectManagerMock; /** - * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit\Framework\MockObject\MockObject + * @var ManagerInterface|MockObject */ protected $messageManagerMock; /** - * @var \Magento\Framework\UrlInterface|\PHPUnit\Framework\MockObject\MockObject + * @var UrlInterface|MockObject */ protected $urlMock; /** - * @var \Magento\Checkout\Helper\Cart|\PHPUnit\Framework\MockObject\MockObject + * @var CartHelper|MockObject */ protected $cartHelperMock; /** - * @var \Magento\Framework\Controller\ResultFactory|\PHPUnit\Framework\MockObject\MockObject + * @var ResultFactory|MockObject */ protected $resultFactoryMock; /** - * @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit\Framework\MockObject\MockObject + * @var Redirect|MockObject */ protected $resultRedirectMock; /** - * @var \Magento\Framework\Controller\Result\Json|\PHPUnit\Framework\MockObject\MockObject + * @var Json|MockObject */ protected $resultJsonMock; /** - * @var \Magento\Framework\Data\Form\FormKey\Validator|\PHPUnit\Framework\MockObject\MockObject + * @var Validator|MockObject */ protected $formKeyValidator; + /** + * @var CookieManagerInterface|MockObject + */ + private $cookieManagerMock; + + /** + * @var CookieMetadataFactory|MockObject + */ + private $cookieMetadataFactoryMock; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function setUp() { $this->wishlistProviderMock = $this->getMockBuilder( - \Magento\Wishlist\Controller\WishlistProviderInterface::class + WishlistProviderInterface::class )->disableOriginalConstructor() ->setMethods(['getWishlist']) ->getMockForAbstractClass(); - $this->quantityProcessorMock = $this->getMockBuilder(\Magento\Wishlist\Model\LocaleQuantityProcessor::class) + $this->quantityProcessorMock = $this->getMockBuilder(LocaleQuantityProcessor::class) ->disableOriginalConstructor() ->getMock(); - $this->itemFactoryMock = $this->getMockBuilder(\Magento\Wishlist\Model\ItemFactory::class) + $this->itemFactoryMock = $this->getMockBuilder(ItemFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->checkoutCartMock = $this->getMockBuilder(\Magento\Checkout\Model\Cart::class) + $this->checkoutCartMock = $this->getMockBuilder(CheckoutCart::class) ->disableOriginalConstructor() ->setMethods(['save', 'getQuote', 'getShouldRedirectToCart', 'getCartUrl']) ->getMock(); - $this->optionFactoryMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item\OptionFactory::class) + $this->optionFactoryMock = $this->getMockBuilder(OptionFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->productHelperMock = $this->getMockBuilder(\Magento\Catalog\Helper\Product::class) + $this->productHelperMock = $this->getMockBuilder(ProductHelper::class) ->disableOriginalConstructor() ->getMock(); - $this->escaperMock = $this->getMockBuilder(\Magento\Framework\Escaper::class) + $this->escaperMock = $this->getMockBuilder(Escaper::class) ->disableOriginalConstructor() ->getMock(); - $this->helperMock = $this->getMockBuilder(\Magento\Wishlist\Helper\Data::class) + $this->helperMock = $this->getMockBuilder(Data::class) ->disableOriginalConstructor() ->getMock(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + $this->requestMock = $this->getMockBuilder(RequestInterface::class) ->disableOriginalConstructor() ->setMethods(['getParams', 'getParam', 'isAjax', 'getPostValue']) ->getMockForAbstractClass(); - $this->redirectMock = $this->getMockBuilder(\Magento\Framework\App\Response\RedirectInterface::class) + $this->redirectMock = $this->getMockBuilder(RedirectInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) ->disableOriginalConstructor() ->setMethods(['addSuccessMessage']) ->getMockForAbstractClass(); - $this->urlMock = $this->getMockBuilder(\Magento\Framework\UrlInterface::class) + $this->urlMock = $this->getMockBuilder(UrlInterface::class) ->disableOriginalConstructor() ->setMethods(['getUrl']) ->getMockForAbstractClass(); - $this->cartHelperMock = $this->getMockBuilder(\Magento\Checkout\Helper\Cart::class) + $this->cartHelperMock = $this->getMockBuilder(CartHelper::class) ->disableOriginalConstructor() ->getMock(); - $this->resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->resultRedirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) + $this->resultRedirectMock = $this->getMockBuilder(Redirect::class) ->disableOriginalConstructor() ->getMock(); - $this->resultJsonMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class) + $this->resultJsonMock = $this->getMockBuilder(Json::class) ->disableOriginalConstructor() ->getMock(); - $this->contextMock = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) + $this->contextMock = $this->getMockBuilder(Context::class) ->disableOriginalConstructor() ->getMock(); $this->contextMock->expects($this->any()) @@ -222,10 +262,26 @@ protected function setUp() ] ); - $this->formKeyValidator = $this->getMockBuilder(\Magento\Framework\Data\Form\FormKey\Validator::class) + $this->formKeyValidator = $this->getMockBuilder(Validator::class) ->disableOriginalConstructor() ->getMock(); + $this->cookieManagerMock = $this->createMock(CookieManagerInterface::class); + + $this->cookieMetadataFactoryMock = $this->getMockBuilder(CookieMetadataFactory::class) + ->disableOriginalConstructor() + ->setMethods(['createPublicCookieMetadata', 'setDuration', 'setPath', 'setHttpOnly']) + ->getMock(); + $this->cookieMetadataFactoryMock->expects($this->any()) + ->method('createPublicCookieMetadata') + ->willReturnSelf(); + $this->cookieMetadataFactoryMock->expects($this->any()) + ->method('setDuration') + ->willReturnSelf(); + $this->cookieMetadataFactoryMock->expects($this->any()) + ->method('setPath') + ->willReturnSelf(); + $this->model = new Cart( $this->contextMock, $this->wishlistProviderMock, @@ -237,7 +293,9 @@ protected function setUp() $this->escaperMock, $this->helperMock, $this->cartHelperMock, - $this->formKeyValidator + $this->formKeyValidator, + $this->cookieManagerMock, + $this->cookieMetadataFactoryMock ); } @@ -265,7 +323,7 @@ public function testExecuteWithNoItem() ->with($this->requestMock) ->willReturn(true); - $itemMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + $itemMock = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() ->getMock(); @@ -302,7 +360,7 @@ public function testExecuteWithNoWishlist() ->with($this->requestMock) ->willReturn(true); - $itemMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + $itemMock = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() ->setMethods(['load', 'getId', 'getWishlistId']) ->getMock(); @@ -390,7 +448,7 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) $params = ['item' => $itemId, 'qty' => $qty]; $refererUrl = 'referer_url'; - $itemMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + $itemMock = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() ->setMethods( [ @@ -427,7 +485,7 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) ->method('getWishlistId') ->willReturn($wishlistId); - $wishlistMock = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class) + $wishlistMock = $this->getMockBuilder(Wishlist::class) ->disableOriginalConstructor() ->getMock(); @@ -465,7 +523,7 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) ->with('*/*/configure/', ['id' => $itemId, 'product_id' => $productId]) ->willReturn($configureUrl); - $optionMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item\Option::class) + $optionMock = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() ->getMock(); @@ -473,7 +531,7 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) ->method('create') ->willReturn($optionMock); - $optionsMock = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Item\Option\Collection::class) + $optionsMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); $optionMock->expects($this->once()) @@ -501,7 +559,7 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) ->method('isAjax') ->willReturn($isAjax); - $buyRequestMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) + $buyRequestMock = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() ->getMock(); @@ -527,7 +585,7 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) ->method('save') ->willReturnSelf(); - $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class) + $quoteMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->setMethods(['getHasError', 'collectTotals']) ->getMock(); @@ -548,15 +606,15 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) ->method('getHasError') ->willReturn(false); - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $productMock = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->getMock(); - $itemMock->expects($this->once()) + $itemMock->expects($this->atLeastOnce()) ->method('getProduct') ->willReturn($productMock); - $productMock->expects($this->once()) + $productMock->expects($this->atLeastOnce()) ->method('getName') ->willReturn($productName); @@ -567,7 +625,7 @@ protected function prepareExecuteWithQuantityArray($isAjax = false) $this->messageManagerMock->expects($this->once()) ->method('addSuccessMessage') - ->with('You added ' . $productName . ' to your shopping cart.', null) + ->with('You added ' . $productName . ' to your shopping cart.', null) ->willReturnSelf(); $this->cartHelperMock->expects($this->once()) @@ -604,7 +662,7 @@ public function testExecuteWithoutQuantityArrayAndOutOfStock() ->with($this->requestMock) ->willReturn(true); - $itemMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + $itemMock = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() ->setMethods( [ @@ -641,7 +699,7 @@ public function testExecuteWithoutQuantityArrayAndOutOfStock() ->method('getWishlistId') ->willReturn($wishlistId); - $wishlistMock = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class) + $wishlistMock = $this->getMockBuilder(Wishlist::class) ->disableOriginalConstructor() ->getMock(); @@ -679,7 +737,7 @@ public function testExecuteWithoutQuantityArrayAndOutOfStock() ->with('*/*/configure/', ['id' => $itemId, 'product_id' => $productId]) ->willReturn($configureUrl); - $optionMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item\Option::class) + $optionMock = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() ->getMock(); @@ -687,7 +745,7 @@ public function testExecuteWithoutQuantityArrayAndOutOfStock() ->method('create') ->willReturn($optionMock); - $optionsMock = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Item\Option\Collection::class) + $optionsMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); $optionMock->expects($this->once()) @@ -712,7 +770,7 @@ public function testExecuteWithoutQuantityArrayAndOutOfStock() ->method('getParams') ->willReturn($params); - $buyRequestMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) + $buyRequestMock = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() ->getMock(); @@ -770,7 +828,7 @@ public function testExecuteWithoutQuantityArrayAndConfigurable() ->with($this->requestMock) ->willReturn(true); - $itemMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + $itemMock = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() ->setMethods( [ @@ -807,7 +865,7 @@ public function testExecuteWithoutQuantityArrayAndConfigurable() ->method('getWishlistId') ->willReturn($wishlistId); - $wishlistMock = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class) + $wishlistMock = $this->getMockBuilder(Wishlist::class) ->disableOriginalConstructor() ->getMock(); @@ -845,7 +903,7 @@ public function testExecuteWithoutQuantityArrayAndConfigurable() ->with('*/*/configure/', ['id' => $itemId, 'product_id' => $productId]) ->willReturn($configureUrl); - $optionMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item\Option::class) + $optionMock = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() ->getMock(); @@ -853,7 +911,7 @@ public function testExecuteWithoutQuantityArrayAndConfigurable() ->method('create') ->willReturn($optionMock); - $optionsMock = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Item\Option\Collection::class) + $optionsMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); $optionMock->expects($this->once()) @@ -878,7 +936,7 @@ public function testExecuteWithoutQuantityArrayAndConfigurable() ->method('getParams') ->willReturn($params); - $buyRequestMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) + $buyRequestMock = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() ->getMock(); @@ -898,7 +956,7 @@ public function testExecuteWithoutQuantityArrayAndConfigurable() $itemMock->expects($this->once()) ->method('addToCart') ->with($this->checkoutCartMock, true) - ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('message'))); + ->willThrowException(new LocalizedException(__('message'))); $this->messageManagerMock->expects($this->once()) ->method('addNoticeMessage') @@ -937,7 +995,7 @@ public function testExecuteWithEditQuantity() ->with($this->requestMock) ->willReturn(true); - $itemMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + $itemMock = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() ->setMethods( [ @@ -974,7 +1032,7 @@ public function testExecuteWithEditQuantity() ->method('getWishlistId') ->willReturn($wishlistId); - $wishlistMock = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class) + $wishlistMock = $this->getMockBuilder(Wishlist::class) ->disableOriginalConstructor() ->getMock(); @@ -1017,7 +1075,7 @@ public function testExecuteWithEditQuantity() ->with('*/*/configure/', ['id' => $itemId, 'product_id' => $productId]) ->willReturn($configureUrl); - $optionMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item\Option::class) + $optionMock = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() ->getMock(); @@ -1025,7 +1083,7 @@ public function testExecuteWithEditQuantity() ->method('create') ->willReturn($optionMock); - $optionsMock = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Item\Option\Collection::class) + $optionsMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); $optionMock->expects($this->once()) @@ -1050,7 +1108,7 @@ public function testExecuteWithEditQuantity() ->method('getParams') ->willReturn($params); - $buyRequestMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) + $buyRequestMock = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() ->getMock(); @@ -1070,7 +1128,7 @@ public function testExecuteWithEditQuantity() $itemMock->expects($this->once()) ->method('addToCart') ->with($this->checkoutCartMock, true) - ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('message'))); + ->willThrowException(new LocalizedException(__('message'))); $this->messageManagerMock->expects($this->once()) ->method('addNoticeMessage') diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less index 84d9cb1530893..2b82330c0049e 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less @@ -52,6 +52,7 @@ } p { + min-height: 72px; text-align: center; } } diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less index 9ed7e3a1ba839..3ca7e7d161064 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less @@ -246,6 +246,11 @@ margin-bottom: @indent__xs; } + .message { + margin-bottom: 0; + margin-top: 10px; + } + .product { > .product-item-photo, > .product-image-container { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less index a97cc041b1c42..f8311a4fd0afd 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less @@ -264,6 +264,11 @@ margin-bottom: @indent__xs; } + .message { + margin-bottom: 0; + margin-top: 10px; + } + .product-item-name { font-weight: @font-weight__regular; margin: 0 0 @indent__s; diff --git a/app/etc/di.xml b/app/etc/di.xml index dbc4a1832adf6..0133288a17ac5 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1787,8 +1787,6 @@ <type name="Magento\Framework\Cache\LockGuardedCacheLoader"> <arguments> <argument name="locker" xsi:type="object">Magento\Framework\Lock\Backend\Cache</argument> - <argument name="lockTimeout" xsi:type="number">10000</argument> - <argument name="delayTimeout" xsi:type="number">20</argument> </arguments> </type> <preference for="Magento\Framework\HTTP\AsyncClientInterface" type="Magento\Framework\HTTP\AsyncClient\GuzzleAsyncClient" /> diff --git a/composer.json b/composer.json index 5223fa2a0aca4..6d63a5328cec2 100644 --- a/composer.json +++ b/composer.json @@ -193,8 +193,20 @@ "magento/module-instant-purchase": "*", "magento/module-integration": "*", "magento/module-layered-navigation": "*", + "magento/module-login-as-customer": "*", + "magento/module-login-as-customer-api": "*", + "magento/module-login-as-customer-log": "*", + "magento/module-login-as-customer-page-cache": "*", + "magento/module-login-as-customer-sales": "*", + "magento/module-login-as-customer-ui": "*", + "magento/module-login-as-customer-webapi": "*", + "magento/module-media-content": "*", + "magento/module-media-content-api": "*", + "magento/module-media-content-catalog": "*", + "magento/module-media-content-cms": "*", "magento/module-media-gallery": "*", "magento/module-media-gallery-api": "*", + "magento/module-media-gallery-catalog": "*", "magento/module-media-storage": "*", "magento/module-message-queue": "*", "magento/module-msrp": "*", diff --git a/composer.lock b/composer.lock index 37849c7c21a29..8a41c7b609e70 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": "1f11bed01d000a3d3eeda8b462e29f75", + "content-hash": "02c03e264c9040e0e229bc22fbdcec61", "packages": [ { "name": "braintree/braintree_php", @@ -201,16 +201,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.6", + "version": "1.2.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e" + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e", - "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", "shasum": "" }, "require": { @@ -253,20 +253,20 @@ "ssl", "tls" ], - "time": "2020-01-13T10:02:55+00:00" + "time": "2020-04-08T08:27:21+00:00" }, { "name": "composer/composer", - "version": "1.10.1", + "version": "1.10.5", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011" + "reference": "7a4d5b6aa30d2118af27c04f5e897b57156ccfa9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/b912a45da3e2b22f5cb5a23e441b697a295ba011", - "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011", + "url": "https://api.github.com/repos/composer/composer/zipball/7a4d5b6aa30d2118af27c04f5e897b57156ccfa9", + "reference": "7a4d5b6aa30d2118af27c04f5e897b57156ccfa9", "shasum": "" }, "require": { @@ -333,7 +333,7 @@ "dependency", "package" ], - "time": "2020-03-13T19:34:27+00:00" + "time": "2020-04-10T09:44:22+00:00" }, { "name": "composer/semver", @@ -698,23 +698,24 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.5.2", + "version": "6.5.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" + "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", - "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e", + "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.0", "guzzlehttp/psr7": "^1.6.1", - "php": ">=5.5" + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.11" }, "require-dev": { "ext-curl": "*", @@ -722,7 +723,6 @@ "psr/log": "^1.1" }, "suggest": { - "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", @@ -761,7 +761,7 @@ "rest", "web service" ], - "time": "2019-12-23T11:57:10+00:00" + "time": "2020-04-18T10:38:46+00:00" }, { "name": "guzzlehttp/promises", @@ -1243,16 +1243,16 @@ }, { "name": "laminas/laminas-db", - "version": "2.11.2", + "version": "2.11.3", "source": { "type": "git", "url": "https://github.com/laminas/laminas-db.git", - "reference": "76f9527da996c2fef32ef1f3a939e18ca5e9d962" + "reference": "6c4238918b9204db1eb8cafae2c1940d40f4c007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-db/zipball/76f9527da996c2fef32ef1f3a939e18ca5e9d962", - "reference": "76f9527da996c2fef32ef1f3a939e18ca5e9d962", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/6c4238918b9204db1eb8cafae2c1940d40f4c007", + "reference": "6c4238918b9204db1eb8cafae2c1940d40f4c007", "shasum": "" }, "require": { @@ -1261,7 +1261,7 @@ "php": "^5.6 || ^7.0" }, "replace": { - "zendframework/zend-db": "self.version" + "zendframework/zend-db": "^2.11.0" }, "require-dev": { "laminas/laminas-coding-standard": "~1.0.0", @@ -1301,7 +1301,7 @@ "db", "laminas" ], - "time": "2020-01-14T13:07:26+00:00" + "time": "2020-03-29T12:08:51+00:00" }, { "name": "laminas/laminas-dependency-plugin", @@ -1402,16 +1402,16 @@ }, { "name": "laminas/laminas-diactoros", - "version": "1.8.7p1", + "version": "1.8.7p2", "source": { "type": "git", "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "56a9aca1f89231763d24d2ae13531b97fa5f4029" + "reference": "6991c1af7c8d2c8efee81b22ba97024781824aaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/56a9aca1f89231763d24d2ae13531b97fa5f4029", - "reference": "56a9aca1f89231763d24d2ae13531b97fa5f4029", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/6991c1af7c8d2c8efee81b22ba97024781824aaa", + "reference": "6991c1af7c8d2c8efee81b22ba97024781824aaa", "shasum": "" }, "require": { @@ -1423,7 +1423,7 @@ "psr/http-message-implementation": "1.0" }, "replace": { - "zendframework/zend-diactoros": "self.version" + "zendframework/zend-diactoros": "~1.8.7.0" }, "require-dev": { "ext-dom": "*", @@ -1473,7 +1473,7 @@ "psr", "psr-7" ], - "time": "2020-01-07T19:25:17+00:00" + "time": "2020-03-23T15:28:28+00:00" }, { "name": "laminas/laminas-escaper", @@ -1584,16 +1584,16 @@ }, { "name": "laminas/laminas-feed", - "version": "2.12.1", + "version": "2.12.2", "source": { "type": "git", "url": "https://github.com/laminas/laminas-feed.git", - "reference": "c9356994eb80d0f6b46d7e12ba048d450bf0cd72" + "reference": "8a193ac96ebcb3e16b6ee754ac2a889eefacb654" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/c9356994eb80d0f6b46d7e12ba048d450bf0cd72", - "reference": "c9356994eb80d0f6b46d7e12ba048d450bf0cd72", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/8a193ac96ebcb3e16b6ee754ac2a889eefacb654", + "reference": "8a193ac96ebcb3e16b6ee754ac2a889eefacb654", "shasum": "" }, "require": { @@ -1605,7 +1605,7 @@ "php": "^5.6 || ^7.0" }, "replace": { - "zendframework/zend-feed": "self.version" + "zendframework/zend-feed": "^2.12.0" }, "require-dev": { "laminas/laminas-cache": "^2.7.2", @@ -1647,20 +1647,20 @@ "feed", "laminas" ], - "time": "2020-03-23T10:40:31+00:00" + "time": "2020-03-29T12:36:29+00:00" }, { "name": "laminas/laminas-filter", - "version": "2.9.3", + "version": "2.9.4", "source": { "type": "git", "url": "https://github.com/laminas/laminas-filter.git", - "reference": "52b5cdbef8902280996e687e7352a648a8e22f31" + "reference": "3c4476e772a062cef7531c6793377ae585d89c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/52b5cdbef8902280996e687e7352a648a8e22f31", - "reference": "52b5cdbef8902280996e687e7352a648a8e22f31", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/3c4476e772a062cef7531c6793377ae585d89c82", + "reference": "3c4476e772a062cef7531c6793377ae585d89c82", "shasum": "" }, "require": { @@ -1672,7 +1672,7 @@ "laminas/laminas-validator": "<2.10.1" }, "replace": { - "zendframework/zend-filter": "self.version" + "zendframework/zend-filter": "^2.9.2" }, "require-dev": { "laminas/laminas-coding-standard": "~1.0.0", @@ -1716,20 +1716,20 @@ "filter", "laminas" ], - "time": "2020-01-07T20:43:53+00:00" + "time": "2020-03-29T12:41:29+00:00" }, { "name": "laminas/laminas-form", - "version": "2.14.4", + "version": "2.14.5", "source": { "type": "git", "url": "https://github.com/laminas/laminas-form.git", - "reference": "8b985f74bfe32910edb4ba9503877c4310228cd2" + "reference": "3e22e09751cf6ae031be87a44e092e7925ce5b7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-form/zipball/8b985f74bfe32910edb4ba9503877c4310228cd2", - "reference": "8b985f74bfe32910edb4ba9503877c4310228cd2", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/3e22e09751cf6ae031be87a44e092e7925ce5b7b", + "reference": "3e22e09751cf6ae031be87a44e092e7925ce5b7b", "shasum": "" }, "require": { @@ -1740,7 +1740,7 @@ "php": "^5.6 || ^7.0" }, "replace": { - "zendframework/zend-form": "self.version" + "zendframework/zend-form": "^2.14.3" }, "require-dev": { "doctrine/annotations": "~1.0", @@ -1798,7 +1798,7 @@ "form", "laminas" ], - "time": "2020-03-18T22:38:54+00:00" + "time": "2020-03-29T12:46:16+00:00" }, { "name": "laminas/laminas-http", @@ -1924,16 +1924,16 @@ }, { "name": "laminas/laminas-i18n", - "version": "2.10.2", + "version": "2.10.3", "source": { "type": "git", "url": "https://github.com/laminas/laminas-i18n.git", - "reference": "9699c98d97d2f519def3da8d4128dfe6e6ad11bf" + "reference": "94ff957a1366f5be94f3d3a9b89b50386649e3ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/9699c98d97d2f519def3da8d4128dfe6e6ad11bf", - "reference": "9699c98d97d2f519def3da8d4128dfe6e6ad11bf", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/94ff957a1366f5be94f3d3a9b89b50386649e3ae", + "reference": "94ff957a1366f5be94f3d3a9b89b50386649e3ae", "shasum": "" }, "require": { @@ -1946,7 +1946,7 @@ "phpspec/prophecy": "<1.9.0" }, "replace": { - "zendframework/zend-i18n": "self.version" + "zendframework/zend-i18n": "^2.10.1" }, "require-dev": { "laminas/laminas-cache": "^2.6.1", @@ -1995,7 +1995,7 @@ "i18n", "laminas" ], - "time": "2020-03-20T11:57:14+00:00" + "time": "2020-03-29T12:51:08+00:00" }, { "name": "laminas/laminas-inputfilter", @@ -2362,16 +2362,16 @@ }, { "name": "laminas/laminas-mime", - "version": "2.7.3", + "version": "2.7.4", "source": { "type": "git", "url": "https://github.com/laminas/laminas-mime.git", - "reference": "e844abb02e868fae154207929190292ad25057cc" + "reference": "e45a7d856bf7b4a7b5bd00d6371f9961dc233add" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/e844abb02e868fae154207929190292ad25057cc", - "reference": "e844abb02e868fae154207929190292ad25057cc", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/e45a7d856bf7b4a7b5bd00d6371f9961dc233add", + "reference": "e45a7d856bf7b4a7b5bd00d6371f9961dc233add", "shasum": "" }, "require": { @@ -2380,7 +2380,7 @@ "php": "^5.6 || ^7.0" }, "replace": { - "zendframework/zend-mime": "self.version" + "zendframework/zend-mime": "^2.7.2" }, "require-dev": { "laminas/laminas-coding-standard": "~1.0.0", @@ -2412,7 +2412,7 @@ "laminas", "mime" ], - "time": "2020-03-06T08:38:03+00:00" + "time": "2020-03-29T13:12:07+00:00" }, { "name": "laminas/laminas-modulemanager", @@ -2797,16 +2797,16 @@ }, { "name": "laminas/laminas-session", - "version": "2.9.2", + "version": "2.9.3", "source": { "type": "git", "url": "https://github.com/laminas/laminas-session.git", - "reference": "fdba34c1b257235dba2fff6ed4df1844390f85f6" + "reference": "519e8966146536cd97c1cc3d59a21b095fb814d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-session/zipball/fdba34c1b257235dba2fff6ed4df1844390f85f6", - "reference": "fdba34c1b257235dba2fff6ed4df1844390f85f6", + "url": "https://api.github.com/repos/laminas/laminas-session/zipball/519e8966146536cd97c1cc3d59a21b095fb814d7", + "reference": "519e8966146536cd97c1cc3d59a21b095fb814d7", "shasum": "" }, "require": { @@ -2816,7 +2816,7 @@ "php": "^5.6 || ^7.0" }, "replace": { - "zendframework/zend-session": "self.version" + "zendframework/zend-session": "^2.9.1" }, "require-dev": { "container-interop/container-interop": "^1.1", @@ -2864,7 +2864,7 @@ "laminas", "session" ], - "time": "2020-03-06T09:44:45+00:00" + "time": "2020-03-29T13:26:04+00:00" }, { "name": "laminas/laminas-soap", @@ -3078,16 +3078,16 @@ }, { "name": "laminas/laminas-validator", - "version": "2.13.2", + "version": "2.13.4", "source": { "type": "git", "url": "https://github.com/laminas/laminas-validator.git", - "reference": "e7bf6a2eedc0508ebde0ebc66662efeb0abbcb2c" + "reference": "93593684e70b8ed1e870cacd34ca32b0c0ace185" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/e7bf6a2eedc0508ebde0ebc66662efeb0abbcb2c", - "reference": "e7bf6a2eedc0508ebde0ebc66662efeb0abbcb2c", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/93593684e70b8ed1e870cacd34ca32b0c0ace185", + "reference": "93593684e70b8ed1e870cacd34ca32b0c0ace185", "shasum": "" }, "require": { @@ -3097,7 +3097,7 @@ "php": "^7.1" }, "replace": { - "zendframework/zend-validator": "self.version" + "zendframework/zend-validator": "^2.13.0" }, "require-dev": { "laminas/laminas-cache": "^2.6.1", @@ -3153,7 +3153,7 @@ "laminas", "validator" ], - "time": "2020-03-16T11:38:27+00:00" + "time": "2020-03-31T18:57:01+00:00" }, { "name": "laminas/laminas-view", @@ -3248,16 +3248,16 @@ }, { "name": "laminas/laminas-zendframework-bridge", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/laminas/laminas-zendframework-bridge.git", - "reference": "faf68f6109ceeff24241226033ab59640c7eb63b" + "reference": "bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/faf68f6109ceeff24241226033ab59640c7eb63b", - "reference": "faf68f6109ceeff24241226033ab59640c7eb63b", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9", + "reference": "bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9", "shasum": "" }, "require": { @@ -3296,7 +3296,7 @@ "laminas", "zf" ], - "time": "2020-03-26T16:07:12+00:00" + "time": "2020-04-03T16:01:00+00:00" }, { "name": "magento/composer", @@ -3863,16 +3863,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.26", + "version": "2.0.27", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "09655fcc1f8bab65727be036b28f6f20311c126c" + "reference": "34620af4df7d1988d8f0d7e91f6c8a3bf931d8dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/09655fcc1f8bab65727be036b28f6f20311c126c", - "reference": "09655fcc1f8bab65727be036b28f6f20311c126c", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/34620af4df7d1988d8f0d7e91f6c8a3bf931d8dc", + "reference": "34620af4df7d1988d8f0d7e91f6c8a3bf931d8dc", "shasum": "" }, "require": { @@ -3951,7 +3951,7 @@ "x.509", "x509" ], - "time": "2020-03-13T04:15:39+00:00" + "time": "2020-04-04T23:17:33+00:00" }, { "name": "psr/container", @@ -4362,16 +4362,16 @@ }, { "name": "symfony/console", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9" + "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", - "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", + "url": "https://api.github.com/repos/symfony/console/zipball/10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", + "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", "shasum": "" }, "require": { @@ -4434,20 +4434,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2020-02-24T13:10:00+00:00" + "time": "2020-03-30T11:41:10+00:00" }, { "name": "symfony/css-selector", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22" + "reference": "afc26133a6fbdd4f8842e38893e0ee4685c7c94b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22", - "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/afc26133a6fbdd4f8842e38893e0ee4685c7c94b", + "reference": "afc26133a6fbdd4f8842e38893e0ee4685c7c94b", "shasum": "" }, "require": { @@ -4487,20 +4487,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2020-02-04T09:01:01+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d" + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4ad8e149799d3128621a3a1f70e92b9897a8930d", - "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abc8e3618bfdb55e44c8c6a00abd333f831bbfed", + "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed", "shasum": "" }, "require": { @@ -4557,7 +4557,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2020-02-04T09:32:40+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -4619,16 +4619,16 @@ }, { "name": "symfony/filesystem", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" + "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", - "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fe297193bf2e6866ed900ed2d5869362768df6a7", + "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7", "shasum": "" }, "require": { @@ -4665,20 +4665,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2020-01-21T08:20:44+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/finder", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357" + "reference": "5729f943f9854c5781984ed4907bbb817735776b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ea69c129aed9fdeca781d4b77eb20b62cf5d5357", - "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357", + "url": "https://api.github.com/repos/symfony/finder/zipball/5729f943f9854c5781984ed4907bbb817735776b", + "reference": "5729f943f9854c5781984ed4907bbb817735776b", "shasum": "" }, "require": { @@ -4714,7 +4714,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2020-02-14T07:42:58+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4774,6 +4774,68 @@ ], "time": "2020-02-27T09:26:54+00:00" }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2020-03-09T19:04:49+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.15.0", @@ -4833,6 +4895,61 @@ ], "time": "2020-03-09T19:04:49+00:00" }, + { + "name": "symfony/polyfill-php72", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "37b0976c78b94856543260ce09b460a7bc852747" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", + "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-02-27T09:26:54+00:00" + }, { "name": "symfony/polyfill-php73", "version": "v1.15.0", @@ -4893,16 +5010,16 @@ }, { "name": "symfony/process", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7" + "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/bf9166bac906c9e69fb7a11d94875e7ced97bcd7", - "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7", + "url": "https://api.github.com/repos/symfony/process/zipball/3e40e87a20eaf83a1db825e1fa5097ae89042db3", + "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3", "shasum": "" }, "require": { @@ -4938,7 +5055,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2020-02-07T20:06:44+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/service-contracts", @@ -5414,16 +5531,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.133.45", + "version": "3.134.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "928a23e2ee7e195a66f93d0758895e26958c3b7d" + "reference": "8a9b598a0ede2165be5988899dcebead6fcc4d41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/928a23e2ee7e195a66f93d0758895e26958c3b7d", - "reference": "928a23e2ee7e195a66f93d0758895e26958c3b7d", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8a9b598a0ede2165be5988899dcebead6fcc4d41", + "reference": "8a9b598a0ede2165be5988899dcebead6fcc4d41", "shasum": "" }, "require": { @@ -5494,7 +5611,7 @@ "s3", "sdk" ], - "time": "2020-03-26T18:12:15+00:00" + "time": "2020-04-17T18:11:57+00:00" }, { "name": "behat/gherkin", @@ -5969,20 +6086,21 @@ }, { "name": "doctrine/annotations", - "version": "v1.8.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc" + "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc", - "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5eb79f3dbdffed6544e1fc287572c0f462bd29bb", + "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb", "shasum": "" }, "require": { "doctrine/lexer": "1.*", + "ext-tokenizer": "*", "php": "^7.1" }, "require-dev": { @@ -5992,7 +6110,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.9.x-dev" } }, "autoload": { @@ -6033,7 +6151,7 @@ "docblock", "parser" ], - "time": "2019-10-01T18:55:10+00:00" + "time": "2020-04-02T12:33:25+00:00" }, { "name": "doctrine/cache", @@ -6567,16 +6685,16 @@ }, { "name": "league/flysystem", - "version": "1.0.66", + "version": "1.0.67", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21" + "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/021569195e15f8209b1c4bebb78bd66aa4f08c21", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5b1f36c75c4bdde981294c2a0ebdb437ee6f275e", + "reference": "5b1f36c75c4bdde981294c2a0ebdb437ee6f275e", "shasum": "" }, "require": { @@ -6647,7 +6765,7 @@ "sftp", "storage" ], - "time": "2020-03-17T18:58:12+00:00" + "time": "2020-04-16T13:21:26+00:00" }, { "name": "lusitanian/oauth", @@ -8970,16 +9088,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "090ce406505149d6852a7c03b0346dec3b8cf612" + "reference": "e4b0dc1b100bf75b5717c5b451397f230a618a42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/090ce406505149d6852a7c03b0346dec3b8cf612", - "reference": "090ce406505149d6852a7c03b0346dec3b8cf612", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/e4b0dc1b100bf75b5717c5b451397f230a618a42", + "reference": "e4b0dc1b100bf75b5717c5b451397f230a618a42", "shasum": "" }, "require": { @@ -9025,20 +9143,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2020-02-23T10:00:59+00:00" + "time": "2020-03-28T10:15:50+00:00" }, { "name": "symfony/config", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9" + "reference": "3f4a3de1af498ed0ea653d4dc2317794144e6ca4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", - "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", + "url": "https://api.github.com/repos/symfony/config/zipball/3f4a3de1af498ed0ea653d4dc2317794144e6ca4", + "reference": "3f4a3de1af498ed0ea653d4dc2317794144e6ca4", "shasum": "" }, "require": { @@ -9089,20 +9207,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2020-02-04T09:32:40+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342" + "reference": "755b18859be26b90f4bf63753432d3387458bf31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ebb2e882e8c9e2eb990aa61ddcd389848466e342", - "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/755b18859be26b90f4bf63753432d3387458bf31", + "reference": "755b18859be26b90f4bf63753432d3387458bf31", "shasum": "" }, "require": { @@ -9162,20 +9280,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2020-02-29T09:50:10+00:00" + "time": "2020-03-30T10:09:30+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929" + "reference": "4d0fb3374324071ecdd94898367a3fa4b5563162" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/11dcf08f12f29981bf770f097a5d64d65bce5929", - "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4d0fb3374324071ecdd94898367a3fa4b5563162", + "reference": "4d0fb3374324071ecdd94898367a3fa4b5563162", "shasum": "" }, "require": { @@ -9223,20 +9341,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2020-02-29T10:05:28+00:00" + "time": "2020-03-29T19:12:22+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.0.5", + "version": "v5.0.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335" + "reference": "26fb006a2c7b6cdd23d52157b05f8414ffa417b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6f9c2ba72f4295d7ce6cf9f79dbb18036291d335", - "reference": "6f9c2ba72f4295d7ce6cf9f79dbb18036291d335", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/26fb006a2c7b6cdd23d52157b05f8414ffa417b6", + "reference": "26fb006a2c7b6cdd23d52157b05f8414ffa417b6", "shasum": "" }, "require": { @@ -9278,20 +9396,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2020-02-14T07:43:07+00:00" + "time": "2020-03-30T14:14:32+00:00" }, { "name": "symfony/mime", - "version": "v5.0.5", + "version": "v5.0.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c" + "reference": "481b7d6da88922fb1e0d86a943987722b08f3955" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/9b3e5b5e58c56bbd76628c952d2b78556d305f3c", - "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c", + "url": "https://api.github.com/repos/symfony/mime/zipball/481b7d6da88922fb1e0d86a943987722b08f3955", + "reference": "481b7d6da88922fb1e0d86a943987722b08f3955", "shasum": "" }, "require": { @@ -9340,20 +9458,20 @@ "mime", "mime-type" ], - "time": "2020-02-04T09:41:09+00:00" + "time": "2020-03-27T16:56:45+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0" + "reference": "9072131b5e6e21203db3249c7db26b52897bc73e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0", - "reference": "9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9072131b5e6e21203db3249c7db26b52897bc73e", + "reference": "9072131b5e6e21203db3249c7db26b52897bc73e", "shasum": "" }, "require": { @@ -9394,69 +9512,7 @@ "configuration", "options" ], - "time": "2020-01-04T13:00:46+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.15-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/polyfill-php70", @@ -9517,73 +9573,18 @@ ], "time": "2020-02-27T09:26:54+00:00" }, - { - "name": "symfony/polyfill-php72", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.15-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2020-02-27T09:26:54+00:00" - }, { "name": "symfony/stopwatch", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "abc08d7c48987829bac301347faa10f7e8bbf4fb" + "reference": "e0324d3560e4128270e3f08617480d9233d81cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/abc08d7c48987829bac301347faa10f7e8bbf4fb", - "reference": "abc08d7c48987829bac301347faa10f7e8bbf4fb", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e0324d3560e4128270e3f08617480d9233d81cfc", + "reference": "e0324d3560e4128270e3f08617480d9233d81cfc", "shasum": "" }, "require": { @@ -9620,20 +9621,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.5", + "version": "v4.4.7", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "94d005c176db2080e98825d98e01e8b311a97a88" + "reference": "ef166890d821518106da3560086bfcbeb4fadfec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/94d005c176db2080e98825d98e01e8b311a97a88", - "reference": "94d005c176db2080e98825d98e01e8b311a97a88", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec", + "reference": "ef166890d821518106da3560086bfcbeb4fadfec", "shasum": "" }, "require": { @@ -9679,7 +9680,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2020-02-03T10:46:43+00:00" + "time": "2020-03-30T11:41:10+00:00" }, { "name": "theseer/fdomdocument", @@ -9763,16 +9764,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.6.1", + "version": "v2.6.3", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5" + "reference": "df4c4d08a639be4ef5d6d1322868f9e477553679" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5", - "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/df4c4d08a639be4ef5d6d1322868f9e477553679", + "reference": "df4c4d08a639be4ef5d6d1322868f9e477553679", "shasum": "" }, "require": { @@ -9780,8 +9781,14 @@ "symfony/polyfill-ctype": "^1.9" }, "require-dev": { + "ext-filter": "*", + "ext-pcre": "*", "phpunit/phpunit": "^4.8.35 || ^5.0" }, + "suggest": { + "ext-filter": "Required to use the boolean validator.", + "ext-pcre": "Required to use most of the library." + }, "type": "library", "extra": { "branch-alias": { @@ -9810,20 +9817,20 @@ "env", "environment" ], - "time": "2019-01-29T11:11:52+00:00" + "time": "2020-04-12T15:11:38+00:00" }, { "name": "webmozart/assert", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", "shasum": "" }, "require": { @@ -9831,7 +9838,7 @@ "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" @@ -9858,7 +9865,7 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2020-04-18T12:12:48+00:00" }, { "name": "weew/helpers-array", @@ -9926,5 +9933,6 @@ "ext-zip": "*", "lib-libxml": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "1.1.0" } diff --git a/dev/tests/api-functional/testsuite/Magento/LoginAsCustomerWebapi/Api/LoginAsCustomerWebapiCreateCustomerAccessTokenTest.php b/dev/tests/api-functional/testsuite/Magento/LoginAsCustomerWebapi/Api/LoginAsCustomerWebapiCreateCustomerAccessTokenTest.php new file mode 100644 index 0000000000000..caf639a678052 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/LoginAsCustomerWebapi/Api/LoginAsCustomerWebapiCreateCustomerAccessTokenTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LoginAsCustomerWebapi\Api; + +use Magento\Framework\Webapi\Rest\Request; +use Magento\Integration\Model\Oauth\Token as TokenModel; +use Magento\Integration\Model\ResourceModel\Oauth\Token\Collection; +use Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory; +use Magento\TestFramework\Authentication\OauthHelper; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Api-functional test for \Magento\LoginAsCustomerWebapi\Api\LoginAsCustomerWebapiCreateCustomerAccessTokenInterface. + */ +class LoginAsCustomerWebapiCreateCustomerAccessTokenTest extends WebapiAbstract +{ + const RESOURCE_PATH = "/V1/login-as-customer/token"; + + /** + * @var Collection + */ + private $tokenCollection; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->_markTestAsRestOnly(); + $tokenCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class); + $this->tokenCollection = $tokenCollectionFactory->create(); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture login_as_customer/general/enabled 1 + */ + public function testCreateCustomerAccessToken() + { + // 'Magento_LoginAsCustomerWebapi::login_token' resource required for access. + OauthHelper::clearApiAccessCredentials(); + OauthHelper::getApiAccessCredentials(['Magento_LoginAsCustomerWebapi::login_token']); + try { + $customerId = 1; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST, + ], + ]; + $requestData = ['customerId' => $customerId]; + $response = $this->_webApiCall($serviceInfo, $requestData); + + $this->assertToken($response, $customerId); + } catch (\Exception $e) { + OauthHelper::clearApiAccessCredentials(); + throw $e; + } + // Restore credentials + OauthHelper::clearApiAccessCredentials(); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture login_as_customer/general/enabled 0 + */ + public function testCreateCustomerAccessTokenLoginModuleDisabled() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Service is disabled.'); + + $customerId = 1; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST, + ], + ]; + $requestData = ['customerId' => $customerId]; + $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture login_as_customer/general/enabled 1 + */ + public function testCreateCustomerAccessTokenLoginNoAccess() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('The consumer isn\'t authorized to access %resources.'); + + // 'Magento_LoginAsCustomerWebapi::login_token' resource required for access. + OauthHelper::clearApiAccessCredentials(); + OauthHelper::getApiAccessCredentials([]); + try { + $customerId = 1; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST, + ], + ]; + $requestData = ['customerId' => $customerId]; + $this->_webApiCall($serviceInfo, $requestData); + } catch (\Exception $e) { + OauthHelper::clearApiAccessCredentials(); + throw $e; + } + // Restore credentials + OauthHelper::clearApiAccessCredentials(); + } + + /** + * Make sure provided token is valid and belongs to the specified user. + * + * @param string $response + * @param int $customerId + */ + private function assertToken(string $response, int $customerId) + { + $this->tokenCollection->addFilterByCustomerId($customerId); + $isTokenCorrect = false; + foreach ($this->tokenCollection->getItems() as $item) { + /** @var $item TokenModel */ + if ($item->getToken() == $response) { + $isTokenCorrect = true; + } + } + + $this->assertTrue($isTokenCorrect); + } +} diff --git a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/Repository/ConfigData.xml index 87657ae5ff571..0b1499091e02a 100644 --- a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/Repository/ConfigData.xml +++ b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/Repository/ConfigData.xml @@ -12,7 +12,7 @@ <item name="scope" xsi:type="string">default</item> <item name="scope_id" xsi:type="number">0</item> <item name="label" xsi:type="string">Yes</item> - <item name="value" xsi:type="string">AIzaSyDwqDWuw1lra-LnpJL2Mr02DYuFmkuRSns</item> + <item name="value" xsi:type="string">AIzaSyAzWKu17L5BcpPwtcHMdVDLma2hHoJQb5w</item> </field> </dataset> <dataset name="youtube_api_key_rollback"> diff --git a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml index f9c1019290a63..862a1351e839d 100644 --- a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml +++ b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml @@ -8,14 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\ProductVideo\Test\TestCase\ConfigurableProductVideoTest" summary="Video displaying for simple variation of configurable on product page" ticketId="MAGETWO-69381"> <variation name="ConfigurableProductVideoTestVariation1"> - <data name="tag" xsi:type="string">stable:no</data> <data name="product/dataset" xsi:type="string">configurable_with_video</data> <data name="simpleProductVideo/data/media_gallery/images/0/video_url" xsi:type="string">https://vimeo.com/16342611</data> <data name="variation" xsi:type="string">attribute_key_0:option_key_0 attribute_key_1:option_key_0</data> <data name="configData" xsi:type="string">youtube_api_key,play_if_base</data> <data name="vimeoDataCode" xsi:type="string">16342611</data> <data name="youtubeDataCode" xsi:type="string">bpOSxM0rNPM</data> - <constraint name="Magento\ProductVideo\Test\Constraint\AssertVideoConfigurableProductView" /> + <constraint name="Magento\ProductVideo\Test\Constraint\AssertVideoConfigurableProductView" /> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml index a6ad2c6e621f4..9d5dd49eadc6f 100644 --- a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml +++ b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\ProductVideo\Test\TestCase\UpdateProductVideoTest" summary="Add Video to PCF"> <variation name="UpdateProductVideoTestVariation1" summary="Edit Youtube URL" ticketId="MAGETWO-43664"> - <data name="tag" xsi:type="string">stable:no</data> <data name="productVideo/dataset" xsi:type="string">product_with_video_youtube</data> <data name="product/data/sku" xsi:type="string">simple_product_with_category_%isolation%</data> <data name="product/data/media_gallery/images/0/video_url" xsi:type="string">https://vimeo.com/16342611</data> @@ -40,7 +39,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> </variation> <variation name="GetVideoInfoTestVariation1" summary="Validate Youtube video info" ticketId="MAGETWO-43663"> - <data name="tag" xsi:type="string">stable:no</data> <data name="productVideo/dataset" xsi:type="string">product_with_video_youtube</data> <data name="product/data/sku" xsi:type="string">simple_product_with_category_%isolation%</data> <data name="product/data/media_gallery/images/0/video_url" xsi:type="string">https://youtu.be/WMp2PvU2qi8</data> diff --git a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php index feb9eca0793a2..d2a6bb7da9abd 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php +++ b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php @@ -7,21 +7,25 @@ /** * Abstract class for the controller tests */ + namespace Magento\TestFramework\TestCase; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Message\MessageInterface; use Magento\Framework\Stdlib\CookieManagerInterface; use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; use Magento\Theme\Controller\Result\MessagePlugin; -use Magento\Framework\App\Request\Http as HttpRequest; -use Magento\Framework\App\Response\Http as HttpResponse; +use PHPUnit\Framework\TestCase; /** + * Set of methods useful for performing requests to Controllers. + * * @SuppressWarnings(PHPMD.NumberOfChildren) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -abstract class AbstractController extends \PHPUnit\Framework\TestCase +abstract class AbstractController extends TestCase { protected $_runCode = ''; @@ -30,12 +34,12 @@ abstract class AbstractController extends \PHPUnit\Framework\TestCase protected $_runOptions = []; /** - * @var \Magento\Framework\App\RequestInterface + * @var RequestInterface */ protected $_request; /** - * @var \Magento\Framework\App\ResponseInterface + * @var ResponseInterface */ protected $_response; @@ -103,8 +107,9 @@ protected function assertPostConditions() */ public function dispatch($uri) { - /** @var HttpRequest $request */ $request = $this->getRequest(); + + $request->setDispatched(false); $request->setRequestUri($uri); if ($request->isPost() && !array_key_exists('form_key', $request->getPost()) @@ -119,25 +124,36 @@ public function dispatch($uri) /** * Request getter * - * @return \Magento\Framework\App\RequestInterface|HttpRequest + * @return RequestInterface */ public function getRequest() { if (!$this->_request) { - $this->_request = $this->_objectManager->get(\Magento\Framework\App\RequestInterface::class); + $this->_request = $this->_objectManager->get(RequestInterface::class); } return $this->_request; } + /** + * Reset Request parameters + * + * @return void + */ + protected function resetRequest(): void + { + $this->_objectManager->removeSharedInstance(RequestInterface::class); + $this->_request = null; + } + /** * Response getter * - * @return \Magento\Framework\App\ResponseInterface|HttpResponse + * @return ResponseInterface */ public function getResponse() { if (!$this->_response) { - $this->_response = $this->_objectManager->get(\Magento\Framework\App\ResponseInterface::class); + $this->_response = $this->_objectManager->get(ResponseInterface::class); } return $this->_response; } diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/ConsecutiveCallTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/ConsecutiveCallTest.php new file mode 100644 index 0000000000000..aff706688dc9f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/ConsecutiveCallTest.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Controller\Adminhtml; + +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * @magentoAppArea adminhtml + */ +class ConsecutiveCallTest extends AbstractBackendController +{ + /** + * Consecutive calls were failing due to `$request['dispatched']` not being reset before request + */ + public function testConsecutiveCallShouldNotFail() + { + $this->dispatch('backend/admin/auth/login'); + $this->dispatch('backend/admin/auth/login'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php index 5a4dadedb1c9e..ad646c1384c4a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php @@ -122,7 +122,7 @@ protected function _testAttribute($method, $entityCode, $expectedResult) try { $this->assertEquals( $expectedResult, - $this->_helper->{$method}(uniqid(), "<p>line1</p>\nline2", $attributeName) + $this->_helper->{$method}(uniqid(), __("<p>line1</p>\nline2"), $attributeName) ); $attribute->setIsHtmlAllowedOnFront($isHtml)->setIsWysiwygEnabled($isWysiwyg); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_multiwebsite_different_description.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_multiwebsite_different_description.php new file mode 100644 index 0000000000000..b77a53621d0bf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_multiwebsite_different_description.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\ProductFactory; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../Store/_files/website.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->create(ProductFactory::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->create(WebsiteRepositoryInterface::class); +$websiteId = $websiteRepository->get('test')->getId(); +$defaultWebsiteId = $websiteRepository->get('base')->getId(); + +$product = $productFactory->create(); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$defaultWebsiteId, $websiteId]) + ->setName('Simple Product on two websites') + ->setSku('simple-on-two-websites-different-description') + ->setPrice(10) + ->setDescription('<p>Product base description</p>'); + +$productRepository->save($product); +$product = $productRepository->get('simple-on-two-websites-different-description'); +$product->setDescription('<p>Product second description</p>'); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_multiwebsite_different_description_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_multiwebsite_different_description_rollback.php new file mode 100644 index 0000000000000..934d380362fcf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_multiwebsite_different_description_rollback.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $productRepository->deleteById('simple-on-two-websites-different-description'); +} catch (NoSuchEntityException $e) { + //product already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/../../Store/_files/website_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_12345.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_12345.php index 70aa7c07ed536..b16a312488131 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_12345.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_12345.php @@ -21,7 +21,7 @@ /** @var ProductRepositoryInterface $productRepository */ $productRepository = Bootstrap::getObjectManager() - ->create(ProductRepositoryInterface::class); + ->get(ProductRepositoryInterface::class); /** @var $installer CategorySetup */ $installer = Bootstrap::getObjectManager()->create(CategorySetup::class); @@ -105,7 +105,7 @@ $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); try { - $productToDelete = $productRepository->getById(11); + $productToDelete = $productRepository->getById(111); $productRepository->delete($productToDelete); /** @var \Magento\Quote\Model\ResourceModel\Quote\Item $itemResource */ diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Account/EmailTemplateTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Account/EmailTemplateTest.php new file mode 100644 index 0000000000000..ac12dc4df8d64 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Account/EmailTemplateTest.php @@ -0,0 +1,204 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Account; + +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Mail\EmailMessage; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Set of tests to verify e-mail templates delivered to Customers + * + * @magentoAppArea frontend + */ +class EmailTemplateTest extends AbstractController +{ + private const FIXTURE_CUSTOMER_EMAIL = 'customer@example.com'; + private const FIXTURE_CUSTOMER_FIRSTNAME = 'John'; + private const FIXTURE_CUSTOMER_LASTNAME = 'Smith'; + private const FIXTURE_CUSTOMER_ID = 1; + private const FIXTURE_CUSTOMER_PASSWORD = 'password'; + private const EXPECTED_GREETING = self::FIXTURE_CUSTOMER_FIRSTNAME . ' ' . self::FIXTURE_CUSTOMER_LASTNAME . ','; + + /** + * @var TransportBuilderMock + */ + private $transportBuilderMock; + + /** + * @var Session + */ + private $session; + + /** + * @var FormKey + */ + private $formKey; + + protected function setUp() + { + parent::setUp(); + $this->transportBuilderMock = $this->_objectManager->get(TransportBuilderMock::class); + $this->session = $this->_objectManager->get(Session::class); + $this->formKey = $this->_objectManager->get(FormKey::class); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture current_store customer/captcha/enable 0 + */ + public function testForgotPasswordEmailTemplateGreeting() + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST) + ->setPostValue(['email' => self::FIXTURE_CUSTOMER_EMAIL]); + $this->dispatch('customer/account/forgotPasswordPost'); + + $this->assertSameGreeting(self::EXPECTED_GREETING, $this->transportBuilderMock->getSentMessage()); + } + + /** + * Covers Magento_Customer::view/frontend/email/change_email.html + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture current_store customer/captcha/enable 0 + */ + public function testCustomerEmailChangeNotificationTemplateGreeting() + { + $this->loginByCustomerId(self::FIXTURE_CUSTOMER_ID); + + $this->sendAccountEditRequest([ + 'email' => 'new.email@example.com', + 'change_email' => 1, + ]); + + $this->assertRedirect($this->stringContains('customer/account/')); + $this->assertSessionMessages( + $this->equalTo(['You saved the account information.']), + MessageInterface::TYPE_SUCCESS + ); + + $this->assertSameGreeting(self::EXPECTED_GREETING, $this->transportBuilderMock->getSentMessage()); + } + + /** + * Covers Magento_Customer::view/frontend/email/change_email_and_password.html + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture current_store customer/captcha/enable 0 + */ + public function testCustomerEmailAndPasswordChangeNotificationTemplateGreeting() + { + $this->loginByCustomerId(self::FIXTURE_CUSTOMER_ID); + + $this->sendAccountEditRequest([ + 'email' => 'new.email@example.com', + 'change_email' => 1, + 'change_password' => 1, + 'password' => 'new-Password1', + 'password_confirmation' => 'new-Password1', + ]); + + $this->assertRedirect($this->stringContains('customer/account/')); + $this->assertSessionMessages( + $this->equalTo(['You saved the account information.']), + MessageInterface::TYPE_SUCCESS + ); + + $this->assertSameGreeting(self::EXPECTED_GREETING, $this->transportBuilderMock->getSentMessage()); + } + + /** + * Covers Magento_Customer::view/frontend/email/change_password.html + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture current_store customer/captcha/enable 0 + */ + public function testCustomerPasswordChangeNotificationTemplateGreeting() + { + $this->loginByCustomerId(self::FIXTURE_CUSTOMER_ID); + + $this->sendAccountEditRequest([ + 'change_password' => 1, + 'password' => 'new-Password1', + 'password_confirmation' => 'new-Password1', + ]); + + $this->assertRedirect($this->stringContains('customer/account/')); + $this->assertSessionMessages( + $this->equalTo(['You saved the account information.']), + MessageInterface::TYPE_SUCCESS + ); + + $this->assertSameGreeting(self::EXPECTED_GREETING, $this->transportBuilderMock->getSentMessage()); + } + + /** + * Wraps Customer Edit POST request + * + * @param array $customData + */ + private function sendAccountEditRequest(array $customData): void + { + $basicData = [ + 'form_key' => $this->formKey->getFormKey(), + 'firstname' => self::FIXTURE_CUSTOMER_FIRSTNAME, + 'lastname' => self::FIXTURE_CUSTOMER_LASTNAME, + 'current_password' => self::FIXTURE_CUSTOMER_PASSWORD + ]; + + $this->getRequest()->setMethod(HttpRequest::METHOD_POST) + ->setPostValue(array_merge($basicData, $customData)); + + $this->dispatch('customer/account/editPost'); + } + + /** + * Verifies if `<p class="greeting"/>` text contents equals the expected one. + * + * @param string $expectedGreeting + * @param EmailMessage $message + */ + private function assertSameGreeting(string $expectedGreeting, EmailMessage $message) + { + $messageContent = $this->getMessageRawContent($message); + $emailDom = new \DOMDocument(); + $emailDom->loadHTML($messageContent); + + $emailXpath = new \DOMXPath($emailDom); + $greeting = $emailXpath->query('//p[@class="greeting"]'); + + $this->assertSame(1, $greeting->length); + $this->assertSame($expectedGreeting, $greeting->item(0)->textContent); + } + + /** + * 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(); + } + + /** + * Performs Customer log in + * + * @param int $customerId + */ + private function loginByCustomerId(int $customerId): void + { + $this->session->loginById($customerId); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Account/LoginPostTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Account/LoginPostTest.php index 10476eb01f306..f55907c17e7bb 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Account/LoginPostTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Account/LoginPostTest.php @@ -28,6 +28,11 @@ class LoginPostTest extends AbstractController /** @var EncoderInterface */ private $urlEncoder; + /** + * @var Url + */ + private $customerUrl; + /** * @inheritdoc */ @@ -37,6 +42,7 @@ protected function setUp() $this->session = $this->_objectManager->get(Session::class); $this->urlEncoder = $this->_objectManager->get(EncoderInterface::class); + $this->customerUrl = $this->_objectManager->get(Url::class); } /** @@ -107,13 +113,16 @@ public function missingParametersDataProvider(): array */ public function testLoginWithUnconfirmedPassword(): void { - $this->markTestSkipped('Blocked by MC-31370.'); $email = 'unconfirmedcustomer@example.com'; $this->prepareRequest($email, 'Qwert12345'); $this->dispatch('customer/account/loginPost'); $this->assertEquals($email, $this->session->getUsername()); + $message = __( + 'This account is not confirmed. <a href="%1">Click here</a> to resend confirmation email.', + $this->customerUrl->getEmailConfirmationUrl($this->session->getUsername()) + ); $this->assertSessionMessages( - $this->equalTo([(string)__('This account is not confirmed. Click here to resend confirmation email.')]), + $this->equalTo([(string)$message]), MessageInterface::TYPE_ERROR ); } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index 7b0a4823f701e..55e970a178e91 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -189,14 +189,14 @@ public function testCreatepasswordActionInvalidToken() } /** - * @param int $customerId + * @param int $customerId * @param string|null $confirmation */ private function assertCustomerConfirmationEquals(int $customerId, string $confirmation = null) { /** @var \Magento\Customer\Model\Customer $customer */ $customer = Bootstrap::getObjectManager() - ->create(\Magento\Customer\Model\Customer::class)->load($customerId); + ->create(\Magento\Customer\Model\Customer::class)->load($customerId); $this->assertEquals($confirmation, $customer->getConfirmation()); } @@ -497,14 +497,14 @@ public function testChangePasswordEditPostAction() ->setMethod('POST') ->setPostValue( [ - 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), - 'firstname' => 'John', - 'lastname' => 'Doe', - 'email' => 'johndoe@email.com', - 'change_password' => 1, - 'change_email' => 1, + 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'johndoe@email.com', + 'change_password' => 1, + 'change_email' => 1, 'current_password' => 'password', - 'password' => 'new-Password1', + 'password' => 'new-Password1', 'password_confirmation' => 'new-Password1', ] ); @@ -654,6 +654,7 @@ public function testRegisterCustomerWithEmailConfirmation(): void /** @var CookieManagerInterface $cookieManager */ $cookieManager = $this->_objectManager->get(CookieManagerInterface::class); $cookieManager->deleteCookie(MessagePlugin::MESSAGES_COOKIES_NAME); + $this->_objectManager->removeSharedInstance(Http::class); $this->_objectManager->removeSharedInstance(Request::class); $this->_request = null; @@ -774,8 +775,9 @@ public function testResetPasswordWhenEmailChanged(): void $customer->setEmail($newEmail); $customerRepository->save($customer); - /* Goes through the link in a mail */ $this->resetRequest(); + + /* Goes through the link in a mail */ $this->getRequest() ->setParam('token', $token) ->setParam('id', $customerData->getId()); @@ -855,15 +857,13 @@ private function assertForgotPasswordEmailContent(string $token): void } /** - * Clear request object. - * - * @return void + * @inheritDoc */ - private function resetRequest(): void + protected function resetRequest(): void { $this->_objectManager->removeSharedInstance(Http::class); $this->_objectManager->removeSharedInstance(Request::class); - $this->_request = null; + parent::resetRequest(); } /** diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php index 4ff16189df1ba..3721d189a5544 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php @@ -13,6 +13,8 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\State\ExpiredException; use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\Url as UrlBuilder; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; /** @@ -651,6 +653,34 @@ public function testGetDefaultAddressesForNonExistentAddress() $this->assertNull($this->accountManagement->getDefaultShippingAddress($customerId)); } + /** + * Test reset password for customer on second website when shared account is enabled + * + * When customer from second website initiate reset password on first website + * global scope should not be reinited to customer scope + * + * @magentoConfigFixture current_store customer/account_share/scope 0 + * @magentoDataFixture Magento/Customer/_files/customer_for_second_website.php + */ + public function testInitiatePasswordResetForCustomerOnSecondWebsite() + { + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $store = $storeManager->getStore(); + + $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + AccountManagement::EMAIL_RESET, + $storeManager->getWebsite()->getId() + ); + + $this->assertEquals($store->getId(), $storeManager->getStore()->getId()); + $urlBuilder = $this->objectManager->get(UrlBuilder::class); + // to init scope if it has not inited yet + $urlBuilder->setScope($urlBuilder->getData('scope')); + $scope = $urlBuilder->getData('scope'); + $this->assertEquals($store->getId(), $scope->getId()); + } + /** * Set Rp data to Customer in fixture * diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php index 9f121268135f8..7ca0e759c8408 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php @@ -23,6 +23,7 @@ public function testToOptionArray() $optionsToCompare = []; foreach ($options as $option) { if (is_array($option['value'])) { + //phpcs:ignore Magento2.Performance.ForeachArrayMerge $optionsToCompare = array_merge($optionsToCompare, $option['value']); } else { $optionsToCompare[] = $option; @@ -36,22 +37,18 @@ public function testToOptionArray() [ 'value' => 1, 'label' => 'Default (General)', - '__disableTmpl' => true, ], [ 'value' => 1, 'label' => 'General', - '__disableTmpl' => true, ], [ 'value' => 2, 'label' => 'Wholesale', - '__disableTmpl' => true, ], [ 'value' => 3, 'label' => 'Retailer', - '__disableTmpl' => true, ], ] ); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_group_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_group_rollback.php index 20a1f4623a1f7..62750e9ec7de1 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_group_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_group_rollback.php @@ -7,6 +7,7 @@ use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\Framework\Registry; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Exception\NoSuchEntityException; @@ -21,6 +22,11 @@ ->create(); $groups = $groupRepository->getList($searchCriteria) ->getItems(); + +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); foreach ($groups as $group) { try { $groupRepository->delete($group); @@ -28,3 +34,5 @@ //Group already removed } } +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filter/DirectiveProcessor/SimpleDirectiveTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filter/DirectiveProcessor/SimpleDirectiveTest.php index ccf867c51c3c8..5b8b9e6d1f3e3 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filter/DirectiveProcessor/SimpleDirectiveTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filter/DirectiveProcessor/SimpleDirectiveTest.php @@ -74,6 +74,7 @@ public function testDefaultFiltersAreUsed() public function testParametersAreParsed() { $filter = $this->objectManager->create(Template::class); + $filter->setStrictMode(false); $processor = $this->createWithProcessorsAndFilters( ['mydir' => $this->objectManager->create(MyDirProcessor::class)], diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php index de09b87b04e4a..f99a34a077524 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filter/TemplateTest.php @@ -155,6 +155,7 @@ public function testIfDirective() public function testNonDataObjectVariableParsing() { + $previous = $this->templateFilter->setStrictMode(false); $this->templateFilter->setVariables( [ 'address' => new class { @@ -168,11 +169,33 @@ public function format($type) $template = '{{var address.format(\'html\')}}'; $expected = '<foo>html</foo>'; - self::assertEquals($expected, $this->templateFilter->filter($template)); + try { + self::assertEquals($expected, $this->templateFilter->filter($template)); + } finally { + $this->templateFilter->setStrictMode($previous); + } + } + + public function testStrictModeByDefault() + { + $this->templateFilter->setVariables( + [ + 'address' => new class { + public function format() + { + throw new \Exception('Should not run'); + } + } + ] + ); + + $template = '{{var address.format(\'html\')}}'; + self::assertEquals('', $this->templateFilter->filter($template)); } public function testComplexVariableArguments() { + $previous = $this->templateFilter->setStrictMode(false); $this->templateFilter->setVariables( [ 'address' => new class { @@ -187,11 +210,16 @@ public function format($a, $b, $c) $template = '{{var address.format($arg1,\'bar\',[param1:baz])}}'; $expected = 'foo bar baz'; - self::assertEquals($expected, $this->templateFilter->filter($template)); + try { + self::assertEquals($expected, $this->templateFilter->filter($template)); + } finally { + $this->templateFilter->setStrictMode($previous); + } } public function testComplexVariableGetterArguments() { + $previous = $this->templateFilter->setStrictMode(false); $this->templateFilter->setVariables( [ 'address' => new class extends DataObject { @@ -206,7 +234,11 @@ public function getFoo($a, $b, $c) $template = '{{var address.getFoo($arg1,\'bar\',[param1:baz])}}'; $expected = 'foo bar baz'; - self::assertEquals($expected, $this->templateFilter->filter($template)); + try { + self::assertEquals($expected, $this->templateFilter->filter($template)); + } finally { + $this->templateFilter->setStrictMode($previous); + } } public function testNonDataObjectRendersBlankInStrictMode() diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/Element/UiComponent/ContextTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/Element/UiComponent/ContextTest.php new file mode 100644 index 0000000000000..b6c56795ebefe --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/View/Element/UiComponent/ContextTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\View\Element\UiComponent; + +use Magento\Framework\Api\Search\SearchCriteria; +use Magento\Framework\App\RequestInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Magento\Ui\Component\Form; +use Magento\Ui\Component\FormFactory; +use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; + +/** + * Test UI component context. + */ +class ContextTest extends TestCase +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @var ContextFactory + */ + private $contextFactory; + + /** + * @var FormFactory + */ + private $componentFactory; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->request = Bootstrap::getObjectManager()->get(RequestInterface::class); + $this->contextFactory = Bootstrap::getObjectManager()->get(ContextFactory::class); + $this->componentFactory = Bootstrap::getObjectManager()->get(FormFactory::class); + } + + /** + * Generate provider for the test. + * + * @return DataProviderInterface + */ + private function generateMockProvider(): DataProviderInterface + { + /** @var DataProviderInterface|MockObject $mock */ + $mock = $this->getMockForAbstractClass(DataProviderInterface::class); + $mock->method('getName')->willReturn('test'); + $mock->method('getPrimaryFieldName')->willReturn('id'); + $mock->method('getRequestFieldName')->willReturn('id'); + $mock->method('getData')->willReturn(['id' => ['some_field' => '${\'some_value\'}']]); + $mock->method('getConfigData')->willReturn([]); + $mock->method('getFieldMetaInfo')->willReturn([]); + $mock->method('getFieldSetMetaInfo')->willReturn('id'); + $mock->method('getFieldsMetaInfo')->willReturn('id'); + $mock->method('getSearchCriteria')->willReturn(new SearchCriteria()); + $mock->method('getSearchResult')->willReturn([]); + + return $mock; + } + + /** + * Check processed provider data. + * + * @return void + */ + public function testGetDataSourceData(): void + { + $dataProvider = $this->generateMockProvider(); + $context = $this->contextFactory->create(['dataProvider' => $dataProvider]); + /** @var Form $component */ + $component = $this->componentFactory->create(['context' => $context]); + $this->request->setParams(['id' => 'id']); + + $data = $context->getDataSourceData($component); + $this->assertEquals( + [ + 'test' => [ + 'type' => 'dataSource', + 'name' => 'test', + 'dataScope' => null, + 'config' => [ + 'data' => ['some_field' => '${\'some_value\'}', '__disableTmpl' => ['some_field' => true]], + 'params' => ['namespace' => null]] + ] + ], + $data + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/Element/UiComponentFactoryTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/Element/UiComponentFactoryTest.php new file mode 100644 index 0000000000000..8a1064ffe6663 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/View/Element/UiComponentFactoryTest.php @@ -0,0 +1,168 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\View\Element; + +use Magento\Framework\Api\Search\SearchCriteria; +use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Magento\Framework\Config\DataInterface as ConfigData; +use Magento\Framework\Config\DataInterfaceFactory as ConfigDataFactory; + +/** + * Test the component factory. + */ +class UiComponentFactoryTest extends TestCase +{ + /** + * @var UiComponentFactoryFactory + */ + private $factory; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->factory = Bootstrap::getObjectManager()->get(UiComponentFactoryFactory::class); + } + + /** + * Create factory with mock config provided. + * + * @param array $mockConfig + * @return UiComponentFactory + */ + private function createFactory(array $mockConfig): UiComponentFactory + { + $dataMock = $this->getMockForAbstractClass(ConfigData::class); + $dataMock->method('get')->willReturnCallback( + function (string $id) use ($mockConfig) : array { + return $mockConfig[$id]; + } + ); + $dataFactoryMock = $this->getMockBuilder(ConfigDataFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $dataFactoryMock->method('create')->willReturn($dataMock); + + return $this->factory->create(['configFactory' => $dataFactoryMock]); + } + + /** + * Test creating a component. + * + * @return void + * @magentoAppArea adminhtml + */ + public function testCreate(): void + { + //Mocking component config. + $factory = $this->createFactory([ + 'test' => [ + 'arguments' => ['data' => ['config' => ['component' => 'uiComponent']]], + 'attributes' => [ + 'name' => 'test', + 'sorting' => true, + 'class' => 'Magento\Ui\Component\Listing', + 'component' => 'uiComponent' + ], + 'children' => [ + 'test_child' => [ + 'arguments' => [ + 'data' => ['config' => ['component' => 'uiComponent']], + 'dataProvider' => $this->generateMockProvider() + ], + 'attributes' => [ + 'name' => 'test_child', + 'sorting' => true, + 'class' => 'Magento\Ui\Component\Listing', + 'component' => 'uiComponent' + ], + 'children' => [] + ] + ] + ], + 'test_child_child' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'component' => 'uiComponent', + 'label' => '${\'Label\'}', + 'componentType' => 'component' + ] + ], + ], + 'attributes' => [ + 'name' => 'test_child_child', + 'sorting' => true, + 'class' => 'Magento\Ui\Component\Listing', + 'component' => 'uiComponent' + ], + 'children' => [] + ] + ]); + $component = $factory->create('test', null, ['data' => ['label' => '${\'Label\'}']]); + + $componentData = $component->getData(); + //Arguments passed must be sanitized + $this->assertArrayHasKey('__disableTmpl', $componentData); + $this->assertEquals(['label' => true], $componentData['__disableTmpl']); + //Metadata provided by the dataProvider must be sanitized as well. + $this->assertArrayHasKey('test_child_child', $childData = $component->getChildComponents()); + $childData = $component->getChildComponents()['test_child_child']->getData()['config']; + $this->assertArrayHasKey('__disableTmpl', $childData); + $this->assertEquals(['label' => true], $childData['__disableTmpl']); + } + + /** + * Generate provider for the test. + * + * @return DataProviderInterface + */ + private function generateMockProvider(): DataProviderInterface + { + /** @var DataProviderInterface|MockObject $mock */ + $mock = $this->getMockForAbstractClass(DataProviderInterface::class); + $mock->method('getName')->willReturn('test'); + $mock->method('getPrimaryFieldName')->willReturn('id'); + $mock->method('getRequestFieldName')->willReturn('id'); + $mock->method('getData')->willReturn([]); + $mock->method('getConfigData')->willReturn([]); + $mock->method('getFieldMetaInfo')->willReturn([]); + $mock->method('getFieldSetMetaInfo')->willReturn('id'); + $mock->method('getFieldsMetaInfo')->willReturn('id'); + $mock->method('getSearchCriteria')->willReturn(new SearchCriteria()); + $mock->method('getSearchResult')->willReturn([]); + $mock->method('getMeta')->willReturn( + [ + 'test_child_child' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'component' => 'uiComponent', + 'label' => '${\'Label\'}', + 'componentType' => 'component' + ] + ], + ], + 'attributes' => [ + 'name' => 'test_child_child', + 'sorting' => true, + 'class' => 'Magento\Ui\Component\Listing', + 'component' => 'uiComponent' + ] + ] + ] + ); + + return $mock; + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaContent/Model/ExtractAssetsFromContentTest.php b/dev/tests/integration/testsuite/Magento/MediaContent/Model/ExtractAssetsFromContentTest.php new file mode 100644 index 0000000000000..61884f0846f86 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContent/Model/ExtractAssetsFromContentTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + * + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\MediaContentApi\Api\ExtractAssetsFromContentInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for ExtractAssetsFromContent + */ +class ExtractAssetsFromContentTest extends TestCase +{ + /** + * @var ExtractAssetsFromContentInterface + */ + private $extractAssetsFromContent; + + /** + * @inheritdoc + */ + public function setUp(): void + { + $this->extractAssetsFromContent = Bootstrap::getObjectManager() + ->get(ExtractAssetsFromContentInterface::class); + } + + /** + * Assing assets to content, retrieve the data, then unassign assets from content + * + * @magentoDataFixture Magento/MediaGallery/_files/media_asset.php + * + * @dataProvider contentProvider + * @param string $content + * @param array $assetIds + */ + public function testExecute(string $content, array $assetIds): void + { + $assets = $this->extractAssetsFromContent->execute($content); + + $extractedAssetIds = []; + foreach ($assets as $asset) { + $extractedAssetIds[] = $asset->getId(); + } + + sort($assetIds); + sort($extractedAssetIds); + + $this->assertEquals($assetIds, $extractedAssetIds); + } + + /** + * Data provider for testExecute + * + * @return array + */ + public function contentProvider() + { + return [ + 'Empty Content' => [ + '', + [] + ], + 'No paths in content' => [ + 'content without paths', + [] + ], + 'Relevant paths in content' => [ + 'content {{media url="testDirectory/path.jpg"}} content', + [ + 2020 + ] + ], + 'Relevant wysiwyg paths in content' => [ + 'content <img src="https://domain.com/media/testDirectory/path.jpg"}} content', + [ + 2020 + ] + ], + 'Relevant path content with pub' => [ + '/pub/media/testDirectory/path.jpg', + [ + 2020 + ] + ], + 'Relevant path content' => [ + '/media/testDirectory/path.jpg', + [ + 2020 + ] + ], + 'Irrelevant paths in content' => [ + 'content {{media url="media/non-existing-path.png"}} content', + [] + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaContent/Model/SaveDeleteContentAssetLinksTest.php b/dev/tests/integration/testsuite/Magento/MediaContent/Model/SaveDeleteContentAssetLinksTest.php new file mode 100644 index 0000000000000..4244cd66475a0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContent/Model/SaveDeleteContentAssetLinksTest.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + * + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\MediaContentApi\Api\SaveContentAssetLinksInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; +use Magento\MediaContentApi\Api\Data\ContentAssetLinkInterface; +use Magento\MediaContentApi\Api\GetAssetIdsByContentIdentityInterface; +use Magento\MediaContentApi\Api\GetContentByAssetIdsInterface; +use Magento\MediaContentApi\Api\DeleteContentAssetLinksInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for AssignAssets service + */ +class SaveDeleteContentAssetLinksTest extends TestCase +{ + /** + * @var SaveContentAssetLinksInterface + */ + private $saveContentAssetLinks; + + /** + * @var GetAssetIdsByContentIdentityInterface + */ + private $getAssetIdsByContentIdentity; + + /** + * @var GetContentByAssetIdsInterface + */ + private $getContentByAssetIds; + + /** + * @var DeleteContentAssetLinksInterface + */ + private $deleteContentAssetLinks; + + /** + * @inheritdoc + */ + public function setUp(): void + { + $this->saveContentAssetLinks = Bootstrap::getObjectManager()->get(SaveContentAssetLinksInterface::class); + $this->getAssetIdsByContentIdentity = Bootstrap::getObjectManager() + ->get(GetAssetIdsByContentIdentityInterface::class); + $this->getContentByAssetIds = Bootstrap::getObjectManager()->get(GetContentByAssetIdsInterface::class); + $this->deleteContentAssetLinks = Bootstrap::getObjectManager()->get(DeleteContentAssetLinksInterface::class); + } + + /** + * Save asset to content links, retrieve the data, delete assets to content links + */ + public function testAssignRetrieveAndUnassign(): void + { + $entityType = 'catalog_product'; + $entityId = 42; + $field = 'description'; + $assetIds = [56, 78]; + + $contentIdentity = Bootstrap::getObjectManager()->create( + ContentIdentityInterface::class, + [ + 'entityType' => $entityType, + 'entityId' => $entityId, + 'field' => $field + ] + ); + + $contentAssetLinks = []; + + foreach ($assetIds as $assetId) { + $contentAssetLinks[] = Bootstrap::getObjectManager()->create( + ContentAssetLinkInterface::class, + [ + 'assetId' => $assetId, + 'contentIdentity' => $contentIdentity + ] + ); + } + + $this->saveContentAssetLinks->execute($contentAssetLinks); + $retrievedAssetIds = $this->getAssetIdsByContentIdentity->execute($contentIdentity); + $this->assertEquals($assetIds, $retrievedAssetIds); + $retrievedContentIdentities = $this->getContentByAssetIds->execute($assetIds); + $this->assertEquals(count($retrievedContentIdentities), 1); + + foreach ($retrievedContentIdentities as $identity) { + $this->assertEquals($entityType, $identity->getEntityType()); + $this->assertEquals($entityId, $identity->getEntityId()); + $this->assertEquals($field, $identity->getField()); + } + + $this->deleteContentAssetLinks->execute($contentAssetLinks); + + $this->assertEmpty($this->getContentByAssetIds->execute($assetIds)); + $this->assertEmpty($this->getAssetIdsByContentIdentity->execute($contentIdentity)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaContent/Model/UpdateContentAssetLinksTest.php b/dev/tests/integration/testsuite/Magento/MediaContent/Model/UpdateContentAssetLinksTest.php new file mode 100644 index 0000000000000..0a9393c1c3c20 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContent/Model/UpdateContentAssetLinksTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + * + */ +declare(strict_types=1); + +namespace Magento\MediaContent\Model; + +use Magento\MediaContentApi\Api\Data\ContentIdentityInterface; +use Magento\MediaContentApi\Api\GetAssetIdsByContentIdentityInterface; +use Magento\MediaContentApi\Api\UpdateContentAssetLinksInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for UpdateContentAssetLinks + */ +class UpdateContentAssetLinksTest extends TestCase +{ + /** + * @var UpdateContentAssetLinksInterface + */ + private $updateContentAssetLinks; + + /** + * @var GetAssetIdsByContentIdentityInterface + */ + private $getAssetIdsByContentIdentity; + + /** + * @inheritdoc + */ + public function setUp(): void + { + $this->updateContentAssetLinks = Bootstrap::getObjectManager()->get(UpdateContentAssetLinksInterface::class); + $this->getAssetIdsByContentIdentity = Bootstrap::getObjectManager() + ->get(GetAssetIdsByContentIdentityInterface::class); + } + + /** + * Assing assets to content, retrieve the data, then unassign assets from content + * + * @magentoDataFixture Magento/MediaGallery/_files/media_asset.php + */ + public function testExecute(): void + { + $entityType = 'catalog_product'; + $entityId = 2020; + $field = 'description'; + $contentWithoutAsset = ''; + $contentWithAsset = 'content {{media url="testDirectory/path.jpg"}} content'; + + $contentIdentity = Bootstrap::getObjectManager()->create( + ContentIdentityInterface::class, + [ + 'entityType' => $entityType, + 'entityId' => $entityId, + 'field' => $field + ] + ); + + $this->updateContentAssetLinks->execute($contentIdentity, $contentWithoutAsset); + $this->assertEmpty($this->getAssetIdsByContentIdentity->execute($contentIdentity)); + + $this->updateContentAssetLinks->execute($contentIdentity, $contentWithAsset); + $this->assertNotEmpty($this->getAssetIdsByContentIdentity->execute($contentIdentity)); + + $this->updateContentAssetLinks->execute($contentIdentity, $contentWithoutAsset); + $this->assertEmpty($this->getAssetIdsByContentIdentity->execute($contentIdentity)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCatalog/Model/ResourceModel/GetEntityContentTest.php b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/Model/ResourceModel/GetEntityContentTest.php new file mode 100644 index 0000000000000..b0343c846ee88 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/Model/ResourceModel/GetEntityContentTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + * + */ +declare(strict_types=1); + +namespace Magento\MediaContentCatalog\Model\ResourceModel; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\MediaContentApi\Model\GetEntityContentsInterface; +use Magento\MediaContentApi\Api\Data\ContentIdentityInterfaceFactory; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for GetEntityContentsInterface + */ +class GetEntityContentTest extends TestCase +{ + private const CONTENT_TYPE = 'catalog_product'; + private const TYPE = 'entityType'; + private const ENTITY_ID = 'entityId'; + private const FIELD = 'field'; + + /** + * @var GetEntityContentsInterface + */ + private $getContent; + + /** + * @var ContentIdentityInterfaceFactory + */ + private $contentIdentityFactory; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @inheritdoc + */ + public function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + $this->getContent = $objectManager->get(GetEntityContentsInterface::class); + $this->contentIdentityFactory = $objectManager->get(ContentIdentityInterfaceFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + } + + /** + * Test for get content from product in different store views + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testProduct(): void + { + $product = $this->productRepository->get('simple'); + $contentIdentity = $this->contentIdentityFactory->create( + [ + self::TYPE => self::CONTENT_TYPE, + self::FIELD => 'description', + self::ENTITY_ID => (string) $product->getEntityId(), + ] + ); + $this->assertEquals( + ['Description with <b>html tag</b>'], + $this->getContent->execute($contentIdentity) + ); + } + + /** + * Test for get content from product in different store views + * + * @magentoDataFixture Magento/Catalog/_files/product_multiwebsite_different_description.php + */ + public function testProductTwoWebsites(): void + { + $product = $this->productRepository->get('simple-on-two-websites-different-description'); + $contentIdentity = $this->contentIdentityFactory->create( + [ + self::TYPE => self::CONTENT_TYPE, + self::FIELD => 'description', + self::ENTITY_ID => (string) $product->getEntityId(), + ] + ); + $this->assertEquals( + [ + '<p>Product base description</p>', + '<p>Product second description</p>' + ], + $this->getContent->execute($contentIdentity) + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/category_with_asset.php b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/category_with_asset.php new file mode 100644 index 0000000000000..d890ea52dfad7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/category_with_asset.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Model\Category; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Category $category */ +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId( + 28767 +)->setCreatedAt( + '2014-06-23 09:50:07' +)->setName( + 'Category 1' +)->setDescription( + 'content {{media url="testDirectory/path.jpg"}} content' +)->setParentId( + 2 +)->setPath( + '1/2/333' +)->setLevel( + 2 +)->setAvailableSortBy( + ['position', 'name'] +)->setDefaultSortBy( + 'name' +)->setIsActive( + true +)->setPosition( + 1 +)->save(); diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/category_with_asset_rollback.php b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/category_with_asset_rollback.php new file mode 100644 index 0000000000000..a5010883e120c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/category_with_asset_rollback.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $category \Magento\Catalog\Model\Category */ +$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); +$category->load(28767); +if ($category->getId()) { + $category->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/product_with_asset.php b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/product_with_asset.php new file mode 100644 index 0000000000000..2a1177661572a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/product_with_asset.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductExtensionInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +Bootstrap::getInstance()->reinitialize(); + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setId(1567) + ->setAttributeSetId(4) + ->setName('Simple Product') + ->setSku('simple_with_asset') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription('content {{media url="testDirectory/path.jpg"}} content') + ->setTaxClassId(0) + ->setDescription('content {{media url="testDirectory/path.jpg"}} content') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ] + ); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/product_with_asset_rollback.php b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/product_with_asset_rollback.php new file mode 100644 index 0000000000000..8f45ecac0d6f1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCatalog/_files/product_with_asset_rollback.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('simple_with_asset', false, null, true); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/block_with_asset.php b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/block_with_asset.php new file mode 100644 index 0000000000000..eb2adf0c0a348 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/block_with_asset.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Cms\Model\Block; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var $block Block */ +$block = Bootstrap::getObjectManager()->create(Block::class); +$block->setTitle( + 'CMS Block Title' +)->setIdentifier( + 'fixture_block_with_asset' +)->setContent( + 'content {{media url="testDirectory/path.jpg"}} content' +)->setIsActive( + 1 +)->setStores( + [ + Bootstrap::getObjectManager()->get(StoreManagerInterface::class)->getStore()->getId() + ] +)->save(); diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/block_with_asset_rollback.php b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/block_with_asset_rollback.php new file mode 100644 index 0000000000000..68069953f22f7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/block_with_asset_rollback.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Cms\Api\BlockRepositoryInterface; +use Magento\Cms\Api\Data\BlockInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var BlockRepositoryInterface $blockRepository */ +$blockRepository = $objectManager->get(BlockRepositoryInterface::class); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter(BlockInterface::IDENTIFIER, 'fixture_block_with_asset') + ->create(); +$result = $blockRepository->getList($searchCriteria); + +/** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + * In that case there is "if" which checks that "fixture_block_with_asset" still exists in database. + */ +foreach ($result->getItems() as $item) { + $blockRepository->delete($item); +} diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/page_with_asset.php b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/page_with_asset.php new file mode 100644 index 0000000000000..8ac3f0aea693a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/page_with_asset.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $page \Magento\Cms\Model\Page */ +$page = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Cms\Model\Page::class); +$page->setTitle('Cms Page 100') + ->setIdentifier('fixture_page_with_asset') + ->setStores([0]) + ->setIsActive(1) + ->setContent('content {{media url="testDirectory/path.jpg"}} content') + ->setContentHeading('<h2>Cms Page 100 Title</h2>') + ->setMetaTitle('Cms Meta title for page100') + ->setMetaKeywords('Cms Meta Keywords for page100') + ->setMetaDescription('Cms Meta Description for page100') + ->setPageLayout('1column') + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/page_with_asset_rollback.php b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/page_with_asset_rollback.php new file mode 100644 index 0000000000000..ebe24d4f05983 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaContentCms/_files/page_with_asset_rollback.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var PageRepositoryInterface $pageRepository */ +$pageRepository = $objectManager->get(PageRepositoryInterface::class); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter(PageInterface::IDENTIFIER, 'fixture_page_with_asset') + ->create(); +$result = $pageRepository->getList($searchCriteria); + +/** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + * In that case there is "if" which checks that "fixture_page_with_asset" still exists in database. + */ +foreach ($result->getItems() as $item) { + $pageRepository->delete($item); +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/AssetEndToEndTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/AssetEndToEndTest.php new file mode 100644 index 0000000000000..61b3c22c3ca54 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/AssetEndToEndTest.php @@ -0,0 +1,151 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model; + +use Magento\MediaGalleryApi\Api\Data\KeywordInterfaceFactory; +use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterfaceFactory; +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface; +use Magento\MediaGalleryApi\Api\SaveAssetsInterface; +use Magento\MediaGalleryApi\Api\SaveAssetsKeywordsInterface; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * End to end test for working with assets and keywords + */ +class AssetEndToEndTest extends TestCase +{ + /** + * @var GetAssetsKeywordsInterface + */ + private $saveAssetsKeywords; + + /** + * @var GetAssetsKeywordsInterface + */ + private $getAssetsKeywords; + + /** + * @var AssetKeywordsInterfaceFactory + */ + private $assetsKeywordsFactory; + + /** + * @var AssetInterfaceFactory + */ + private $assetFactory; + + /** + * @var KeywordInterfaceFactory + */ + private $keywordFactory; + + /** + * @var SaveAssetsInterface + */ + private $saveAssets; + + /** + * @var GetAssetsByIdsInterface + */ + private $getAssetsByIds; + + /** + * @var GetAssetsByPathsInterface + */ + private $getAssetsByPath; + + /** + * @var DeleteAssetsByPathsInterface + */ + private $deleteAssetsByPaths; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->saveAssetsKeywords = Bootstrap::getObjectManager()->get(SaveAssetsKeywordsInterface::class); + $this->getAssetsKeywords = Bootstrap::getObjectManager()->get(GetAssetsKeywordsInterface::class); + $this->assetsKeywordsFactory = Bootstrap::getObjectManager()->get(AssetKeywordsInterfaceFactory::class); + $this->assetFactory = Bootstrap::getObjectManager()->get(AssetInterfaceFactory::class); + $this->keywordFactory = Bootstrap::getObjectManager()->get(KeywordInterfaceFactory::class); + $this->saveAssets = Bootstrap::getObjectManager()->get(SaveAssetsInterface::class); + $this->getAssetsByIds = Bootstrap::getObjectManager()->get(GetAssetsByIdsInterface::class); + $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); + $this->deleteAssetsByPaths = Bootstrap::getObjectManager()->get(DeleteAssetsByPathsInterface::class); + } + + /** + * Testing assets keywords save and get + */ + public function testExecute(): void + { + $keyword1 = $this->keywordFactory->create( + [ + 'keyword' => 'pear' + ] + ); + + $keyword2 = $this->keywordFactory->create( + [ + 'keyword' => 'plum' + ] + ); + + $asset = $this->assetFactory->create( + [ + 'path' => 'fruit.jpg', + 'title' => 'Img', + 'source' => 'Local', + 'contentType' => 'image/jpeg', + 'width' => 420, + 'height' => 240, + 'size' => 12877 + ] + ); + $this->saveAssets->execute([$asset]); + $loadedAssets = $this->getAssetsByPath->execute([$asset->getPath()]); + $loadedAsset = $loadedAssets[0]; + + $this->assertEquals(1, count($loadedAssets)); + + $assetKeywords = $this->assetsKeywordsFactory->create( + [ + 'assetId' => $loadedAsset->getId(), + 'keywords' => [ + $keyword1, + $keyword2 + ] + ] + ); + + $this->saveAssetsKeywords->execute([$assetKeywords]); + $loadedAssetKeywords = $this->getAssetsKeywords->execute([$loadedAsset->getId()]); + + $this->assertEquals(1, count($loadedAssetKeywords)); + + /** @var AssetKeywordsInterface $loadedAssetKeywords1 */ + $loadedAssetKeywords1 = current($loadedAssetKeywords); + + $loadedKeywords = $loadedAssetKeywords1->getKeywords(); + + $this->assertEquals(2, count($loadedKeywords)); + + foreach ($loadedKeywords as $theKeyword) { + $this->assertTrue(in_array($theKeyword->getKeyword(), ['pear', 'plum'])); + } + + $this->deleteAssetsByPaths->execute(['fruit.jpg']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/Directory/Command/CreateByPathsTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/Directory/Command/CreateByPathsTest.php new file mode 100644 index 0000000000000..f1a9272c2b13c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/Directory/Command/CreateByPathsTest.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + * + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory\Command; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\MediaGalleryApi\Api\CreateDirectoriesByPathsInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Test for CreateDirectoriesByPathsInterface + */ +class CreateByPathsTest extends \PHPUnit\Framework\TestCase +{ + /** + * Test directory name + */ + private const TEST_DIRECTORY_NAME = 'testCreateDirectory'; + + /** + * Absolute path to the media directory + */ + private $mediaDirectoryPath; + + /** + * @var CreateDirectoriesByPathsInterface + */ + private $createByPaths; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->createByPaths = Bootstrap::getObjectManager()->get(CreateDirectoriesByPathsInterface::class); + $this->mediaDirectoryPath = Bootstrap::getObjectManager()->get(Filesystem::class) + ->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath(); + } + + /** + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function testCreateDirectory(): void + { + $this->createByPaths->execute([self::TEST_DIRECTORY_NAME]); + $this->assertFileExists($this->mediaDirectoryPath . self::TEST_DIRECTORY_NAME); + } + + /** + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + */ + public function testCreateDirectoryThatAlreadyExist(): void + { + $this->createByPaths->execute([self::TEST_DIRECTORY_NAME]); + $this->assertFileExists($this->mediaDirectoryPath . self::TEST_DIRECTORY_NAME); + $this->createByPaths->execute([self::TEST_DIRECTORY_NAME]); + } + + /** + * @param array $paths + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @dataProvider notAllowedPathsProvider + */ + public function testCreateDirectoryWithRelativePath(array $paths): void + { + $this->createByPaths->execute($paths); + } + + /** + * Provider of paths that are not allowed for deletion + * + * @return array + */ + public function notAllowedPathsProvider(): array + { + return [ + [ + ['../../pub/' . self::TEST_DIRECTORY_NAME] + ], + [ + ['theme/' . self::TEST_DIRECTORY_NAME] + ], + [ + ['../../pub/media', 'theme'] + ] + ]; + } + + /** + * Test create child directory with the same name as parent + */ + public function testCreateChildDirectoryTheSameNameAsParentDirectory(): void + { + $dir = self::TEST_DIRECTORY_NAME; + $childPath = $dir . '/' . $dir; + + $this->createByPaths->execute([$dir]); + $this->assertFileExists($this->mediaDirectoryPath . $dir); + $this->createByPaths->execute([$childPath]); + $this->assertFileExists($this->mediaDirectoryPath . $childPath); + } + + /** + * @throws \Magento\Framework\Exception\FileSystemException + */ + protected function tearDown() + { + $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\Filesystem::class); + /** @var \Magento\Framework\Filesystem\Directory\WriteInterface $directory */ + $directory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); + if ($directory->isExist(self::TEST_DIRECTORY_NAME)) { + $directory->delete(self::TEST_DIRECTORY_NAME); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/Directory/Command/DeleteByPathsTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/Directory/Command/DeleteByPathsTest.php new file mode 100644 index 0000000000000..3eaf7fbe3538a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/Directory/Command/DeleteByPathsTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + * + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\Directory\Command; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\MediaGalleryApi\Api\DeleteDirectoriesByPathsInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Test for DeleteDirectoriesByPathsInterface + */ +class DeleteByPathsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var DeleteDirectoriesByPathsInterface + */ + private $deleteByPaths; + + /** + * @var string + */ + private $testDirectoryName = 'testDeleteDirectory'; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->deleteByPaths = Bootstrap::getObjectManager()->get(DeleteDirectoriesByPathsInterface::class); + $this->filesystem = Bootstrap::getObjectManager()->get(Filesystem::class); + } + + /** + * @throws \Magento\Framework\Exception\CouldNotDeleteException + * @throws \Magento\Framework\Exception\FileSystemException + */ + public function testDeleteDirectory(): void + { + /** @var \Magento\Framework\Filesystem\Directory\WriteInterface $mediaDirectory */ + $mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $mediaDirectory->create($this->testDirectoryName); + $fullPath = $mediaDirectory->getAbsolutePath($this->testDirectoryName); + $this->assertFileExists($fullPath); + $this->deleteByPaths->execute([$this->testDirectoryName]); + $this->assertFileNotExists($fullPath); + } + + /** + * @param array $paths + * @throws \Magento\Framework\Exception\CouldNotDeleteException + * @expectedException \Magento\Framework\Exception\CouldNotDeleteException + * @dataProvider notAllowedPathsProvider + */ + public function testDeleteDirectoryThatIsNotAllowed(array $paths): void + { + $this->deleteByPaths->execute($paths); + } + + /** + * Provider of paths that are not allowed for deletion + * + * @return array + */ + public function notAllowedPathsProvider(): array + { + return [ + [ + ['../../pub/media'] + ], + [ + ['theme'] + ], + [ + ['../../pub/media', 'theme'] + ] + ]; + } + + /** + * @throws \Magento\Framework\Exception\FileSystemException + */ + public function tearDown() + { + $directory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + if ($directory->isExist($this->testDirectoryName)) { + $directory->delete($this->testDirectoryName); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/IsBlacklistedTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/IsBlacklistedTest.php new file mode 100644 index 0000000000000..47ff2b3a94aa3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/IsBlacklistedTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model; + +use Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for IsPathBlacklistedInterface + */ +class IsBlacklistedTest extends TestCase +{ + + /** + * @var IsPathBlacklistedInterface + */ + private $service; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->service = Bootstrap::getObjectManager()->get(IsPathBlacklistedInterface::class); + } + + /** + * Testing the blacklisted paths + * + * @param string $path + * @param bool $isBlacklisted + * @dataProvider pathsProvider + */ + public function testExecute(string $path, bool $isBlacklisted): void + { + $this->assertEquals($isBlacklisted, $this->service->execute($path)); + } + + /** + * Provider of paths and if the path should be in the blacklist + * + * @return array + */ + public function pathsProvider(): array + { + return [ + ['theme', true], + ['.thumbs', true], + ['catalog/product/somedir', true], + ['catalog/category', false] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetKeywordsTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetKeywordsTest.php new file mode 100644 index 0000000000000..f6b9ed5e1ff75 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetKeywordsTest.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Behat\Gherkin\Keywords\KeywordsInterface; +use Magento\MediaGalleryApi\Api\Data\KeywordInterfaceFactory; +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterfaceFactory; +use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface; +use Magento\MediaGalleryApi\Api\SaveAssetsKeywordsInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Testing assets keywords operation + */ +class AssetKeywordsTest extends TestCase +{ + private const FIXTURE_ASSET_PATH = 'testDirectory/path.jpg'; + + /** + * @var GetAssetsKeywordsInterface + */ + private $saveAssetsKeywords; + + /** + * @var GetAssetsKeywordsInterface + */ + private $getAssetsKeywords; + + /** + * @var AssetKeywordsInterfaceFactory + */ + private $assetsKeywordsFactory; + + /** + * @var KeywordInterfaceFactory + */ + private $keywordFactory; + + /** + * @var GetAssetsByPathsInterface + */ + private $getAssetsByPath; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->saveAssetsKeywords = Bootstrap::getObjectManager()->get(SaveAssetsKeywordsInterface::class); + $this->getAssetsKeywords = Bootstrap::getObjectManager()->get(GetAssetsKeywordsInterface::class); + $this->assetsKeywordsFactory = Bootstrap::getObjectManager()->get(AssetKeywordsInterfaceFactory::class); + $this->keywordFactory = Bootstrap::getObjectManager()->get(KeywordInterfaceFactory::class); + $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); + } + + /** + * Testing assets keywords save and get + * + * @magentoDataFixture Magento/MediaGallery/_files/media_asset.php + * @dataProvider keywordsProvider + * @param array $keywords + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testSaveAndGetKeywords(array $keywords): void + { + $keywords = ['pear', 'plum']; + + $loadedAssets = $this->getAssetsByPath->execute([self::FIXTURE_ASSET_PATH]); + $this->assertEquals(1, count($loadedAssets)); + $loadedAsset = current($loadedAssets); + + $assetKeywords = $this->assetsKeywordsFactory->create( + [ + 'assetId' => $loadedAsset->getId(), + 'keywords' => $this->getKeywords($keywords) + ] + ); + + $this->saveAssetsKeywords->execute([$assetKeywords]); + $loadedAssetKeywords = $this->getAssetsKeywords->execute([$loadedAsset->getId()]); + + $this->assertEquals(1, count($loadedAssetKeywords)); + + /** @var AssetKeywordsInterface $loadedAssetKeyword */ + $loadedAssetKeyword = current($loadedAssetKeywords); + + $loadedKeywords = $loadedAssetKeyword->getKeywords(); + + $this->assertEquals(count($keywords), count($loadedKeywords)); + + $loadedKeywordStrings = []; + foreach ($loadedKeywords as $loadedKeywordObject) { + $loadedKeywordStrings[] = $loadedKeywordObject->getKeyword(); + } + + sort($loadedKeywordStrings); + sort($keywords); + + $this->assertEquals($keywords, $loadedKeywordStrings); + } + + /** + * Data provider of paths matching existing asset + * + * @return array + */ + public function keywordsProvider(): array + { + return [ + [['one-keyword']], + [['кириллица']], + [['plum', 'pear']], + [[]] + ]; + } + + /** + * Create keywords + * + * @param string[] $keywords + * @return KeywordsInterface[] + */ + private function getKeywords(array $keywords): array + { + $keywordObjects = []; + foreach ($keywords as $keyword) { + $keywordObjects[] = $this->keywordFactory->create( + [ + 'keyword' => $keyword + ] + ); + } + return $keywordObjects; + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetsByIdsTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetsByIdsTest.php new file mode 100644 index 0000000000000..de0393372d23d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetsByIdsTest.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for GetAssetsByIdsInterface + */ +class AssetsByIdsTest extends TestCase +{ + private const FIXTURE_ASSET_ID = 2020; + private const FIXTURE_ASSET_PATH = 'testDirectory/path.jpg'; + + /** + * @var GetAssetsByIdsInterface + */ + private $getAssetsByIds; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->getAssetsByIds = Bootstrap::getObjectManager()->get(GetAssetsByIdsInterface::class); + } + + /** + * Testing assets keywords save and get + * + * @throws \Magento\Framework\Exception\LocalizedException + * + * @magentoDataFixture Magento/MediaGallery/_files/media_asset.php + */ + public function testExecute(): void + { + $assets = $this->getAssetsByIds->execute([self::FIXTURE_ASSET_ID]); + $this->assertEquals(1, count($assets)); + $this->assertEquals($assets[0]->getPath(), self::FIXTURE_ASSET_PATH); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetsTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetsTest.php new file mode 100644 index 0000000000000..8aec9454951f9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/AssetsTest.php @@ -0,0 +1,188 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\MediaGalleryApi\Api\SaveAssetsInterface; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for assets operations + */ +class AssetsTest extends TestCase +{ + /** + * @var AssetInterfaceFactory + */ + private $assetFactory; + + /** + * @var SaveAssetsInterface + */ + private $saveAssets; + + /** + * @var GetAssetsByIdsInterface + */ + private $getAssetsByIds; + + /** + * @var GetAssetsByPathsInterface + */ + private $getAssetsByPath; + + /** + * @var DeleteAssetsByPathsInterface + */ + private $deleteAssetsByPaths; + + /** + * @var DataObjectProcessor + */ + private $dataObjectProcessor; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->assetFactory = Bootstrap::getObjectManager()->get(AssetInterfaceFactory::class); + $this->saveAssets = Bootstrap::getObjectManager()->get(SaveAssetsInterface::class); + $this->getAssetsByIds = Bootstrap::getObjectManager()->get(GetAssetsByIdsInterface::class); + $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); + $this->deleteAssetsByPaths = Bootstrap::getObjectManager()->get(DeleteAssetsByPathsInterface::class); + $this->dataObjectProcessor = Bootstrap::getObjectManager()->get(DataObjectProcessor::class); + } + + /** + * Testing assets keywords save and get + * + * @param array $assetsData + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\LocalizedException + * + * @dataProvider assetsDataProvider + */ + public function testExecute(array $assetsData): void + { + $this->saveAssets->execute($this->getAssets($assetsData)); + + $paths = $this->getKeyValues($assetsData, 'path'); + $loadedAssets = $this->getAssetsByPath->execute($paths); + $loadedPaths = $this->getFieldValues($loadedAssets, 'path'); + + $this->assertEquals(count($assetsData), count($loadedAssets)); + + sort($paths); + sort($loadedPaths); + $this->assertEquals($paths, $loadedPaths); + + $this->deleteAssetsByPaths->execute($paths); + $this->assertEmpty($this->getAssetsByPath->execute($paths)); + } + + /** + * Data provider for testExecute + * + * @return array + */ + public function assetsDataProvider(): array + { + return [ + 'One asset' => [ + 'assetsData' => [ + 'asset1' => [ + 'path' => 'fruit.jpg', + 'title' => 'Img', + 'source' => 'Local', + 'contentType' => 'image/jpeg', + 'width' => 420, + 'height' => 240, + 'size' => 12877 + ] + ] + ], + 'Two assets' => [ + 'assetsData' => [ + 'asset1' => [ + 'path' => 'image.jpg', + 'title' => 'Img', + 'source' => 'Local', + 'contentType' => 'image/jpeg', + 'width' => 420, + 'height' => 240, + 'size' => 12877 + ], + 'asset2' => [ + 'path' => 'image2.jpg', + 'title' => 'Img', + 'source' => 'Local', + 'contentType' => 'image/jpeg', + 'width' => 420, + 'height' => 240, + 'size' => 12877 + ] + ] + ], + ]; + } + + /** + * Create assets + * + * @param array $assetsData + * @return AssetInterface[] + */ + private function getAssets(array $assetsData): array + { + $assets = []; + foreach ($assetsData as $assetData) { + $assets[] = $this->assetFactory->create($assetData); + } + return $assets; + } + + /** + * Get field values from assets + * + * @param AssetInterface[] $assets + * @param string $fieldName + * @return string[] + */ + private function getFieldValues(array $assets, string $fieldName): array + { + $values = []; + foreach ($assets as $asset) { + $data = $this->dataObjectProcessor->buildOutputDataArray($asset, AssetInterface::class); + $values[] = $data[$fieldName]; + } + return $values; + } + + /** + * Get key values from assets data array + * + * @param array $assetsData + * @param string $key + * @return string[] + */ + private function getKeyValues(array $assetsData, string $key): array + { + $values = []; + foreach ($assetsData as $assetData) { + $values[] = $assetData[$key]; + } + return $values; + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/DeleteAssetsTest.php b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/DeleteAssetsTest.php new file mode 100644 index 0000000000000..0b790730805e9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/Model/ResourceModel/DeleteAssetsTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallery\Model\ResourceModel; + +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Testing delete assets operation + */ +class DeleteAssetsTest extends TestCase +{ + private const FIXTURE_ASSET_PATH = 'testDirectory/path.jpg'; + /** + * @var GetAssetsByPathsInterface + */ + private $getAssetsByPath; + + /** + * @var DeleteAssetsByPathsInterface + */ + private $deleteAssetsByPaths; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); + $this->deleteAssetsByPaths = Bootstrap::getObjectManager()->get(DeleteAssetsByPathsInterface::class); + } + + /** + * Test deletion of assets by path + * + * @magentoDataFixture Magento/MediaGallery/_files/media_asset.php + * + * @param array $paths + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\LocalizedException + * + * @dataProvider matchingPathsProvider + */ + public function testAssetsAreDeleted(array $paths): void + { + $this->deleteAssetsByPaths->execute($paths); + $this->assertEmpty($this->getAssetsByPath->execute([self::FIXTURE_ASSET_PATH])); + } + + /** + * Test scenarios where delete operation should not delete an asset + * + * @magentoDataFixture Magento/MediaGallery/_files/media_asset.php + * + * @param array $paths + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\LocalizedException + * + * @dataProvider notMatchingPathsProvider + */ + public function testAssetsAreNotDeleted(array $paths): void + { + $this->deleteAssetsByPaths->execute($paths); + $this->assertNotEmpty($this->getAssetsByPath->execute([self::FIXTURE_ASSET_PATH])); + } + + /** + * Data provider of paths matching existing asset + * + * @return array + */ + public function matchingPathsProvider(): array + { + return [ + [['testDirectory/path.jpg']], + [['testDirectory/']], + [['testDirectory']] + ]; + } + + /** + * Data provider of paths not matching existing asset + * + * @return array + */ + public function notMatchingPathsProvider(): array + { + return [ + [['testDirectory/path.png']], + [['anotherDirectory/path.jpg']], + [['path.jpg']] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/_files/media_asset.php b/dev/tests/integration/testsuite/Magento/MediaGallery/_files/media_asset.php new file mode 100644 index 0000000000000..1a2dce9e032fa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/_files/media_asset.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; +use Magento\MediaGalleryApi\Api\SaveAssetsInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var AssetInterfaceFactory $mediaAssetFactory */ +$mediaAssetFactory = $objectManager->get(AssetInterfaceFactory::class); +/** @var AssetInterface $mediaAsset */ +$mediaAsset = $mediaAssetFactory->create( + [ + 'id' => 2020, + 'path' => 'testDirectory/path.jpg', + 'contentType' => 'image', + 'title' => 'Img', + 'source' => 'Local', + 'width' => 420, + 'height' => 240, + 'size' => 12877 + ] +); +/** @var SaveAssetsInterface $mediaSave */ +$mediaSave = $objectManager->get(SaveAssetsInterface::class); +$mediaId = $mediaSave->execute([$mediaAsset]); diff --git a/dev/tests/integration/testsuite/Magento/MediaGallery/_files/media_asset_rollback.php b/dev/tests/integration/testsuite/Magento/MediaGallery/_files/media_asset_rollback.php new file mode 100644 index 0000000000000..193e26916fdd3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallery/_files/media_asset_rollback.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\MediaGalleryApi\Api\DeleteAssetsByPathsInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var DeleteAssetsByPathsInterface $mediaSave */ +$mediaAssetDelete = $objectManager->get(DeleteAssetsByPathsInterface::class); + +try { + $mediaAssetDelete->execute(['testDirectory/path.jpg']); +} catch (\Exception $exception) { + +} diff --git a/dev/tests/integration/testsuite/Magento/MsrpGroupedProduct/Pricing/MsrpPriceCalculatorTest.php b/dev/tests/integration/testsuite/Magento/MsrpGroupedProduct/Pricing/MsrpPriceCalculatorTest.php new file mode 100644 index 0000000000000..4a35f6fe552f4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MsrpGroupedProduct/Pricing/MsrpPriceCalculatorTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MsrpGroupedProduct\Pricing; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test group product minimum advertised price model + */ +class MsrpPriceCalculatorTest extends TestCase +{ + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + /** + * @var MsrpPriceCalculator + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp() + { + parent::setUp(); + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->model = $objectManager->get(MsrpPriceCalculator::class); + } + + /** + * Test grouped product minimum advertised price + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/GroupedProduct/_files/product_grouped.php + * @dataProvider getMsrpPriceValueDataProvider + * @param float|null $simpleProductPriceMsrp + * @param float|null $virtualProductMsrp + * @param float|null $expectedMsrp + */ + public function testGetMsrpPriceValue( + ?float $simpleProductPriceMsrp, + ?float $virtualProductMsrp, + ?float $expectedMsrp + ): void { + $this->setProductMinimumAdvertisedPrice('simple', $simpleProductPriceMsrp); + $this->setProductMinimumAdvertisedPrice('virtual-product', $virtualProductMsrp); + $groupedProduct = $this->getProduct('grouped-product'); + $this->assertEquals($expectedMsrp, $this->model->getMsrpPriceValue($groupedProduct)); + } + + /** + * Set product minimum advertised price by sku + * + * @param string $sku + * @param float|null $msrp + */ + private function setProductMinimumAdvertisedPrice(string $sku, ?float $msrp): void + { + $product = $this->getProduct($sku); + $product->setMsrp($msrp); + $this->productRepository->save($product); + } + + /** + * Get product by sku + * + * @param string $sku + * @return ProductInterface + */ + private function getProduct(string $sku): ProductInterface + { + return $this->productRepository->get($sku, false, null, true); + } + + /** + * @return array + */ + public function getMsrpPriceValueDataProvider(): array + { + return [ + [ + 12.0, + 8.0, + 8.0 + ], + [ + 12.0, + null, + 12.0 + ], + [ + null, + null, + 0.0 + ] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php index 06c8902f45897..ee79ff9ad0e63 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php @@ -89,7 +89,7 @@ public function testUnsubscribeSubscribe(): void $this->assertEquals($subscriber, $subscriber->unsubscribe()); $this->assertContains( 'You have been unsubscribed from the newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); // Subscribe and verify @@ -97,7 +97,7 @@ public function testUnsubscribeSubscribe(): void $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); $this->assertContains( 'You have been successfully subscribed to our newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); } @@ -116,14 +116,14 @@ public function testUnsubscribeSubscribeByCustomerId(): void $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); $this->assertContains( 'You have been unsubscribed from the newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); // Subscribe and verify $this->assertSame($subscriber, $subscriber->subscribeCustomerById(1)); $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); $this->assertContains( 'You have been successfully subscribed to our newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); } @@ -143,7 +143,7 @@ public function testConfirm(): void $subscriber->confirm($subscriber->getSubscriberConfirmCode()); $this->assertContains( 'You have been successfully subscribed to our newsletter.', - $this->transportBuilder->getSentMessage()->getRawMessage() + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); } diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php index 0b2a4124dcc2e..7e604de42f35c 100644 --- a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php +++ b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php @@ -101,8 +101,8 @@ public function testSend($isCustomerIdUsed) $this->_emailModel->send(); $this->assertContains( - 'Smith,', - $this->transportBuilder->getSentMessage()->getRawMessage() + 'John Smith,', + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); } diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php index e4721f515a320..edc4e05dbbc80 100644 --- a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php +++ b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php @@ -70,8 +70,8 @@ public function testProcess() { $this->observer->process(); $this->assertContains( - 'ohn Smith,', - $this->transportBuilder->getSentMessage()->getRawMessage() + 'John Smith,', + $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent() ); } diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php index c080d7a3ab229..e2f86dd17bfdd 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Quote\Model; use Magento\Customer\Api\AddressRepositoryInterface; @@ -12,11 +11,19 @@ use Magento\Customer\Api\Data\GroupInterface; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Customer\Model\Vat; +use Magento\Customer\Observer\AfterAddressSaveObserver; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Config\MutableScopeConfigInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\ObjectManagerInterface; use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\AddressInterfaceFactory; +use Magento\Quote\Api\Data\EstimateAddressInterface; +use Magento\Quote\Api\GuestShippingMethodManagementInterface; use Magento\Quote\Api\ShippingMethodManagementInterface; +use Magento\Quote\Observer\Frontend\Quote\Address\CollectTotalsObserver; +use Magento\Quote\Observer\Frontend\Quote\Address\VatValidator; use Magento\Store\Model\ScopeInterface; use Magento\Tax\Api\Data\TaxClassInterface; use Magento\Tax\Api\TaxClassRepositoryInterface; @@ -24,6 +31,7 @@ use Magento\Tax\Model\Config as TaxConfig; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Quote\Model\GetQuoteByReservedOrderId; +use PHPUnit\Framework\TestCase; /** * Test for shipping methods management @@ -31,7 +39,7 @@ * @magentoDbIsolation enabled * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class ShippingMethodManagementTest extends \PHPUnit\Framework\TestCase +class ShippingMethodManagementTest extends TestCase { /** @var ObjectManagerInterface $objectManager */ private $objectManager; @@ -56,14 +64,14 @@ protected function setUp() * @magentoDataFixture Magento/SalesRule/_files/cart_rule_100_percent_off.php * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php * @return void - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws NoSuchEntityException */ public function testRateAppliedToShipping(): void { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); - /** @var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */ - $quoteRepository = $objectManager->create(\Magento\Quote\Api\CartRepositoryInterface::class); + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $objectManager->create(CartRepositoryInterface::class); $customerQuote = $quoteRepository->getForCustomer(1); $this->assertEquals(0, $customerQuote->getBaseGrandTotal()); } @@ -80,17 +88,17 @@ public function testRateAppliedToShipping(): void */ public function testTableRateFreeShipping() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = $objectManager->get(\Magento\Quote\Model\Quote::class); + $objectManager = Bootstrap::getObjectManager(); + /** @var Quote $quote */ + $quote = $objectManager->get(Quote::class); $quote->load('test01', 'reserved_order_id'); $cartId = $quote->getId(); if (!$cartId) { $this->fail('quote fixture failed'); } - /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ - $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + /** @var QuoteIdMask $quoteIdMask */ + $quoteIdMask = Bootstrap::getObjectManager() + ->create(QuoteIdMaskFactory::class) ->create(); $quoteIdMask->load($cartId, 'quote_id'); //Use masked cart Id @@ -103,10 +111,10 @@ public function testTableRateFreeShipping() 'region_id' => null ] ]; - /** @var \Magento\Quote\Api\Data\EstimateAddressInterface $address */ - $address = $objectManager->create(\Magento\Quote\Api\Data\EstimateAddressInterface::class, $data); - /** @var \Magento\Quote\Api\GuestShippingMethodManagementInterface $shippingEstimation */ - $shippingEstimation = $objectManager->get(\Magento\Quote\Api\GuestShippingMethodManagementInterface::class); + /** @var EstimateAddressInterface $address */ + $address = $objectManager->create(EstimateAddressInterface::class, $data); + /** @var GuestShippingMethodManagementInterface $shippingEstimation */ + $shippingEstimation = $objectManager->get(GuestShippingMethodManagementInterface::class); $result = $shippingEstimation->estimateByAddress($cartId, $address); $this->assertNotEmpty($result); $expectedResult = [ @@ -134,25 +142,25 @@ public function testTableRateFreeShipping() */ public function testTableRateWithCartRuleForFreeShipping() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); $quote = $this->getQuote('tableRate'); $cartId = $quote->getId(); if (!$cartId) { $this->fail('quote fixture failed'); } - /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ - $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + /** @var QuoteIdMask $quoteIdMask */ + $quoteIdMask = Bootstrap::getObjectManager() + ->create(QuoteIdMaskFactory::class) ->create(); $quoteIdMask->load($cartId, 'quote_id'); //Use masked cart Id $cartId = $quoteIdMask->getMaskedId(); - $addressFactory = $this->objectManager->get(\Magento\Quote\Api\Data\AddressInterfaceFactory::class); + $addressFactory = $this->objectManager->get(AddressInterfaceFactory::class); /** @var \Magento\Quote\Api\Data\AddressInterface $address */ $address = $addressFactory->create(); $address->setCountryId('US'); - /** @var \Magento\Quote\Api\GuestShippingMethodManagementInterface $shippingEstimation */ - $shippingEstimation = $objectManager->get(\Magento\Quote\Api\GuestShippingMethodManagementInterface::class); + /** @var GuestShippingMethodManagementInterface $shippingEstimation */ + $shippingEstimation = $objectManager->get(GuestShippingMethodManagementInterface::class); $result = $shippingEstimation->estimateByExtendedAddress($cartId, $address); $this->assertCount(1, $result); $rate = reset($result); @@ -234,17 +242,17 @@ public function testEstimateByAddress() */ private function executeTestFlow($flatRateAmount, $tableRateAmount) { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = $objectManager->get(\Magento\Quote\Model\Quote::class); + $objectManager = Bootstrap::getObjectManager(); + /** @var Quote $quote */ + $quote = $objectManager->get(Quote::class); $quote->load('test01', 'reserved_order_id'); $cartId = $quote->getId(); if (!$cartId) { $this->fail('quote fixture failed'); } - /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ - $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + /** @var QuoteIdMask $quoteIdMask */ + $quoteIdMask = Bootstrap::getObjectManager() + ->create(QuoteIdMaskFactory::class) ->create(); $quoteIdMask->load($cartId, 'quote_id'); //Use masked cart Id @@ -257,17 +265,17 @@ private function executeTestFlow($flatRateAmount, $tableRateAmount) 'region_id' => null ] ]; - /** @var \Magento\Quote\Api\Data\EstimateAddressInterface $address */ - $address = $objectManager->create(\Magento\Quote\Api\Data\EstimateAddressInterface::class, $data); - /** @var \Magento\Quote\Api\GuestShippingMethodManagementInterface $shippingEstimation */ - $shippingEstimation = $objectManager->get(\Magento\Quote\Api\GuestShippingMethodManagementInterface::class); + /** @var EstimateAddressInterface $address */ + $address = $objectManager->create(EstimateAddressInterface::class, $data); + /** @var GuestShippingMethodManagementInterface $shippingEstimation */ + $shippingEstimation = $objectManager->get(GuestShippingMethodManagementInterface::class); $result = $shippingEstimation->estimateByAddress($cartId, $address); $this->assertNotEmpty($result); $expectedResult = [ 'tablerate' => [ - 'method_code' => 'bestway', - 'amount' => $tableRateAmount - ], + 'method_code' => 'bestway', + 'amount' => $tableRateAmount + ], 'flatrate' => [ 'method_code' => 'flatrate', 'amount' => $flatRateAmount @@ -295,15 +303,15 @@ private function executeTestFlow($flatRateAmount, $tableRateAmount) */ public function testEstimateByAddressWithInclExclTaxAndVATGroup() { - /** @var CustomerRepositoryInterface $customerRepository */ - $customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); - $customer = $customerRepository->get('customer@example.com'); - /** @var GroupInterface $customerGroup */ $customerGroup = $this->findCustomerGroupByCode('custom_group'); + $this->mockCustomerVat((int)$customerGroup->getId()); + $customerGroup->setTaxClassId($this->getTaxClass('CustomerTaxClass')->getClassId()); $this->groupRepository->save($customerGroup); - + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); + $customer = $customerRepository->get('customer@example.com'); $customer->setGroupId($customerGroup->getId()); $customer->setTaxvat('12'); $customerRepository->save($customer); @@ -320,11 +328,46 @@ public function testEstimateByAddressWithInclExclTaxAndVATGroup() $this->assertEquals(5.0, $result[0]->getPriceExclTax()); } + /** + * Create a test double fot customer vat class + * + * @param int $customerGroupId + */ + private function mockCustomerVat(int $customerGroupId): void + { + $gatewayResponse = new DataObject([ + 'is_valid' => false, + 'request_date' => '', + 'request_identifier' => '123123123', + 'request_success' => false, + 'request_message' => __('Error during VAT Number verification.'), + ]); + $customerVat = $this->createPartialMock( + Vat::class, + [ + 'checkVatNumber', + 'isCountryInEU', + 'getCustomerGroupIdBasedOnVatNumber', + 'getMerchantCountryCode', + 'getMerchantVatNumber' + ] + ); + $customerVat->method('checkVatNumber')->willReturn($gatewayResponse); + $customerVat->method('isCountryInEU')->willReturn(true); + $customerVat->method('getMerchantCountryCode')->willReturn('GB'); + $customerVat->method('getMerchantVatNumber')->willReturn('11111'); + $customerVat->method('getCustomerGroupIdBasedOnVatNumber')->willReturn($customerGroupId); + $this->objectManager->removeSharedInstance(Vat::class); + $this->objectManager->addSharedInstance($customerVat, Vat::class); + + // Remove instances where the customer vat object is cached + $this->objectManager->removeSharedInstance(CollectTotalsObserver::class); + } + /** * Find the group with a given code. * * @param string $code - * * @return GroupInterface */ protected function findCustomerGroupByCode(string $code): ?GroupInterface diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php index b75501911be6b..207899ceca3e5 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php @@ -10,16 +10,20 @@ namespace Magento\Sales\Block\Adminhtml\Order\Create\Form; use Magento\Backend\Model\Session\Quote as SessionQuote; +use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\AttributeMetadataInterface; use Magento\Customer\Api\Data\AttributeMetadataInterfaceFactory; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Model\Data\Option; use Magento\Customer\Model\Metadata\Form; use Magento\Customer\Model\Metadata\FormFactory; use Magento\Framework\View\LayoutInterface; use Magento\Quote\Model\Quote; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * Class for test Account @@ -27,7 +31,7 @@ * @magentoAppArea adminhtml * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class AccountTest extends \PHPUnit\Framework\TestCase +class AccountTest extends TestCase { /** * @var Account @@ -81,9 +85,9 @@ public function testGetFormWithCustomer() ); $fixtureCustomerId = 1; - /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ - $customerRepository = $this->objectManager->get(\Magento\Customer\Api\CustomerRepositoryInterface::class); - /** @var \Magento\Customer\Api\Data\CustomerInterface $customer */ + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); + /** @var CustomerInterface $customer */ $customer = $customerRepository->getById($fixtureCustomerId); $customer->setGroupId($customerGroup); $customerRepository->save($customer); @@ -125,8 +129,8 @@ public function testGetFormWithCustomer() */ public function testGetFormWithUserDefinedAttribute() { - /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ - $storeManager = Bootstrap::getObjectManager()->get(\Magento\Store\Model\StoreManagerInterface::class); + /** @var StoreManagerInterface $storeManager */ + $storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); $secondStore = $storeManager->getStore('secondstore'); $quoteSession = $this->objectManager->get(SessionQuote::class); @@ -159,6 +163,46 @@ public function testGetFormWithUserDefinedAttribute() ); } + /** + * Test for get form with default customer group + * + */ + public function testGetFormWithDefaultCustomerGroup() + { + $customerGroup = 0; + $quote = $this->objectManager->create(Quote::class); + $quote->setCustomerGroupId($customerGroup); + + $this->session = $this->getMockBuilder(SessionQuote::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomerId', 'getQuote']) + ->getMock(); + $this->session->method('getQuote') + ->willReturn($quote); + $this->session->method('getCustomerId') + ->willReturn(1); + + $formFactory = $this->getFormFactoryMock(); + $this->objectManager->addSharedInstance($formFactory, FormFactory::class); + + /** @var LayoutInterface $layout */ + $layout = $this->objectManager->get(LayoutInterface::class); + $accountBlock = $layout->createBlock( + Account::class, + 'address_block' . rand(), + ['sessionQuote' => $this->session] + ); + + $expectedGroupId = 1; + $form = $accountBlock->getForm(); + + self::assertEquals( + $expectedGroupId, + $form->getElement('group_id')->getValue(), + 'The Customer Group specified for the chosen customer should be selected.' + ); + } + /** * Creates a mock for Form object. * diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/TotalsTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/TotalsTest.php new file mode 100644 index 0000000000000..e16ed1ef98ad5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/TotalsTest.php @@ -0,0 +1,178 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Block\Adminhtml\Order; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Phrase; +use Magento\Framework\View\LayoutInterface; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Block\Adminhtml\Order\Totals\Tax; +use Magento\Sales\Model\Order; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * @magentoAppArea adminhtml + */ +class TotalsTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $om; + + /** + * @var LayoutInterface + */ + private $layout; + + /** + * @var OrderInterfaceFactory + */ + private $orderFactory; + + /** + * @inheritDoc + */ + public function setUp() + { + $this->om = Bootstrap::getObjectManager(); + $this->layout = $this->om->get(LayoutInterface::class); + $this->orderFactory = $this->om->get(OrderInterfaceFactory::class); + } + + /** + * Test block totals including tax. + * + * @magentoConfigFixture default_store tax/sales_display/subtotal 2 + * @magentoConfigFixture default_store tax/sales_display/shipping 2 + * + * @magentoDataFixture Magento/Sales/_files/order.php + * + * @return void + */ + public function testTotalsInclTax(): void + { + $order = $this->prepareOrderInclTax('100000001'); + + $blockTotals = $this->getBlockTotals()->setOrder($order); + $this->assertSubtotal($blockTotals->toHtml(), (float) $order->getSubtotal()); + $this->assertShipping($blockTotals->toHtml(), (float) $order->getShippingAmount()); + + $blockTax = $this->getBlockTax(); + $blockTotals->setChild('child_tax_block', $blockTax); + $blockTax->initTotals(); + + $this->assertSubtotal($blockTotals->toHtml(), (float) $order->getSubtotalInclTax()); + $this->assertShipping($blockTotals->toHtml(), (float) $order->getShippingInclTax()); + } + + /** + * Check if subtotal amount present in block. + * + * @param string $blockTotalsHtml + * @param float $amount + * @return void + */ + private function assertSubtotal(string $blockTotalsHtml, float $amount): void + { + $this->assertTrue( + $this->isBlockContainsTotalAmount($blockTotalsHtml, __('Subtotal'), $amount), + 'Subtotal amount is missing or incorrect.' + ); + } + + /** + * Check if shipping amount present in block. + * + * @param string $blockTotalsHtml + * @param float $amount + * @return void + */ + private function assertShipping(string $blockTotalsHtml, float $amount): void + { + $this->assertTrue( + $this->isBlockContainsTotalAmount($blockTotalsHtml, __('Shipping & Handling'), $amount), + 'Shipping & Handling amount is missing or incorrect.' + ); + } + + /** + * Prepare order for test. + * + * @param string $incrementId + * @return Order + */ + private function prepareOrderInclTax(string $incrementId): Order + { + /** @var Order $order */ + $order = $this->orderFactory->create()->loadByIncrementId($incrementId); + + $order->setSubtotalInclTax(110); + $order->setBaseSubtotalInclTax(110); + + $order->setShippingAmount(10); + $order->setBaseShippingAmount(10); + $order->setShippingInclTax(11); + $order->setBaseShippingInclTax(11); + + return $order; + } + + /** + * Create block totals. + * + * @return Totals + */ + private function getBlockTotals(): Totals + { + /** @var Totals $block */ + $block = $this->layout->createBlock(Totals::class, 'block_totals'); + $block->setTemplate('Magento_Sales::order/totals.phtml'); + + return $block; + } + + /** + * Create block tax. + * + * @return Tax + */ + private function getBlockTax(): Tax + { + /** @var Tax $block */ + $block = $this->layout->createBlock(Tax::class, 'block_tax'); + $block->setTemplate('Magento_Sales::order/totals/tax.phtml'); + + return $block; + } + + /** + * Check if amount present in appropriate block node. + * + * @param string $blockTotalsHtml + * @param Phrase $totalLabel + * @param float $totalAmount + * @return bool + */ + private function isBlockContainsTotalAmount( + string $blockTotalsHtml, + Phrase $totalLabel, + float $totalAmount + ): bool { + $dom = new \DOMDocument(); + $dom->loadHTML($blockTotalsHtml); + $query = sprintf( + "//tr[contains(., '%s')]//span[contains(text(), '%01.2f')]", + $totalLabel, + $totalAmount + ); + + return (bool) (new \DOMXPath($dom))->query($query)->count(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php index 80dfc17f522f1..86da51ac40f97 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Guest/FormTest.php @@ -55,7 +55,7 @@ public function testAttemptToOpenTheFormAsLoggedIn() { $this->login(1); $this->dispatch('sales/guest/form/'); - $this->assertRedirect($this->stringContains('customer/account')); + $this->assertRedirect($this->stringContains('sales/order/history')); } /** diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php index 4f5c332269cf0..05c12eee3418a 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/lib/internal/Magento/Framework/Test/Unit/View/Element/UiComponentFactoryTest.php @@ -6,6 +6,8 @@ namespace Magento\Framework\Test\Unit\View\Element; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\View\Element\UiComponent\DataProvider\Sanitizer; +use PHPUnit\Framework\MockObject\MockObject; class UiComponentFactoryTest extends \PHPUnit\Framework\TestCase { @@ -15,25 +17,25 @@ class UiComponentFactoryTest extends \PHPUnit\Framework\TestCase /** @var ObjectManagerHelper */ protected $objectManagerHelper; - /** @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\ObjectManagerInterface|MockObject */ protected $objectManagerMock; - /** @var \Magento\Framework\Data\Argument\InterpreterInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Data\Argument\InterpreterInterface|MockObject */ protected $interpreterMock; - /** @var \Magento\Framework\View\Element\UiComponent\ContextFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\View\Element\UiComponent\ContextFactory|MockObject */ protected $contextFactoryMock; - /** @var \Magento\Framework\Config\DataInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Config\DataInterfaceFactory|MockObject */ protected $dataInterfaceFactoryMock; - /** @var \SafeReflectionClass|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \SafeReflectionClass|MockObject */ protected $safeReflectionClassMock; - /** @var \SafeReflectionClass|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \SafeReflectionClass|MockObject */ protected $safeReflectionClassMock2; - /** @var \Magento\Ui\Config\Reader\Definition\Data|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Ui\Config\Reader\Definition\Data|MockObject */ protected $dataMock; protected function setUp() @@ -58,6 +60,9 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->dataMock = $this->createMock(\Magento\Framework\Config\DataInterface::class); + $sanitizerMock = $this->createMock(Sanitizer::class); + $sanitizerMock->method('sanitize')->willReturnArgument(0); + $sanitizerMock->method('sanitizeComponentMetadata')->willReturnArgument(0); $this->objectManagerHelper = new ObjectManagerHelper($this); $this->model = $this->objectManagerHelper->getObject( \Magento\Framework\View\Element\UiComponentFactory::class, @@ -68,7 +73,8 @@ protected function setUp() 'configFactory' => $this->dataInterfaceFactoryMock, 'data' => [], 'componentChildFactories' => [], - 'definitionData' => $this->dataMock + 'definitionData' => $this->dataMock, + 'sanitizer' => $sanitizerMock ] ); } diff --git a/dev/tests/integration/testsuite/Magento/Shipping/_files/track.php b/dev/tests/integration/testsuite/Magento/Shipping/_files/track.php new file mode 100644 index 0000000000000..bf6b5a102ff48 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/_files/track.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\ShipmentTrackRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Shipment; +use Magento\Sales\Model\Order\Shipment\Track; +use Magento\Sales\Model\Order\ShipmentFactory; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../../Magento/Sales/_files/order.php'; + +/** @var Order $order */ +$items = []; +foreach ($order->getItems() as $orderItem) { + $items[$orderItem->getId()] = $orderItem->getQtyOrdered(); +} + +/** @var Shipment $shipment */ +$shipment = Bootstrap::getObjectManager()->get(ShipmentFactory::class)->create($order, $items); +$shipment->setPackages([['1'], ['2']]); +$shipment->setShipmentStatus(Shipment::STATUS_NEW); +$shipment->save(); + +/** @var Track $track */ +$track = Bootstrap::getObjectManager()->create(Track::class); +$track->setOrderId($order->getId()); +$track->setParentId($shipment->getId()); +$track->setTitle('Shipment Title'); +$track->setCarrierCode(Track::CUSTOM_CARRIER_CODE); +$track->setTrackNumber('track_number'); +$track->setDescription('Description of shipment'); + +/** @var ShipmentTrackRepositoryInterface $shipmentTrackRepository */ +$shipmentTrackRepository = Bootstrap::getObjectManager()->get(ShipmentTrackRepositoryInterface::class); +$shipmentTrackRepository->save($track); diff --git a/dev/tests/integration/testsuite/Magento/Shipping/_files/track_rollback.php b/dev/tests/integration/testsuite/Magento/Shipping/_files/track_rollback.php new file mode 100644 index 0000000000000..c401f4052b190 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/_files/track_rollback.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\Shipping\Model\Order\Track; +use Magento\Shipping\Model\ResourceModel\Order\Track\Collection; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../../Magento/Sales/_files/order_rollback.php'; + +/** @var Registry $registry */ +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$trackCollection = Bootstrap::getObjectManager()->create(Collection::class); +/** @var $track Track */ +foreach ($trackCollection as $track) { + $track->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de_rollback.php b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de_rollback.php index a87a031ee78a6..0004002dc6d26 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de_rollback.php @@ -6,6 +6,8 @@ declare(strict_types=1); use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Registry; +use Magento\Tax\Api\TaxClassManagementInterface; use Magento\Tax\Model\ClassModel; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\ObjectManagerInterface; @@ -18,10 +20,6 @@ /** @var ObjectManagerInterface $objectManager */ $objectManager = Bootstrap::getObjectManager(); -$taxClasses = [ - 'CustomerTaxClass', - 'ProductTaxClass', -]; $taxRuleRepository = $objectManager->get(TaxRuleRepositoryInterface::class); /** @var SearchCriteriaBuilder $searchBuilder */ $searchBuilder = $objectManager->get(SearchCriteriaBuilder::class); @@ -29,6 +27,11 @@ ->create(); $taxRules = $taxRuleRepository->getList($searchCriteria) ->getItems(); + +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); foreach ($taxRules as $taxRule) { try { $taxRuleRepository->delete($taxRule); @@ -36,12 +39,17 @@ //Rule already removed } } -$searchCriteria = $searchBuilder->addFilter(ClassModel::KEY_NAME, $taxClasses, 'in') - ->create(); -/** @var TaxClassRepositoryInterface $groupRepository */ + +/** @var TaxClassRepositoryInterface $taxClassRepository */ $taxClassRepository = $objectManager->get(TaxClassRepositoryInterface::class); -$taxClasses = $taxClassRepository->getList($searchCriteria) - ->getItems(); +$searchCriteria = $searchBuilder->addFilter(ClassModel::KEY_NAME, 'CustomerTaxClass') + ->addFilter(ClassModel::KEY_TYPE, TaxClassManagementInterface::TYPE_CUSTOMER) + ->create(); +$taxClasses = $taxClassRepository->getList($searchCriteria)->getItems(); +$searchCriteria = $searchBuilder->addFilter(ClassModel::KEY_NAME, 'ProductTaxClass') + ->addFilter(ClassModel::KEY_TYPE, TaxClassManagementInterface::TYPE_PRODUCT) + ->create(); +$taxClasses = array_merge($taxClasses, $taxClassRepository->getList($searchCriteria)->getItems()); foreach ($taxClasses as $taxClass) { try { $taxClassRepository->delete($taxClass); @@ -49,6 +57,7 @@ //TaxClass already removed } } + $searchCriteria = $searchBuilder->addFilter(Rate::KEY_CODE, 'Denmark') ->create(); /** @var TaxRateRepositoryInterface $groupRepository */ @@ -62,3 +71,5 @@ //TaxRate already removed } } +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Ui/Component/Form/Element/MultiSelectTest.php b/dev/tests/integration/testsuite/Magento/Ui/Component/Form/Element/MultiSelectTest.php new file mode 100644 index 0000000000000..6c7e369419a55 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Ui/Component/Form/Element/MultiSelectTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Ui\Component\Form\Element; + +use Magento\Framework\Data\OptionSourceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test MultiSelect component. + */ +class MultiSelectTest extends TestCase +{ + /** + * @var MultiSelectFactory + */ + private $factory; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->factory = Bootstrap::getObjectManager()->get(MultiSelectFactory::class); + } + + /** + * Options data to verify + * + * @return array + */ + public function getTestOptions(): array + { + return [ + 'List' => [ + [ + ['value' => '${\'my-value\'}', 'label' => 'My label'], + ['value' => '1', 'label' => 'Label'], + ['value' => '${\'my-value-2\'}', 'label' => 'This is ${\'My label\'}'] + ], + [ + ['value' => '${\'my-value\'}', 'label' => 'My label', '__disableTmpl' => ['value' => true]], + ['value' => '1', 'label' => 'Label'], + [ + 'value' => '${\'my-value-2\'}', + 'label' => 'This is ${\'My label\'}', + '__disableTmpl' => ['value' => true, 'label' => true] + ] + ] + ], + 'provider' => [ + new class implements OptionSourceInterface + { + /** + * @inheritDoc + */ + public function toOptionArray() + { + return [['value' => '${\'value\'}', 'label' => 'Test']]; + } + }, + [['value' => '${\'value\'}', 'label' => 'Test', '__disableTmpl' => ['value' => true]]] + ] + ]; + } + + /** + * Check that options received from an options provider properly initiated. + * + * @param array|OptionSourceInterface $options Options provided + * @param array $expected Expected initialized options + * @return void + * @dataProvider getTestOptions + */ + public function testOptions($options, array $expected): void + { + /** @var MultiSelect $component */ + $component = $this->factory->create(['options' => $options]); + $component->prepare(); + + $this->assertEquals($expected, $component->getData('config')['options']); + } +} diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js deleted file mode 100644 index 0420256e20ef5..0000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/in-context/express-checkout.test.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -/* eslint-disable max-nested-callbacks */ -define([ - 'squire', - 'jquery' -], function (Squire, $) { - 'use strict'; - - describe('Magento_Paypal/js/in-context/express-checkout', function () { - - var model, - event, - paypalExpressCheckout, - injector = new Squire(), - mocks = { - 'paypalInContextExpressCheckout': { - checkout: jasmine.createSpyObj('checkout', - ['setup', 'initXO', 'startFlow', 'closeFlow'] - ) - }, - 'Magento_Customer/js/customer-data': { - set: jasmine.createSpy(), - invalidate: jasmine.createSpy() - } - }; - - /** - * Run before each test method - * - * @return void - */ - beforeEach(function (done) { - event = { - /** Stub */ - preventDefault: jasmine.createSpy('preventDefault') - }; - - injector.mock(mocks); - - injector.require([ - 'paypalInContextExpressCheckout', - 'Magento_Paypal/js/in-context/express-checkout'], function (PayPal, Constr) { - paypalExpressCheckout = PayPal; - model = new Constr(); - - done(); - }); - }); - - afterEach(function () { - try { - injector.clean(); - injector.remove(); - } catch (e) {} - }); - - describe('clientConfig.click method', function () { - - it('Check for properties defined ', function () { - expect(model.hasOwnProperty('clientConfig')).toBeDefined(); - expect(model.clientConfig.hasOwnProperty('click')).toBeDefined(); - expect(model.clientConfig.hasOwnProperty('checkoutInited')).toBeDefined(); - }); - - it('Check properties type', function () { - expect(typeof model.clientConfig.checkoutInited).toEqual('boolean'); - expect(typeof model.clientConfig.click).toEqual('function'); - }); - - it('Check properties value', function () { - expect(model.clientConfig.checkoutInited).toEqual(false); - }); - - it('Check call "click" method', function () { - - spyOn(jQuery.fn, 'trigger'); - spyOn(jQuery, 'get').and.callFake(function () { - var d = $.Deferred(); - - d.resolve({ - 'url': true - }); - - return d.promise(); - }); - - model.clientConfig.click(event); - - expect(event.preventDefault).toHaveBeenCalled(); - expect(paypalExpressCheckout.checkout.initXO).toHaveBeenCalled(); - expect(model.clientConfig.checkoutInited).toEqual(true); - expect(jQuery.get).toHaveBeenCalled(); - expect(jQuery('body').trigger).toHaveBeenCalledWith( - jasmine.arrayContaining(['processStart'], ['processStop']) - ); - }); - - it('Check call "click" method', function () { - var message = { - text: 'text', - type: 'error' - }; - - spyOn(jQuery.fn, 'trigger'); - spyOn(jQuery, 'get').and.callFake(function () { - var d = $.Deferred(); - - d.resolve({ - message: message - }); - - return d.promise(); - }); - - model.clientConfig.click(event); - expect(mocks['Magento_Customer/js/customer-data'].set).toHaveBeenCalledWith('messages', { - messages: [message] - }); - expect(event.preventDefault).toHaveBeenCalled(); - expect(paypalExpressCheckout.checkout.initXO).toHaveBeenCalled(); - expect(model.clientConfig.checkoutInited).toEqual(true); - expect(jQuery.get).toHaveBeenCalled(); - expect(jQuery('body').trigger).toHaveBeenCalledWith( - jasmine.arrayContaining(['processStart'], ['processStop']) - ); - }); - }); - }); -}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/image-uploader.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/image-uploader.test.js index 31d1c1ce27e96..34deb9fe04ba6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/image-uploader.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/image-uploader.test.js @@ -78,7 +78,10 @@ define([ '?isAjax=true¤t_tree_path=d3lzaXd5Zw,,', null, null, - 'Hello world' + 'Hello world', + { + targetElementId: 'theTargetId' + } ); }); }); diff --git a/dev/tests/js/jasmine/tests/lib/mage/browser.test.js b/dev/tests/js/jasmine/tests/lib/mage/browser.test.js index 06f355908daca..d5687a0d9737d 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/browser.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/browser.test.js @@ -37,6 +37,7 @@ define([ */ done: function () { obj.targetElementId = 1; + obj.modalLoaded = true; } }; }); @@ -58,8 +59,8 @@ define([ } }; }); - obj.openDialog('instance/url', 100, 100, 'title', undefined); - obj.openDialog('instance/url', 100, 100, 'title', undefined); + obj.openDialog('instance/url/target_element_id/YDW2424/', 100, 100, 'title', undefined); + obj.openDialog('instance/target_element_id/Y45GDRg/', 100, 100, 'title', undefined); expect($.ajax.calls.count()).toBe(1); }); }); diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php index 7ae854885affa..2076daf811fb1 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php @@ -12,6 +12,7 @@ use Magento\Framework\App\Utility\Files; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\UrlInterface; +use Magento\TestFramework\Dependency\Reader\ClassScanner; use Magento\TestFramework\Dependency\Route\RouteMapper; use Magento\TestFramework\Exception\NoSuchActionException; @@ -79,24 +80,33 @@ class PhpRule implements RuleInterface */ private $whitelists; + /** + * @var ClassScanner + */ + private $classScanner; + /** * @param array $mapRouters * @param array $mapLayoutBlocks * @param array $pluginMap * @param array $whitelists - * @throws \Exception + * @param ClassScanner|null $classScanner + * + * @throws LocalizedException */ public function __construct( array $mapRouters, array $mapLayoutBlocks, array $pluginMap = [], - array $whitelists = [] + array $whitelists = [], + ClassScanner $classScanner = null ) { $this->_mapRouters = $mapRouters; $this->_mapLayoutBlocks = $mapLayoutBlocks; $this->pluginMap = $pluginMap ?: null; $this->routeMapper = new RouteMapper(); $this->whitelists = $whitelists; + $this->classScanner = $classScanner ?? new ClassScanner(); } /** @@ -176,7 +186,7 @@ private function caseClassesAndIdentifiers($currentModule, $file, &$contents) if (empty($matches['class_inside_module'][$i]) && !empty($matches['module_scoped_key'][$i])) { $dependencyType = RuleInterface::TYPE_SOFT; } else { - $currentClass = $this->getClassFromFilepath($file, $currentModule); + $currentClass = $this->getClassFromFilepath($file); $dependencyType = $this->isPluginDependency($currentClass, $dependencyClass) ? RuleInterface::TYPE_SOFT : RuleInterface::TYPE_HARD; @@ -196,14 +206,11 @@ private function caseClassesAndIdentifiers($currentModule, $file, &$contents) * Get class name from filename based on class/file naming conventions * * @param string $filepath - * @param string $module * @return string */ - private function getClassFromFilepath($filepath, $module) + private function getClassFromFilepath(string $filepath): string { - $class = strstr($filepath, str_replace(['_', '\\', '/'], DIRECTORY_SEPARATOR, $module)); - $class = str_replace(DIRECTORY_SEPARATOR, '\\', strstr($class, '.php', true)); - return $class; + return $this->classScanner->getClassName($filepath); } /** @@ -267,7 +274,8 @@ private function isPluginDependency($dependent, $dependency) if ($subject === $dependency) { return true; } elseif ($subject) { - $subjectModule = substr($subject, 0, strpos($subject, '\\', 9)); // (strlen('Magento\\') + 1) === 9 + $moduleNameLength = strpos($subject, '\\', strpos($subject, '\\') + 1); + $subjectModule = substr($subject, 0, $moduleNameLength); return strpos($dependency, $subjectModule) === 0; } else { return false; diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/Reader/ClassScanner.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/Reader/ClassScanner.php new file mode 100644 index 0000000000000..a821551e66b8f --- /dev/null +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/Reader/ClassScanner.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Dependency\Reader; + +use Magento\Setup\Module\Di\Code\Reader\FileClassScanner; + +/** + * Search classes in file by path. + */ +class ClassScanner +{ + /** + * @var string[] + */ + private $classNames = []; + + /** + * Get class name by file name. + * + * @param string $filePath + * + * @return string + */ + public function getClassName(string $filePath): string + { + if (!isset($this->classNames[$filePath])) { + $this->classNames[$filePath] = $this->loadClassName($filePath); + } + + return $this->classNames[$filePath]; + } + + /** + * Load class name from file. + * + * @param string $filePath + * + * @return string + */ + private function loadClassName(string $filePath): string + { + $scanner = new FileClassScanner($filePath); + return $scanner->getClassName(); + } +} diff --git a/dev/tests/static/framework/autoload.php b/dev/tests/static/framework/autoload.php index 4f04f4fbb0246..09f489dd43269 100644 --- a/dev/tests/static/framework/autoload.php +++ b/dev/tests/static/framework/autoload.php @@ -34,3 +34,6 @@ $generatedCode = DirectoryList::getDefaultConfig()[DirectoryList::GENERATED_CODE][DirectoryList::PATH]; $autoloadWrapper->addPsr4('Magento\\', $baseDir . '/' . $generatedCode . '/Magento/'); + +$setup = DirectoryList::getDefaultConfig()[DirectoryList::SETUP][DirectoryList::PATH]; +$autoloadWrapper->addPsr4('Magento\\Setup\\', $baseDir . '/' . $setup . '/Magento/Setup/'); diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php index d1350d1b54db9..a15b06c6c2776 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php @@ -7,6 +7,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\TestFramework\Dependency\Reader\ClassScanner; use Magento\TestFramework\Exception\NoSuchActionException; /** @@ -24,6 +25,11 @@ class PhpRuleTest extends \PHPUnit\Framework\TestCase */ private $objectManagerHelper; + /** + * @var ClassScanner + */ + private $classScanner; + /** * @inheritDoc * @throws \Exception @@ -39,6 +45,8 @@ protected function setUp() $whitelist = []; $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->classScanner = $this->createMock(ClassScanner::class); + $this->model = $this->objectManagerHelper->getObject( PhpRule::class, [ @@ -46,6 +54,7 @@ protected function setUp() 'mapLayoutBlocks' => $mapLayoutBlocks, 'pluginMap' => $pluginMap, 'whitelists' => $whitelist, + 'classScanner' => $this->classScanner ] ); } @@ -62,14 +71,20 @@ public function testNonPhpGetDependencyInfo() /** * @param string $class * @param string $content + * @param int $expectedScans * @param array $expected - * @dataProvider getDependencyInfoDataProvider + * * @throws \Exception + * @dataProvider getDependencyInfoDataProvider */ - public function testGetDependencyInfo($class, $content, array $expected) + public function testGetDependencyInfo(string $class, string $content, int $expectedScans, array $expected): void { $file = $this->makeMockFilepath($class); $module = $this->getModuleFromClass($class); + $this->classScanner->expects($this->exactly($expectedScans)) + ->method('getClassName') + ->with($file) + ->willReturn($class); $this->assertEquals($expected, $this->model->getDependencyInfo($module, 'php', $file, $content)); } @@ -82,11 +97,13 @@ public function getDependencyInfoDataProvider() 'Extend class in same module' => [ 'Magento\SomeModule\SomeClass', 'something extends \Magento\SomeModule\Any\ClassName {', + 0, [] ], 'Extend class in different module' => [ 'Magento\AnotherModule\SomeClass', 'something extends \Magento\SomeModule\Any\ClassName {', + 1, [ [ 'modules' => ['Magento\SomeModule'], @@ -98,11 +115,13 @@ public function getDependencyInfoDataProvider() 'getViewFileUrl in same module' => [ 'Magento\SomeModule\SomeClass', '$this->getViewFileUrl("Magento_SomeModule::js/order-by-sku-failure.js")', + 0, [] ], 'getViewFileUrl in different module' => [ 'Magento\AnotherModule\SomeClass', '$this->getViewFileUrl("Magento_SomeModule::js/order-by-sku-failure.js")', + 1, [ [ 'modules' => ['Magento\SomeModule'], @@ -114,11 +133,13 @@ public function getDependencyInfoDataProvider() 'Helper class from same module' => [ 'Magento\SomeModule\SomeClass', '$this->helper("Magento\SomeModule\Any\ClassName")', + 0, [] ], 'Helper class from another module' => [ 'Magento\AnotherModule\SomeClass', '$this->helper("Magento\SomeModule\Any\ClassName")', + 1, [ [ 'modules' => ['Magento\SomeModule'], @@ -129,11 +150,14 @@ public function getDependencyInfoDataProvider() ], 'getBlock from same module' => [ 'Magento\SomeModule\SomeClass', - '$this->getLayout()->getBlock(\'block.name\');', [] + '$this->getLayout()->getBlock(\'block.name\');', + 0, + [] ], 'getBlock from another module' => [ 'Magento\AnotherModule\SomeClass', '$this->getLayout()->getBlock(\'block.name\');', + 0, [ [ 'modules' => ['Magento\SomeModule'], @@ -145,16 +169,19 @@ public function getDependencyInfoDataProvider() 'Plugin on class in same module' => [ 'Magento\Module1\Plugin1', ', \Magento\Module1\Subject $variable', + 0, [] ], 'Plugin depends on arbitrary class in same module' => [ 'Magento\Module1\Plugin1', ', \Magento\Module1\NotSubject $variable', + 0, [] ], 'Plugin on class in different module' => [ 'Magento\Module1\Plugin2', 'Magento\Module2\Subject', + 1, [ [ 'modules' => ['Magento\Module2'], @@ -166,6 +193,7 @@ public function getDependencyInfoDataProvider() 'Plugin depends on arbitrary class in same module as subject' => [ 'Magento\Module1\Plugin2', 'Magento\Module2\NotSubject', + 1, [ [ 'modules' => ['Magento\Module2'], @@ -177,6 +205,7 @@ public function getDependencyInfoDataProvider() 'Plugin depends on arbitrary class in arbitrary module' => [ 'Magento\Module1\Plugin2', 'Magento\OtherModule\NotSubject', + 1, [ [ 'modules' => ['Magento\OtherModule'], @@ -322,8 +351,9 @@ private function makeMockFilepath($class) * @param string $class * @return string */ - private function getModuleFromClass($class) + private function getModuleFromClass(string $class): string { - return substr($class, 0, strpos($class, '\\', 9)); // (strlen('Magento\\') + 1) === 9 + $moduleNameLength = strpos($class, '\\', strpos($class, '\\') + 1); + return substr($class, 0, $moduleNameLength); } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/_files/ExtensibleInterfacesTest/blacklist_ce.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/_files/ExtensibleInterfacesTest/blacklist_ce.txt index e9b1e6e428e03..c544cf71dfce1 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/_files/ExtensibleInterfacesTest/blacklist_ce.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/_files/ExtensibleInterfacesTest/blacklist_ce.txt @@ -1,4 +1,5 @@ module Magento_Payment Model/Info.php module Magento_Customer Model/Address/AbstractAddress.php library magento/framework MessageQueue/Rpc/Publisher.php -module Magento_GraphQl Model/Query/ContextInterface.php \ No newline at end of file +module Magento_GraphQl Model/Query/ContextInterface.php +module Magento_LoginAsCustomerApi Api/Data/AuthenticationDataInterface.php \ No newline at end of file diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt index d153593a5ed4f..53bba8d71d65d 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt @@ -7,6 +7,7 @@ lib/internal/Magento/Framework/Interception/Test/Unit/Config/ConfigTest.php lib/internal/Magento/Framework/Cache/Backend/Eaccelerator.php lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php dev/tests/integration/framework/deployTestModules.php +dev/tests/integration/testsuite/Magento/Framework/Filter/DirectiveProcessor/SimpleDirectiveTest.php dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php diff --git a/lib/internal/Magento/Framework/App/Action/AbstractAction.php b/lib/internal/Magento/Framework/App/Action/AbstractAction.php index cae2153ddd586..27f8838f9b165 100644 --- a/lib/internal/Magento/Framework/App/Action/AbstractAction.php +++ b/lib/internal/Magento/Framework/App/Action/AbstractAction.php @@ -1,7 +1,5 @@ <?php /** - * Abstract redirect/forward action class - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -10,6 +8,12 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; +/** + * Abstract redirect/forward action class + * + * @deprecated Inheritance in controllers should be avoided in favor of composition + * @see \Magento\Framework\App\ActionInterface + */ abstract class AbstractAction implements \Magento\Framework\App\ActionInterface { /** diff --git a/lib/internal/Magento/Framework/App/Action/Action.php b/lib/internal/Magento/Framework/App/Action/Action.php index 6a3b665c7d3ed..9d9b26313d7c6 100644 --- a/lib/internal/Magento/Framework/App/Action/Action.php +++ b/lib/internal/Magento/Framework/App/Action/Action.php @@ -23,7 +23,8 @@ * It contains standard action behavior (event dispatching, flag checks) * Action classes that do not extend from this class will lose this behavior and might not function correctly * - * @deprecated Use \Magento\Framework\App\ActionInterface + * @deprecated Inheritance in controllers should be avoided in favor of composition + * @see \Magento\Framework\App\ActionInterface * * phpcs:disable Magento2.Classes.AbstractApi * @api diff --git a/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php b/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php index d15f23e0d7c0b..d963e83396d19 100644 --- a/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php +++ b/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php @@ -48,22 +48,31 @@ class LockGuardedCacheLoader private $loadTimeout; /** - * LockGuardedCacheLoader constructor. + * Minimal delay timeout in ms. + * + * @var int + */ + private $minimalDelayTimeout; + + /** * @param LockManagerInterface $locker * @param int $lockTimeout * @param int $delayTimeout * @param int $loadTimeout + * @param int $minimalDelayTimeout */ public function __construct( LockManagerInterface $locker, int $lockTimeout = 10000, int $delayTimeout = 20, - int $loadTimeout = 10000 + int $loadTimeout = 10000, + int $minimalDelayTimeout = 5 ) { $this->locker = $locker; $this->lockTimeout = $lockTimeout; $this->delayTimeout = $delayTimeout; $this->loadTimeout = $loadTimeout; + $this->minimalDelayTimeout = $minimalDelayTimeout; } /** @@ -82,7 +91,7 @@ public function lockedLoadData( callable $dataSaver ) { $cachedData = $dataLoader(); //optimistic read - $deadline = microtime(true) + $this->loadTimeout; + $deadline = microtime(true) + $this->loadTimeout / 100; while ($cachedData === false) { if ($deadline <= microtime(true)) { @@ -100,7 +109,7 @@ public function lockedLoadData( } if ($cachedData === false) { - usleep($this->delayTimeout * 1000); + usleep($this->getLookupTimeout() * 1000); $cachedData = $dataLoader(); } } @@ -118,14 +127,21 @@ public function lockedLoadData( public function lockedCleanData(string $lockName, callable $dataCleaner) { while ($this->locker->isLocked($lockName)) { - usleep($this->delayTimeout * 1000); - } - try { - if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) { - $dataCleaner(); - } - } finally { - $this->locker->unlock($lockName); + usleep($this->getLookupTimeout() * 1000); } + + $dataCleaner(); + } + + /** + * Delay will be applied as rand($minimalDelayTimeout, $delayTimeout). + * This helps to desynchronize multiple clients trying + * to acquire the lock for the same resource at the same time + * + * @return int + */ + private function getLookupTimeout() + { + return rand($this->minimalDelayTimeout, $this->delayTimeout); } } diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Editor.php b/lib/internal/Magento/Framework/Data/Form/Element/Editor.php index 08a5edd09bf35..41e27b1d431b2 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Editor.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Editor.php @@ -286,15 +286,19 @@ protected function _getPluginButtonsHtml($visible = true) // Button to media images insertion window if ($this->getConfig('add_images')) { + $htmlId = $this->getHtmlId(); + $url = $this->getConfig('files_browser_window_url') + . 'target_element_id/' + . $htmlId + . '/' + . (null !== $this->getConfig('store_id') + ? 'store/' . $this->getConfig('store_id') . '/"' + : ''); $buttonsHtml .= $this->_getButtonHtml( [ 'title' => $this->translate('Insert Image...'), - 'onclick' => "MediabrowserUtility.openDialog('" - . $this->getConfig('files_browser_window_url') - . "target_element_id/" . $this->getHtmlId() . "/" - . (null !== $this->getConfig('store_id') ? 'store/' - . $this->getConfig('store_id') . '/' : '') - . "')", + 'onclick' => 'MediabrowserUtility.openDialog(\'' . $url + . '\', null, null, null, { \'targetElementId\': \'' . $htmlId . '\' })', 'class' => 'action-add-image plugin', 'style' => $visible ? '' : 'display:none', ] @@ -496,13 +500,13 @@ protected function getInlineJs($jsSetupObject, $forceLoad) $jsString = ' <script type="text/javascript"> //<![CDATA[ - window.tinyMCE_GZ = window.tinyMCE_GZ || {}; + window.tinyMCE_GZ = window.tinyMCE_GZ || {}; window.tinyMCE_GZ.loaded = true; require([ - "jquery", - "mage/translate", - "mage/adminhtml/events", - "mage/adminhtml/wysiwyg/tiny_mce/setup", + "jquery", + "mage/translate", + "mage/adminhtml/events", + "mage/adminhtml/wysiwyg/tiny_mce/setup", "mage/adminhtml/wysiwyg/widget" ], function(jQuery){' . "\n" . diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index 1ac024d505fc3..ca597a7056566 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -91,7 +91,7 @@ class Template implements \Zend_Filter_Interface /** * @var bool */ - private $strictMode = false; + private $strictMode = true; /** * @var VariableResolverInterface|null diff --git a/lib/internal/Magento/Framework/Lock/Backend/Cache.php b/lib/internal/Magento/Framework/Lock/Backend/Cache.php index 5e1cdd706e326..451c0ba06ce13 100644 --- a/lib/internal/Magento/Framework/Lock/Backend/Cache.php +++ b/lib/internal/Magento/Framework/Lock/Backend/Cache.php @@ -24,12 +24,20 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface */ private $cache; + /** + * Sign for locks, helps to avoid removing a lock that was created by another client + * + * @string + */ + private $lockSign; + /** * @param FrontendInterface $cache */ public function __construct(FrontendInterface $cache) { $this->cache = $cache; + $this->lockSign = $this->generateLockSign(); } /** @@ -37,11 +45,25 @@ public function __construct(FrontendInterface $cache) */ public function lock(string $name, int $timeout = -1): bool { - if ((bool)$this->cache->test($this->getIdentifier($name))) { + if (empty($this->lockSign)) { + $this->lockSign = $this->generateLockSign(); + } + + $data = $this->cache->load($this->getIdentifier($name)); + + if (false !== $data) { return false; } - return $this->cache->save('1', $this->getIdentifier($name), [], $timeout); + $this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout * 100); + + $data = $this->cache->load($this->getIdentifier($name)); + + if ($data === $this->lockSign) { + return true; + } + + return false; } /** @@ -49,7 +71,22 @@ public function lock(string $name, int $timeout = -1): bool */ public function unlock(string $name): bool { - return (bool)$this->cache->remove($this->getIdentifier($name)); + if (empty($this->lockSign)) { + return false; + } + + $data = $this->cache->load($this->getIdentifier($name)); + + if (false === $data) { + return false; + } + + $removeResult = false; + if ($data === $this->lockSign) { + $removeResult = (bool)$this->cache->remove($this->getIdentifier($name)); + } + + return $removeResult; } /** @@ -70,4 +107,27 @@ private function getIdentifier(string $cacheIdentifier): string { return self::LOCK_PREFIX . $cacheIdentifier; } + + /** + * Function that generates lock sign that helps to avoid removing a lock that was created by another client. + * + * @return string + */ + private function generateLockSign() + { + $sign = implode( + '-', + [ + \getmypid(), \crc32(\gethostname()) + ] + ); + + try { + $sign .= '-' . \bin2hex(\random_bytes(4)); + } catch (\Exception $e) { + $sign .= '-' . \uniqid('-uniqid-'); + } + + return $sign; + } } diff --git a/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php index 2ec812092d654..e7351ca39280b 100644 --- a/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php +++ b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php @@ -53,6 +53,16 @@ public function testUnlock(): void { $identifier = 'lock_name'; + $closure = \Closure::bind(function ($cacheInstance) { + return $cacheInstance->lockSign; + }, null, $this->cache); + $lockSign = $closure($this->cache); + + $this->frontendCacheMock + ->expects($this->once())->method('load') + ->with(self::LOCK_PREFIX . $identifier) + ->willReturn($lockSign); + $this->frontendCacheMock ->expects($this->once()) ->method('remove') @@ -61,4 +71,22 @@ public function testUnlock(): void $this->assertEquals(true, $this->cache->unlock($identifier)); } + + /** + * Verify that lock will no be released without sign matches. + * Sign generates in Cache class constructor. + * + * @return void + */ + public function testUnlockWithAnotherSign(): void + { + $identifier = 'lock_name'; + + $this->frontendCacheMock + ->expects($this->once())->method('load') + ->with(self::LOCK_PREFIX . $identifier) + ->willReturn(\uniqid('some_rand-')); + + $this->assertEquals(false, $this->cache->unlock($identifier)); + } } diff --git a/lib/internal/Magento/Framework/Url.php b/lib/internal/Magento/Framework/Url.php index a2318f1169715..567fd2a96c18a 100644 --- a/lib/internal/Magento/Framework/Url.php +++ b/lib/internal/Magento/Framework/Url.php @@ -424,8 +424,10 @@ protected function _isSecure() */ public function setScope($params) { - $this->setData('scope', $this->_scopeResolver->getScope($params)); - $this->getRouteParamsResolver()->setScope($this->_scopeResolver->getScope($params)); + $scope = $this->_scopeResolver->getScope($params); + $this->setData('scope', $scope); + $this->getRouteParamsResolver()->setScope($scope); + return $this; } diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php index fbb84712b2afd..8f980d82d4be0 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php @@ -15,12 +15,13 @@ use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderFactory; use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; +use Magento\Framework\View\Element\UiComponent\DataProvider\Sanitizer; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Framework\View\LayoutInterface as PageLayoutInterface; /** - * Class Context + * Request context for UI components to utilize. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -101,6 +102,11 @@ class Context implements ContextInterface */ private $authorization; + /** + * @var Sanitizer + */ + private $sanitizer; + /** * @param PageLayoutInterface $pageLayout * @param RequestInterface $request @@ -113,6 +119,7 @@ class Context implements ContextInterface * @param DataProviderInterface|null $dataProvider * @param string $namespace * @param AuthorizationInterface|null $authorization + * @param Sanitizer|null $sanitizer * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -126,7 +133,8 @@ public function __construct( UiComponentFactory $uiComponentFactory, DataProviderInterface $dataProvider = null, $namespace = null, - AuthorizationInterface $authorization = null + AuthorizationInterface $authorization = null, + ?Sanitizer $sanitizer = null ) { $this->namespace = $namespace; $this->request = $request; @@ -141,6 +149,7 @@ public function __construct( $this->authorization = $authorization ?: ObjectManager::getInstance()->get( AuthorizationInterface::class ); + $this->sanitizer = $sanitizer ?? ObjectManager::getInstance()->get(Sanitizer::class); $this->setAcceptType(); } @@ -241,16 +250,20 @@ public function getDataProvider() */ public function getDataSourceData(UiComponentInterface $component) { + //Getting dynamic data for the component $dataSource = $component->getDataSourceData(); $this->prepareDataSource($dataSource, $component); $dataProviderConfig = $this->getDataProvider()->getConfigData(); + //Dynamic UI component data should not contain templates. + $config = $this->sanitizer->sanitize(array_merge($dataSource, $dataProviderConfig)); + return [ $this->getDataProvider()->getName() => [ 'type' => 'dataSource', 'name' => $this->getDataProvider()->getName(), 'dataScope' => $this->getNamespace(), 'config' => array_replace_recursive( - array_merge($dataSource, $dataProviderConfig), + $config, [ 'params' => [ 'namespace' => $this->getNamespace() diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/Sanitizer.php b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/Sanitizer.php new file mode 100644 index 0000000000000..27a42fb337ce8 --- /dev/null +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/Sanitizer.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\View\Element\UiComponent\DataProvider; + +use Magento\Framework\Phrase; + +/** + * Sanitizes data received from UI data providers. + */ +class Sanitizer +{ + /** + * Extract rendering config from given UI data. + * + * @param array $data + * @return bool|array + */ + private function extractConfig(array $data) + { + /** @var array|bool $config */ + $config = []; + if (array_key_exists('__disableTmpl', $data)) { + //UI data provider has explicitly provided rendering config. + $config = $data['__disableTmpl']; + unset($data['__disableTmpl']); + } + + return $config; + } + + /** + * Sanitizes data from a UI data provider. + * + * @param array $data + * @return array + */ + public function sanitize(array $data): array + { + $config = $this->extractConfig($data); + $toProcess = []; + array_walk( + $data, + function ($datum, string $key) use (&$config, &$toProcess) : void { + if (is_array($datum)) { + //Each array must have it's own __disableTmpl property + $toProcess[$key] = $datum; + } elseif (( + !is_bool($config) && !array_key_exists($key, $config) + ) + && (is_string($datum) || $datum instanceof Phrase) + && preg_match('/\$\{.+\}/', (string)$datum) + ) { + //Templating is not disabled for all properties or for this property specifically + //Property is a string that contains template syntax so we are disabling it's rendering + $config[$key] = true; + } + } + ); + if ($toProcess) { + //Processing sub-arrays + $data = array_replace($data, array_map([$this, 'sanitize'], $toProcess)); + } + if ($config !== []) { + //Some properties require rendering configuration. + $data['__disableTmpl'] = $config; + } + + return $data; + } + + /** + * Sanitize a component's metadata. + * + * Will sanitize full component's metadata as well as metadata of it's child components. + * + * @param array $meta + * @return array + */ + public function sanitizeComponentMetadata(array $meta): array + { + if (array_key_exists('arguments', $meta) + && is_array($meta['arguments']) + && array_key_exists('data', $meta['arguments']) + && is_array($meta['arguments']['data']) + && array_key_exists('config', $meta['arguments']['data']) + && is_array($meta['arguments']['data']['config']) + ) { + $meta['arguments']['data']['config'] = $this->sanitize($meta['arguments']['data']['config']); + } + if (array_key_exists('children', $meta) && is_array($meta['children'])) { + $meta['children'] = array_map([$this, 'sanitizeComponentMetadata'], $meta['children']); + } + + return $meta; + } +} diff --git a/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php b/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php index b395e95f0baaf..16c06fe3abf3a 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponentFactory.php @@ -17,10 +17,11 @@ use Magento\Framework\View\Element\UiComponent\ContextFactory; use Magento\Framework\Phrase; use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; +use Magento\Framework\View\Element\UiComponent\DataProvider\Sanitizer; use Magento\Framework\View\Element\UiComponent\Factory\ComponentFactoryInterface; /** - * Class UiComponentFactory + * Factory that creates UI component descriptor based on configuration provided. * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -69,6 +70,11 @@ class UiComponentFactory extends DataObject */ private $definitionData; + /** + * @var Sanitizer + */ + private $sanitizer; + /** * @param ObjectManagerInterface $objectManager * @param ManagerInterface $componentManager @@ -78,6 +84,7 @@ class UiComponentFactory extends DataObject * @param array $componentChildFactories * @param DataInterface $definitionData * @param DataInterfaceFactory $configFactory + * @param Sanitizer|null $sanitizer */ public function __construct( ObjectManagerInterface $objectManager, @@ -87,7 +94,8 @@ public function __construct( array $data = [], array $componentChildFactories = [], DataInterface $definitionData = null, - DataInterfaceFactory $configFactory = null + DataInterfaceFactory $configFactory = null, + ?Sanitizer $sanitizer = null ) { $this->objectManager = $objectManager; $this->componentManager = $componentManager; @@ -98,6 +106,7 @@ public function __construct( parent::__construct($data); $this->definitionData = $definitionData ?: $this->objectManager->get(DataInterface::class); + $this->sanitizer = $sanitizer ?? $this->objectManager->get(Sanitizer::class); } /** @@ -198,8 +207,12 @@ protected function argumentsResolver($identifier, array $componentData) */ public function create($identifier, $name = null, array $arguments = []) { + //$argument contain dynamic data generated by UI component classes which should not contain templates. + $arguments = $this->sanitizer->sanitize($arguments); if ($name === null) { - $componentData = $this->configFactory->create(['componentName' => $identifier])->get($identifier); + /** @var DataInterface $config */ + $config = $this->configFactory->create(['componentName' => $identifier]); + $componentData = $config->get($identifier); $bundleComponents = [$identifier => $componentData]; list($className, $componentArguments) = $this->argumentsResolver( @@ -312,10 +325,10 @@ protected function mergeMetadata($identifier, array $bundleComponents, $reverseM { $dataProvider = $this->getDataProvider($identifier, $bundleComponents); if ($dataProvider instanceof DataProviderInterface) { + //Dynamic meta from data providers should not contain templates. + $metadata = $dataProvider->getMeta(); $metadata = [ - $identifier => [ - 'children' => $dataProvider->getMeta(), - ], + $identifier => $this->sanitizer->sanitizeComponentMetadata(['children' => $metadata]) ]; $bundleComponents = $this->mergeMetadataItem($bundleComponents, $metadata, $reverseMerge); } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/UiComponent/DataProvider/SanitizerTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/UiComponent/DataProvider/SanitizerTest.php new file mode 100644 index 0000000000000..75fbfd868cfc1 --- /dev/null +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/UiComponent/DataProvider/SanitizerTest.php @@ -0,0 +1,185 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\View\Test\Unit\Element\UiComponent\DataProvider; + +use Magento\Framework\View\Element\UiComponent\DataProvider\Sanitizer; +use PHPUnit\Framework\TestCase; + +/** + * Test sanitizer for different kind of scenarios. + */ +class SanitizerTest extends TestCase +{ + /** + * @var Sanitizer + */ + private $sanitizer; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->sanitizer = new Sanitizer(); + } + + /** + * Data sets to sanitize. + * + * @return array + */ + public function getSanitizeDataSets(): array + { + return [ + 'simpleSet' => [ + ['foo' => '${\'bar\'}', 'xyz' => 42], + ['foo' => '${\'bar\'}', 'xyz' => 42, '__disableTmpl' => ['foo' => true]] + ], + 'configuredSet' => [ + ['foo' => 'bar', 'xyz' => '${\'zyx\'}', '__disableTmpl' => true], + ['foo' => 'bar', 'xyz' => '${\'zyx\'}', '__disableTmpl' => true] + ], + 'partiallyConfiguredSet' => [ + ['foo' => '${\'bar\'}', 'xyz' => '${\'zyx\'}', '__disableTmpl' => ['foo' => false]], + ['foo' => '${\'bar\'}', 'xyz' => '${\'zyx\'}', '__disableTmpl' => ['foo' => false, 'xyz' => true]] + ], + 'enabledSet' => [ + ['foo' => 'bar', 'xyz' => '${\'zyx\'}', '__disableTmpl' => false], + ['foo' => 'bar', 'xyz' => '${\'zyx\'}', '__disableTmpl' => false] + ], + 'complexSet' => [ + [ + 'foo' => 'bar', + 'sub1' => ['foo' => '${\'bar\'}'], + 'sub2' => [ + 'field' => '${\'value\'}', + 'subSub1' => ['foo' => 'bar'], + 'subSub2' => ['foo' => '${\'bar\'}', '__disableTmpl' => false], + 'subSub3' => [ + 'fooSub' => [ + 'foo' => '${\'bar\'}', + '__disableTmpl' => false, + 'subSubSub1' => ['field' => '${\'value\'}'] + ] + ], + 'subSub4' => [['foo' => '${\'bar\'}'], ['foo' => '${\'bar\'}', 'xyz' => '${\'zyx\'}']] + ] + ], + [ + 'foo' => 'bar', + 'sub1' => ['foo' => '${\'bar\'}', '__disableTmpl' => ['foo' => true]], + 'sub2' => [ + 'field' => '${\'value\'}', + 'subSub1' => ['foo' => 'bar'], + 'subSub2' => ['foo' => '${\'bar\'}', '__disableTmpl' => false], + 'subSub3' => [ + 'fooSub' => [ + 'foo' => '${\'bar\'}', + '__disableTmpl' => false, + 'subSubSub1' => ['field' => '${\'value\'}', '__disableTmpl' => ['field' => true]] + ] + ], + 'subSub4' => [ + ['foo' => '${\'bar\'}', '__disableTmpl' => ['foo' => true]], + [ + 'foo' => '${\'bar\'}', + 'xyz' => '${\'zyx\'}', + '__disableTmpl' => ['foo' => true, 'xyz' => true] + ] + ], + '__disableTmpl' => ['field' => true] + ] + ] + ] + ]; + } + + /** + * Test sanitize method for different data sets. + * + * @param array $input + * @param array $expectedOutput + * @return void + * @dataProvider getSanitizeDataSets + */ + public function testSanitize(array $input, array $expectedOutput): void + { + $this->assertEquals($expectedOutput, $this->sanitizer->sanitize($input)); + } + + /** + * Full UI component data sets to sanitize. + * + * @return array + */ + public function getSanitizeComponentDataSets(): array + { + return [ + 'simpleComponent' => [ + [ + 'arguments' => ['data' => ['config' => ['foo' => '${\'bar\'}', 'xyz' => 42]]], + 'children' => [ + 'child_component' => [ + 'arguments' => ['data' => ['config' => ['foo' => '${\'bar\'}', 'xyz' => '${\'xyz\'}']]] + ] + ] + ], + [ + 'arguments' => [ + 'data' => [ + 'config' => ['foo' => '${\'bar\'}', 'xyz' => 42, '__disableTmpl' => ['foo' => true]] + ] + ], + 'children' => [ + 'child_component' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'foo' => '${\'bar\'}', + 'xyz' => '${\'xyz\'}', + '__disableTmpl' => ['foo' => true, 'xyz' => true] + ] + ] + ] + ] + ] + ] + ], + 'argumentsOnly' => [ + ['arguments' => ['data' => ['config' => ['foo' => '${\'bar\'}']]]], + ['arguments' => ['data' => ['config' => ['foo' => '${\'bar\'}', '__disableTmpl' => ['foo' => true]]]]] + ], + 'childrenOnly' => [ + ['children' => ['child1' => ['arguments' => ['data' => ['config' => ['foo' => '${\'bar\'}']]]]]], + [ + 'children' => [ + 'child1' => [ + 'arguments' => [ + 'data' => ['config' => ['foo' => '${\'bar\'}', '__disableTmpl' => ['foo' => true]]] + ] + ] + ] + ] + ] + ]; + } + + /** + * Test sanitizeComponentMetadata method for different data sets. + * + * @param array $input + * @param array $expectedOutput + * @return void + * @dataProvider getSanitizeComponentDataSets + */ + public function testSanitizeComponentMetadata(array $input, array $expectedOutput): void + { + $this->assertEquals($expectedOutput, $this->sanitizer->sanitizeComponentMetadata($input)); + } +} diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js index 06a77cb46064b..3ca01dd5e0826 100644 --- a/lib/web/mage/adminhtml/browser.js +++ b/lib/web/mage/adminhtml/browser.js @@ -20,6 +20,7 @@ define([ window.MediabrowserUtility = { windowId: 'modal_dialog_message', modalLoaded: false, + targetElementId: false, /** * @return {Number} @@ -50,42 +51,30 @@ define([ */ openDialog: function (url, width, height, title, options) { var windowId = this.windowId, - content = '<div class="popup-window" id="' + windowId + '"></div>', - self = this; + content = '<div class="popup-window" id="' + windowId + '"></div>'; - if (options && - self.targetElementId && - self.targetElementId === options.targetElementId) { - if (typeof options.closed !== 'undefined') { + if (this.modalLoaded) { + + if (!_.isUndefined(options)) { this.modal.modal('option', 'closed', options.closed); } - this.modal.modal('openModal'); - return; - } else if (_.isUndefined(options) && - self.modalLoaded === true && - self.targetElementId === url - ) { this.modal.modal('openModal'); + this.setTargetElementId(options, url); + $(window).trigger('reload.MediaGallery'); return; } - if (this.modal) { - this.modal.html($(content).html()); + this.modal = $(content).modal($.extend({ + title: title || 'Insert File...', + modalClass: 'magento', + type: 'slide', + buttons: [] + }, options)); - if (options && typeof options.closed !== 'undefined') { - this.modal.modal('option', 'closed', options.closed); - } - } else { - this.modal = $(content).modal($.extend({ - title: title || 'Insert File...', - modalClass: 'magento', - type: 'slide', - buttons: [] - }, options)); - } this.modal.modal('openModal'); + $.ajax({ url: url, type: 'get', @@ -93,15 +82,25 @@ define([ showLoader: true }).done(function (data) { - self.modal.html(data).trigger('contentUpdated'); - self.modalLoaded = true; - self.targetElementId = options ? - options.targetElementId - : url; - }); + this.modal.html(data).trigger('contentUpdated'); + this.modalLoaded = true; + this.setTargetElementId(options, url); + }.bind(this)); }, + /** + * Setter for targetElementId property + * + * @param {Object} options + * @param {String} url + */ + setTargetElementId: function (options, url) { + this.targetElementId = options && options.targetElementId ? + options.targetElementId + : url.match(/\/target_element_id\/([\s\S].*?)\//)[1]; + }, + /** * Close dialog. */ @@ -113,7 +112,6 @@ define([ $.widget('mage.mediabrowser', { eventPrefix: 'mediabrowser', options: { - targetElementId: null, contentsUrl: null, onInsertUrl: null, newFolderUrl: null, @@ -142,6 +140,8 @@ define([ 'fileuploaddone': '_uploadDone', 'click [data-row=breadcrumb]': 'selectFolder' }); + + $(window).on('reload.MediaGallery', $.proxy(this.reload, this)); this.activeNode = null; //tree dont use event bubbling this.tree = this.element.find('[data-role=tree]'); @@ -322,16 +322,18 @@ define([ * return {HTMLElement|null} */ getTargetElement: function () { - var opener, targetElementId; + var opener, + targetElementId, + mediaBrowser = window.MediabrowserUtility; - if (typeof wysiwyg != 'undefined' && wysiwyg.get(this.options.targetElementId)) { + if (!_.isUndefined(wysiwyg) && wysiwyg.get(mediaBrowser.targetElementId)) { opener = this.getMediaBrowserOpener() || window; - targetElementId = tinyMceEditors.get(this.options.targetElementId).getMediaBrowserTargetElementId(); + targetElementId = tinyMceEditors.get(mediaBrowser.targetElementId).getMediaBrowserTargetElementId(); return $(opener.document.getElementById(targetElementId)); } - return $('#' + this.options.targetElementId); + return $('#' + mediaBrowser.targetElementId); }, /** @@ -340,12 +342,12 @@ define([ * return {Object|null} */ getMediaBrowserOpener: function () { - if (typeof wysiwyg != 'undefined' && - wysiwyg.get(this.options.targetElementId) && - typeof tinyMceEditors != 'undefined' && - !tinyMceEditors.get(this.options.targetElementId).getMediaBrowserOpener().closed + var targetElementId = window.MediabrowserUtility.targetElementId; + + if (!_.isUndefined(wysiwyg) && wysiwyg.get(targetElementId) && !_.isUndefined(tinyMceEditors) && + !tinyMceEditors.get(targetElementId).getMediaBrowserOpener().closed ) { - return tinyMceEditors.get(this.options.targetElementId).getMediaBrowserOpener(); + return tinyMceEditors.get(targetElementId).getMediaBrowserOpener(); } return null; diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php index 89a37429c47c9..ea0b35d7ffd63 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php @@ -20,7 +20,7 @@ class Cache implements ConfigOptionsListInterface { const INPUT_VALUE_CACHE_REDIS = 'redis'; - const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; + const CONFIG_VALUE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis'; const INPUT_KEY_CACHE_BACKEND = 'cache-backend'; const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; @@ -233,7 +233,7 @@ private function validateRedisConfig(array $options, DeploymentConfig $deploymen self::CONFIG_PATH_CACHE_BACKEND_DATABASE, $this->getDefaultConfigValue(self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE) ); - + $config['password'] = isset($options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD]) ? $options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD] : $deploymentConfig->get( @@ -282,6 +282,6 @@ private function getDefaultConfigValue($inputKey) */ private function generateCachePrefix(): string { - return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; + return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; } } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php index 65bfc650c0206..b97909fa5e791 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php @@ -20,7 +20,7 @@ class PageCache implements ConfigOptionsListInterface { const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis'; - const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis'; + const CONFIG_VALUE_PAGE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis'; const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server'; @@ -284,6 +284,6 @@ private function getDefaultConfigValue($inputKey) */ private function generateCachePrefix(): string { - return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_'; + return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_'; } } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php index 783c11e941eed..24bae6253e964 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php @@ -91,7 +91,7 @@ public function testCreateConfigCacheRedis() 'cache' => [ 'frontend' => [ 'default' => [ - 'backend' => 'Cm_Cache_Backend_Redis', + 'backend' => '\\Magento\\Framework\\Cache\\Backend\Redis', 'backend_options' => [ 'server' => '', 'port' => '', @@ -120,7 +120,7 @@ public function testCreateConfigWithRedisConfig() 'cache' => [ 'frontend' => [ 'default' => [ - 'backend' => 'Cm_Cache_Backend_Redis', + 'backend' => '\\Magento\\Framework\\Cache\\Backend\Redis', 'backend_options' => [ 'server' => 'localhost', 'port' => '1234', @@ -237,6 +237,6 @@ public function testValidateWithInvalidInput() */ private function expectedIdPrefix(): string { - return substr(\md5(dirname(__DIR__, 8)), 0, 3) . '_'; + return substr(\hash('sha256', dirname(__DIR__, 8)), 0, 3) . '_'; } } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php index 673168fe2fffd..84f44b4b5b02d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php @@ -91,7 +91,7 @@ public function testCreateConfigWithRedis() 'cache' => [ 'frontend' => [ 'page_cache' => [ - 'backend' => 'Cm_Cache_Backend_Redis', + 'backend' => '\\Magento\\Framework\\Cache\\Backend\Redis', 'backend_options' => [ 'server'=> '', 'port' => '', @@ -120,7 +120,7 @@ public function testCreateConfigWithRedisConfiguration() 'cache' => [ 'frontend' => [ 'page_cache' => [ - 'backend' => 'Cm_Cache_Backend_Redis', + 'backend' => '\\Magento\\Framework\\Cache\\Backend\Redis', 'backend_options' => [ 'server' => 'foo.bar', 'port' => '9000', @@ -238,6 +238,6 @@ public function testValidationWithInvalidData() */ private function expectedIdPrefix(): string { - return substr(\md5(dirname(__DIR__, 8)), 0, 3) . '_'; + return substr(\hash('sha256', dirname(__DIR__, 8)), 0, 3) . '_'; } }