diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 00763eb56e0c6..0000000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,206 +0,0 @@ -/app/code/Magento/AdminNotification/ @paliarush -/app/code/Magento/Backend/ @paliarush -/app/code/Magento/User/ @paliarush -/lib/internal/Magento/Framework/App/ @buskamuza -/lib/internal/Magento/Framework/Controller/ @buskamuza -/lib/internal/Magento/Framework/Flag/ @buskamuza -/lib/internal/Magento/Framework/HTTP/ @buskamuza -/lib/internal/Magento/Framework/Logger/ @buskamuza -/lib/internal/Magento/Framework/Message/ @buskamuza -/lib/internal/Magento/Framework/Notification/ @buskamuza -/lib/internal/Magento/Framework/Session/ @buskamuza -/lib/internal/Magento/Framework/Url/ @buskamuza -/app/code/Magento/Cms/ @melnikovi -/app/code/Magento/CmsUrlRewrite/ @melnikovi -/app/code/Magento/Contact/ @melnikovi -/app/code/Magento/Email/ @melnikovi -/app/code/Magento/Variable/ @melnikovi -/app/code/Magento/Widget/ @melnikovi -/lib/internal/Magento/Framework/Cache/ @kokoc -/app/code/Magento/CacheInvalidate/ @kokoc -/app/code/Magento/CatalogInventory/ @tariqjawed83 @maghamed -/app/code/Magento/Bundle/ @akaplya -/app/code/Magento/BundleImportExport/ @akaplya -/app/code/Magento/Catalog/ @akaplya -/app/code/Magento/CatalogAnalytics/ @akaplya -/app/code/Magento/CatalogImportExport/ @akaplya -/app/code/Magento/CatalogSearch/ @kokoc -/app/code/Magento/CatalogUrlRewrite/ @akaplya -/app/code/Magento/ConfigurableImportExport/ @akaplya -/app/code/Magento/ConfigurableProduct/ @akaplya -/app/code/Magento/Downloadable/ @akaplya -/app/code/Magento/DownloadableImportExport/ @akaplya -/app/code/Magento/GroupedImportExport/ @akaplya -/app/code/Magento/GroupedProduct/ @akaplya -/app/code/Magento/LayeredNavigation/ @kokoc -/app/code/Magento/ProductVideo/ @akaplya -/app/code/Magento/Review/ @akaplya -/app/code/Magento/Swatches/ @akaplya -/app/code/Magento/SwatchesLayeredNavigation/ @kokoc -/app/code/Magento/Checkout/ @paliarush -/app/code/Magento/CheckoutAgreements/ @paliarush -/app/code/Magento/GiftMessage/ @paliarush -/app/code/Magento/InstantPurchase/ @paliarush -/app/code/Magento/Multishipping/ @joni-jones -/app/code/Magento/Quote/ @paliarush -/app/code/Magento/QuoteAnalytics/ @paliarush -/lib/internal/Magento/Framework/Code/ @joni-jones -/lib/internal/Magento/Framework/Reflection/ @joni-jones -/lib/internal/Magento/Framework/Component/ @buskamuza -/app/code/Magento/Version/ @buskamuza -/lib/internal/Magento/Framework/Config/ @paliarush -/app/code/Magento/Config/ @paliarush -/lib/internal/Magento/Framework/Console/ @joni-jones -/lib/internal/Magento/Framework/Process/ @joni-jones -/lib/internal/Magento/Framework/Shell/ @joni-jones -/app/code/Magento/Cookie/ @kokoc -/lib/internal/Magento/Framework/Crontab/ @tariqjawed83 @buskamuza -/app/code/Magento/Cron/ @tariqjawed83 @buskamuza -/app/code/Magento/Customer/ @paliarush -/app/code/Magento/CustomerAnalytics/ @paliarush -/app/code/Magento/CustomerImportExport/ @paliarush -/app/code/Magento/Persistent/ @paliarush -/app/code/Magento/Wishlist/ @paliarush -/lib/internal/Magento/Framework/DB/ @akaplya -/lib/internal/Magento/Framework/EntityManager/ @akaplya -/lib/internal/Magento/Framework/Indexer/ @akaplya -/lib/internal/Magento/Framework/Model/ @akaplya -/lib/internal/Magento/Framework/Mview/ @akaplya -/app/code/Magento/Eav/ @akaplya -/app/code/Magento/Indexer/ @akaplya -/lib/internal/Magento/Framework/Archive/ @joni-jones -/lib/internal/Magento/Framework/Convert/ @joni-jones -/lib/internal/Magento/Framework/Data/ @joni-jones -/lib/internal/Magento/Framework/DomDocument/ @joni-jones -/lib/internal/Magento/Framework/Json/ @joni-jones -/lib/internal/Magento/Framework/Math/ @joni-jones -/lib/internal/Magento/Framework/Parse/ @joni-jones -/lib/internal/Magento/Framework/Serialize/ @joni-jones -/lib/internal/Magento/Framework/Simplexml/ @joni-jones -/lib/internal/Magento/Framework/Stdlib/ @joni-jones -/lib/internal/Magento/Framework/Unserialize/ @joni-jones -/lib/internal/Magento/Framework/Xml/ @joni-jones -/lib/internal/Magento/Framework/XsltProcessor/ @joni-jones -/app/code/Magento/Deploy/ @kandy @buskamuza -/lib/internal/Magento/Framework/Profiler/ @kandy -/app/code/Magento/Developer/ @buskamuza -/app/code/Magento/Directory/ @buskamuza -/lib/internal/Magento/Framework/Exception/ @paliarush -/lib/internal/Magento/Framework/File/ @buskamuza -/lib/internal/Magento/Framework/Filesystem/ @buskamuza -/lib/internal/Magento/Framework/System/ @buskamuza -/lib/internal/Magento/Framework/Css/ @DrewML -/lib/internal/Magento/Framework/Option/ @DrewML -/lib/internal/Magento/Framework/RequireJs/ @DrewML -/lib/internal/Magento/Framework/View/ @melnikovi -/dev/tests/js/ @DrewML -/app/code/Magento/RequireJs/ @DrewML -/app/code/Magento/Theme/ @melnikovi -/app/code/Magento/Ui/ @melnikovi -/lib/internal/Magento/Framework/Intl/ @melnikovi -/lib/internal/Magento/Framework/Locale/ @melnikovi -/lib/internal/Magento/Framework/Phrase/ @melnikovi -/lib/internal/Magento/Framework/Translate/ @melnikovi -/app/code/Magento/Translation/ @melnikovi -/app/code/Magento/ImportExport/ @akaplya -/app/code/Magento/GoogleAdwords/ @buskamuza @melnikovi -/app/code/Magento/Newsletter/ @buskamuza @melnikovi -/app/code/Magento/ProductAlert/ @buskamuza @melnikovi -/app/code/Magento/Rss/ @buskamuza @melnikovi -/app/code/Magento/SendFriend/ @buskamuza @melnikovi -/app/code/Magento/Marketplace/ @buskamuza -/app/code/Magento/MediaStorage/ @buskamuza -/lib/internal/Magento/Framework/Amqp/ @tariqjawed83 @paliarush -/lib/internal/Magento/Framework/Bulk/ @tariqjawed83 @paliarush -/lib/internal/Magento/Framework/Communication/ @tariqjawed83 @paliarush -/app/code/Magento/Amqp/ @tariqjawed83 @paliarush -/app/code/Magento/AsynchronousOperations/ @tariqjawed83 @paliarush -/app/code/Magento/MessageQueue/ @tariqjawed83 @paliarush -/app/code/Magento/MysqlMq/ @tariqjawed83 @paliarush -/app/code/Magento/Sales/ @joni-jones -/app/code/Magento/SalesInventory/ @joni-jones -/app/code/Magento/SalesSequence/ @joni-jones -/lib/internal/Magento/Framework/Event/ @buskamuza @kandy -/lib/internal/Magento/Framework/Interception/ @buskamuza @kandy -/lib/internal/Magento/Framework/ObjectManager/ @buskamuza @kandy -/app/code/Magento/PageCache/ @Andrey @kokoc @paliarush -/app/code/Magento/Authorizenet/ @joni-jones -/app/code/Magento/Braintree/ @joni-jones -/app/code/Magento/OfflinePayments/ @joni-jones -/app/code/Magento/Payment/ @joni-jones -/app/code/Magento/Paypal/ @joni-jones -/app/code/Magento/Signifyd/ @joni-jones -/app/code/Magento/Vault/ @joni-jones -/lib/internal/Magento/Framework/Pricing/ @akaplya -/app/code/Magento/AdvancedPricingImportExport/ @akaplya -/app/code/Magento/CurrencySymbol/ @akaplya -/app/code/Magento/Msrp/ @akaplya -/app/code/Magento/Tax/ @akaplya -/app/code/Magento/TaxImportExport/ @akaplya -/app/code/Magento/Weee/ @akaplya -/app/code/Magento/CatalogRule/ @kokoc -/app/code/Magento/CatalogRuleConfigurable/ @kokoc -/app/code/Magento/CatalogWidget/ @kokoc -/app/code/Magento/Rule/ @kokoc -/app/code/Magento/SalesRule/ @akaplya -/app/code/Magento/ReleaseNotification/ @paliarush -/app/code/Magento/Analytics/ @tariqjawed83 @buskamuza -/app/code/Magento/GoogleAnalytics/ @tariqjawed83 @buskamuza -/app/code/Magento/NewRelicReporting/ @tariqjawed83 @buskamuza -/app/code/Magento/Reports/ @tariqjawed83 @buskamuza -/app/code/Magento/ReviewAnalytics/ @tariqjawed83 @buskamuza -/app/code/Magento/SalesAnalytics/ @tariqjawed83 @buskamuza -/app/code/Magento/WishlistAnalytics/ @tariqjawed83 @buskamuza -/app/code/Magento/GoogleOptimizer/ @paliarush -/app/code/Magento/Robots/ @paliarush -/app/code/Magento/Sitemap/ @paliarush -/lib/internal/Magento/Framework/Search/ @kokoc -/app/code/Magento/AdvancedSearch/ @kokoc -/app/code/Magento/Elasticsearch/ @kokoc -/app/code/Magento/Search/ @kokoc -/lib/internal/Magento/Framework/Acl/ @kokoc -/lib/internal/Magento/Framework/Authorization/ @kokoc -/lib/internal/Magento/Framework/Encryption/ @kokoc -/app/code/Magento/Authorization/ @kokoc -/app/code/Magento/Captcha/ @kokoc -/app/code/Magento/EncryptionKey/ @kokoc -/app/code/Magento/Security/ @kokoc -/lib/internal/Magento/Framework/Autoload/ @buskamuza -/lib/internal/Magento/Framework/Backup/ @buskamuza -/lib/internal/Magento/Framework/Composer/ @buskamuza -/lib/internal/Magento/Framework/Setup/ @buskamuza -/app/code/Magento/Backup/ @buskamuza -/setup/ @buskamuza -/app/code/Magento/Dhl/ @joni-jones -/app/code/Magento/Fedex/ @joni-jones -/app/code/Magento/OfflineShipping/ @joni-jones -/app/code/Magento/Shipping/ @joni-jones -/app/code/Magento/Ups/ @joni-jones -/app/code/Magento/Usps/ @joni-jones -/app/code/Magento/Store/ @akaplya -/lib/internal/Magento/Framework/TestFramework/ @paliarush -/dev/tests/integration/framework/ @buskamuza -/dev/tests/setup-integration/framework/ @paliarush -/dev/tests/static/framework/ @paliarush -/dev/tests/unit/ @paliarush -/dev/tests/api-functional/ @paliarush -/app/code/Magento/UrlRewrite/ @kokoc -/lib/internal/Magento/Framework/Image/ @buskamuza -/lib/internal/Magento/Framework/Mail/ @melnikovi -/lib/internal/Magento/Framework/Filter/ @melnikovi -/lib/internal/Magento/Framework/Validation/ @melnikovi -/lib/internal/Magento/Framework/Validator/ @melnikovi -/lib/internal/Magento/Framework/Api/ @paliarush -/lib/internal/Magento/Framework/GraphQL/ @paliarush -/lib/internal/Magento/Framework/Oauth/ @paliarush -/lib/internal/Magento/Framework/Webapi/ @paliarush -/app/code/Magento/GraphQL/ @paliarush -/app/code/Magento/Integration/ @paliarush -/app/code/Magento/Swagger/ @paliarush -/app/code/Magento/Webapi/ @paliarush -/app/code/Magento/WebapiSecurity/ @paliarush - -composer.json @buskamuza -*.js @DrewML -.htaccess* @akaplya -nginx.conf* @akaplya diff --git a/.github/ISSUE_TEMPLATE/developer-experience-issue.md b/.github/ISSUE_TEMPLATE/developer-experience-issue.md index 423d4818fb31c..713ce410a16e1 100644 --- a/.github/ISSUE_TEMPLATE/developer-experience-issue.md +++ b/.github/ISSUE_TEMPLATE/developer-experience-issue.md @@ -1,6 +1,7 @@ --- name: Developer experience issue about: Issues related to customization, extensibility, modularity +labels: 'Triage: Dev.Experience' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index f64185773cab4..7b6a8d199f28f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,6 +1,7 @@ --- name: Feature request about: Please consider reporting directly to https://github.com/magento/community-features +labels: 'feature request' --- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 11da06ee704c6..5d6620ce19228 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,6 +15,9 @@ Letting us know what has changed and why it needed changing will help us validate this pull request. --> +### Related Pull Requests + + ### Fixed Issues (if relevant) - + When you enable this option your site may slow down. Magento\Config\Model\Config\Source\Yesno - + validate-digits validate-zero-or-greater 1 - + Magento\Config\Model\Config\Source\Yesno When you enable this option your site may slow down. diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml index 2a04128099345..999d565353329 100644 --- a/app/code/Magento/Analytics/etc/adminhtml/system.xml +++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml @@ -7,29 +7,29 @@ --> -
+
general Magento_Analytics::analytics_settings - + For more information, see our terms and conditions.]]> - + Magento\Config\Model\Config\Source\Enabledisable Magento\Analytics\Model\Config\Backend\Enabled Magento\Analytics\Block\Adminhtml\System\Config\SubscriptionStatusLabel analytics/subscription/enabled - + Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel Magento\Analytics\Model\Config\Backend\CollectionTime - + Industry Data In order to personalize your Advanced Reporting experience, please select your industry. @@ -40,7 +40,7 @@ 1 - + Learn more about
advanced - + - + validate-zero-or-greater validate-digits diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml index 3f2037f70b2df..fe91967ed4a62 100644 --- a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml +++ b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml @@ -10,88 +10,88 @@
- + Magento\Config\Model\Config\Source\Yesno - + Magento\Authorizenet\Model\Source\PaymentAction - + Magento\Config\Model\Config\Backend\Encrypted - + Magento\Config\Model\Config\Backend\Encrypted - + Magento\Config\Model\Config\Backend\Encrypted - + Magento\Config\Model\Config\Backend\Encrypted - + Magento\Sales\Model\Config\Source\Order\Status\Processing - + Magento\Config\Model\Config\Source\Yesno - + - + - + Magento\Config\Model\Config\Source\Locale\Currency - + Magento\Config\Model\Config\Source\Yesno - + Magento\Config\Model\Config\Source\Yesno - + validate-email - + Magento\Authorizenet\Model\Source\Cctype - + Magento\Config\Model\Config\Source\Yesno - + Magento\Payment\Model\Config\Source\Allspecificcountries - + Magento\Directory\Model\Config\Source\Country - + validate-number validate-zero-or-greater - + validate-number validate-zero-or-greater - + validate-number diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml index 7cd00959d9772..86b6d3a198d81 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml @@ -10,7 +10,7 @@
- + Magento\Config\Model\Config\Source\Yesno @@ -25,17 +25,17 @@ payment/authorizenet_acceptjs/title - + Magento\AuthorizenetAcceptjs\Model\Adminhtml\Source\Environment payment/authorizenet_acceptjs/environment - + Magento\AuthorizenetAcceptjs\Model\Adminhtml\Source\PaymentAction payment/authorizenet_acceptjs/payment_action - + Magento\Config\Model\Config\Backend\Encrypted payment/authorizenet_acceptjs/login @@ -44,7 +44,7 @@ 1 - + Magento\Config\Model\Config\Backend\Encrypted payment/authorizenet_acceptjs/trans_key @@ -53,7 +53,7 @@ 1 - + payment/authorizenet_acceptjs/public_client_key required-entry @@ -61,7 +61,7 @@ 1 - + Magento\Config\Model\Config\Backend\Encrypted payment/authorizenet_acceptjs/trans_signature_key @@ -70,7 +70,7 @@ 1 - + Magento\Config\Model\Config\Backend\Encrypted payment/authorizenet_acceptjs/trans_md5 @@ -79,55 +79,55 @@ - + 0 - + Magento\Config\Model\Config\Source\Locale\Currency payment/authorizenet_acceptjs/currency - + Magento\Config\Model\Config\Source\Yesno payment/authorizenet_acceptjs/debug - + Magento\Config\Model\Config\Source\Yesno payment/authorizenet_acceptjs/email_customer - + Magento\Config\Model\Config\Source\Yesno payment/authorizenet_acceptjs/cvv_enabled - + Magento\AuthorizenetAcceptjs\Model\Adminhtml\Source\Cctype payment/authorizenet_acceptjs/cctypes - + Magento\Payment\Model\Config\Source\Allspecificcountries payment/authorizenet_acceptjs/allowspecific - + Magento\Directory\Model\Config\Source\Country payment/authorizenet_acceptjs/specificcountry - + payment/authorizenet_acceptjs/min_order_total validate-number validate-zero-or-greater - + payment/authorizenet_acceptjs/max_order_total validate-number validate-zero-or-greater - + validate-number payment/authorizenet_acceptjs/sort_order diff --git a/app/code/Magento/AuthorizenetCardinal/etc/adminhtml/system.xml b/app/code/Magento/AuthorizenetCardinal/etc/adminhtml/system.xml index 2be287a5e8743..cf8ad28d26d0e 100644 --- a/app/code/Magento/AuthorizenetCardinal/etc/adminhtml/system.xml +++ b/app/code/Magento/AuthorizenetCardinal/etc/adminhtml/system.xml @@ -10,7 +10,7 @@
- + Magento\Config\Model\Config\Source\Yesno three_d_secure/cardinal/enabled_authorizenet diff --git a/app/code/Magento/Backend/Block/Media/Uploader.php b/app/code/Magento/Backend/Block/Media/Uploader.php index 84fa487281ac8..e95b6397cd244 100644 --- a/app/code/Magento/Backend/Block/Media/Uploader.php +++ b/app/code/Magento/Backend/Block/Media/Uploader.php @@ -87,7 +87,7 @@ protected function _construct() $this->setId($this->getId() . '_Uploader'); - $uploadUrl = $this->_urlBuilder->addSessionParam()->getUrl('adminhtml/*/upload'); + $uploadUrl = $this->_urlBuilder->getUrl('adminhtml/*/upload'); $this->getConfig()->setUrl($uploadUrl); $this->getConfig()->setParams(['form_key' => $this->getFormKey()]); $this->getConfig()->setFileField('file'); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Denied/Index.php b/app/code/Magento/Backend/Controller/Adminhtml/Denied/Index.php new file mode 100644 index 0000000000000..c06c0a0f01650 --- /dev/null +++ b/app/code/Magento/Backend/Controller/Adminhtml/Denied/Index.php @@ -0,0 +1,27 @@ +setHasAvailableResources(false); } - $action = '*/*/denied'; + $action = '*/denied'; } return $action; } diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml index ce33f01c60141..960e77db7194f 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml @@ -24,4 +24,4 @@ - \ No newline at end of file + diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginWithRestrictPermissionTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginWithRestrictPermissionTest.xml new file mode 100644 index 0000000000000..e664a4a5f3e2f --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginWithRestrictPermissionTest.xml @@ -0,0 +1,61 @@ + + + + + + + + + <stories value="Login on the Admin Login page" /> + <testCaseId value="MC-29321" /> + <severity value="MAJOR" /> + <description value="Check login with restrict role."/> + <group value="login"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="logIn"/> + <!--Create user role--> + <actionGroup ref="AdminFillUserRoleRequiredDataActionGroup" stepKey="fillUserRoleRequiredData"> + <argument name="User" value="adminRole"/> + <argument name="restrictedRole" value="Media Gallery"/> + </actionGroup> + <actionGroup ref="AdminUserClickRoleResourceTabActionGroup" stepKey="switchToRoleResourceTab"/> + <actionGroup ref="AdminAddRestrictedRoleActionGroup" stepKey="addRestrictedRoleStores"> + <argument name="User" value="adminRole"/> + <argument name="restrictedRole" value="Media Gallery"/> + </actionGroup> + <actionGroup ref="AdminUserSaveRoleActionGroup" stepKey="saveRole"/> + <!--Create user and assign role to it--> + <actionGroup ref="AdminCreateUserActionGroup" stepKey="createAdminUser"> + <argument name="role" value="adminRole"/> + <argument name="User" value="admin2"/> + </actionGroup> + </before> + <after> + <actionGroup ref="logout" stepKey="logoutAsSaleRoleUser"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Delete created data--> + <actionGroup ref="AdminUserOpenAdminRolesPageActionGroup" stepKey="navigateToUserRoleGrid"/> + <actionGroup ref="AdminDeleteRoleActionGroup" stepKey="deleteUserRole"> + <argument name="role" value="adminRole"/> + </actionGroup> + <actionGroup ref="AdminOpenAdminUsersPageActionGroup" stepKey="goToAllUsersPage"/> + <actionGroup ref="AdminDeleteNewUserActionGroup" stepKey="deleteUser"> + <argument name="userName" value="{{admin2.username}}"/> + </actionGroup> + </after> + <!--Log out of admin and login with newly created user--> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsNewUser"> + <argument name="adminUser" value="admin2"/> + </actionGroup> + <actionGroup ref="AssertUserRoleRestrictedAccessActionGroup" stepKey="assertRestrictPage"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Unit/Model/UrlTest.php b/app/code/Magento/Backend/Test/Unit/Model/UrlTest.php index ad42108cb5eea..1ef3b3980441e 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/UrlTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/UrlTest.php @@ -190,7 +190,7 @@ public function testFindFirstAvailableMenuDenied() $this->_menuMock->expects($this->any())->method('getFirstAvailableChild')->will($this->returnValue(null)); - $this->assertEquals('*/*/denied', $this->_model->findFirstAvailableMenu()); + $this->assertEquals('*/denied', $this->_model->findFirstAvailableMenu()); } public function testFindFirstAvailableMenu() diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 4a92ed8124bf8..3a2b3554cc4a0 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -20,7 +20,7 @@ @deprecated Magento does not support custom disabling/enabling modules output since 2.2.0 version. Section 'Advanced' was disabled. This section will be removed from code in one release. --> - <section id="advanced" translate="label" type="text" sortOrder="910" showInDefault="0" showInWebsite="0" showInStore="0"> + <section id="advanced" translate="label" type="text" sortOrder="910"> <label>Advanced</label> <tab>advanced</tab> <resource>Magento_Config::advanced</resource> @@ -131,7 +131,7 @@ </depends> <comment>Add the following parameter to the URL to show template hints ?templatehints=[parameter_value]</comment> </field> - <field id="template_hints_admin" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="template_hints_admin" translate="label" type="select" sortOrder="20" showInDefault="1"> <label>Enable Template Path Hints for Admin</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -162,7 +162,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Translate</backend_model> </field> - <field id="active_admin" translate="label comment" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="active_admin" translate="label comment" type="select" sortOrder="20" showInDefault="1"> <label>Enabled for Admin</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Translate</backend_model> @@ -197,18 +197,18 @@ <comment>Minification is not applied in developer mode.</comment> </field> </group> - <group id="image" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="image" translate="label" type="text" sortOrder="120" showInDefault="1"> <label>Image Processing Settings</label> - <field id="default_adapter" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="default_adapter" translate="label comment" type="select" sortOrder="10" showInDefault="1" canRestore="1"> <label>Image Adapter</label> <source_model>Magento\Config\Model\Config\Source\Image\Adapter</source_model> <backend_model>Magento\Config\Model\Config\Backend\Image\Adapter</backend_model> <comment>When the adapter was changed, please flush Catalog Images Cache.</comment> </field> </group> - <group id="static" translate="label" type="text" sortOrder="130" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="static" translate="label" type="text" sortOrder="130" showInDefault="1"> <label>Static Files Settings</label> - <field id="sign" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="sign" translate="label" type="select" sortOrder="10" showInDefault="1" canRestore="1"> <label>Sign Static Files</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -220,7 +220,7 @@ <resource>Magento_Config::config_general</resource> <group id="country" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Country Options</label> - <field id="allow" translate="label" type="multiselect" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allow" translate="label" type="multiselect" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allow Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> @@ -229,7 +229,7 @@ <label>Default Country</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> </field> - <field id="eu_countries" translate="label" type="multiselect" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="eu_countries" translate="label" type="multiselect" sortOrder="30" showInDefault="1" canRestore="1"> <label>European Union Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> </field> @@ -241,7 +241,7 @@ </group> <group id="locale" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Locale Options</label> - <field id="timezone" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="timezone" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1"> <label>Timezone</label> <source_model>Magento\Config\Model\Config\Source\Locale\Timezone</source_model> <backend_model>Magento\Config\Model\Config\Backend\Locale\Timezone</backend_model> @@ -271,35 +271,35 @@ <field id="hours" translate="label" type="text" sortOrder="22" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Store Hours of Operation</label> </field> - <field id="country_id" translate="label" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="country_id" translate="label" type="select" sortOrder="25" showInDefault="1" showInWebsite="1"> <label>Country</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <frontend_class>countries</frontend_class> <can_be_empty>1</can_be_empty> </field> - <field id="region_id" translate="label" type="text" sortOrder="27" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="region_id" translate="label" type="text" sortOrder="27" showInDefault="1" showInWebsite="1"> <label>Region/State</label> </field> - <field id="postcode" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="postcode" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>ZIP/Postal Code</label> </field> - <field id="city" translate="label" type="text" sortOrder="45" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="city" translate="label" type="text" sortOrder="45" showInDefault="1" showInWebsite="1"> <label>City</label> </field> - <field id="street_line1" translate="label" type="text" sortOrder="55" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="street_line1" translate="label" type="text" sortOrder="55" showInDefault="1" showInWebsite="1"> <label>Street Address</label> </field> - <field id="street_line2" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="street_line2" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1"> <label>Street Address Line 2</label> </field> - <field id="merchant_vat_number" translate="label" type="text" sortOrder="61" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="merchant_vat_number" translate="label" type="text" sortOrder="61" showInDefault="1" showInWebsite="1"> <label>VAT Number</label> <can_be_empty>1</can_be_empty> </field> </group> - <group id="single_store_mode" translate="label" type="text" sortOrder="150" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="single_store_mode" translate="label" type="text" sortOrder="150" showInDefault="1"> <label>Single-Store Mode</label> - <field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1"> <label>Enable Single-Store Mode</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>This setting will not be taken into account if system has more than one store view.</comment> @@ -326,7 +326,7 @@ <validate>validate-digits validate-digits-range digits-range-0-65535</validate> <comment>Please enter at least 0 and at most 65535 (For Windows server only).</comment> </field> - <field id="set_return_path" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="set_return_path" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Set Return-Path</label> <source_model>Magento\Config\Model\Config\Source\Yesnocustom</source_model> </field> @@ -341,12 +341,12 @@ </group> <group id="upload_configuration" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Images Upload Configuration</label> - <field id="enable_resize" translate="label" type="select" sortOrder="200" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="enable_resize" translate="label" type="select" sortOrder="200" showInDefault="1" canRestore="1"> <label>Enable Frontend Resize</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Resize performed via javascript before file upload.</comment> </field> - <field id="max_width" translate="label comment" type="text" sortOrder="300" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="max_width" translate="label comment" type="text" sortOrder="300" showInDefault="1" canRestore="1"> <label>Maximum Width</label> <validate>validate-greater-than-zero validate-number required-entry</validate> <comment>Maximum allowed width for uploaded image.</comment> @@ -354,7 +354,7 @@ <field id="enable_resize">1</field> </depends> </field> - <field id="max_height" translate="label comment" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="max_height" translate="label comment" type="text" sortOrder="400" showInDefault="1" canRestore="1"> <label>Maximum Height</label> <validate>validate-greater-than-zero validate-number required-entry</validate> <comment>Maximum allowed height for uploaded image.</comment> @@ -364,37 +364,37 @@ </field> </group> </section> - <section id="admin" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <section id="admin" translate="label" type="text" sortOrder="20" showInDefault="1"> <label>Admin</label> <tab>advanced</tab> <resource>Magento_Config::config_admin</resource> - <group id="emails" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="emails" translate="label" type="text" sortOrder="10" showInDefault="1"> <label>Admin User Emails</label> - <field id="forgot_email_template" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="forgot_email_template" translate="label comment" type="select" sortOrder="10" showInDefault="1" canRestore="1"> <label>Forgot Password Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> </field> - <field id="forgot_email_identity" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="forgot_email_identity" translate="label" type="select" sortOrder="20" showInDefault="1" canRestore="1"> <label>Forgot and Reset Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> </field> </group> - <group id="startup" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="startup" translate="label" type="text" sortOrder="20" showInDefault="1"> <label>Startup Page</label> - <field id="menu_item_id" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="menu_item_id" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Startup Page</label> <source_model>Magento\Config\Model\Config\Source\Admin\Page</source_model> </field> </group> - <group id="url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="url" translate="label" type="text" sortOrder="30" showInDefault="1"> <label>Admin Base URL</label> - <field id="use_custom" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_custom" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Use Custom Admin URL</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Admin\Usecustom</backend_model> </field> - <field id="custom" translate="label comment" type="text" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="custom" translate="label comment" type="text" sortOrder="2" showInDefault="1" canRestore="1"> <label>Custom Admin URL</label> <backend_model>Magento\Config\Model\Config\Backend\Admin\Custom</backend_model> <depends> @@ -402,12 +402,12 @@ </depends> <comment>Make sure that base URL ends with '/' (slash), e.g. http://yourdomain/magento/</comment> </field> - <field id="use_custom_path" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_custom_path" translate="label" type="select" sortOrder="3" showInDefault="1" canRestore="1"> <label>Use Custom Admin Path</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Admin\Custompath</backend_model> </field> - <field id="custom_path" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="custom_path" translate="label comment" type="text" sortOrder="4" showInDefault="1" canRestore="1"> <label>Custom Admin Path</label> <validate>required-entry validate-alphanum</validate> <backend_model>Magento\Config\Model\Config\Backend\Admin\Custompath</backend_model> @@ -417,33 +417,33 @@ <comment>You will have to sign in after you save your custom admin path.</comment> </field> </group> - <group id="security" translate="label" type="text" sortOrder="35" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="security" translate="label" type="text" sortOrder="35" showInDefault="1"> <label>Security</label> - <field id="password_reset_link_expiration_period" translate="label comment" type="text" sortOrder="7" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="password_reset_link_expiration_period" translate="label comment" type="text" sortOrder="7" showInDefault="1" canRestore="1"> <label>Recovery Link Expiration Period (hours)</label> <comment>Please enter a number 1 or greater in this field.</comment> <validate>required-entry integer validate-greater-than-zero</validate> <backend_model>Magento\Config\Model\Config\Backend\Admin\Password\Link\Expirationperiod</backend_model> </field> - <field id="use_form_key" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_form_key" translate="label" type="select" sortOrder="10" showInDefault="1" canRestore="1"> <label>Add Secret Key to URLs</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Admin\Usesecretkey</backend_model> </field> - <field id="use_case_sensitive_login" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="use_case_sensitive_login" translate="label" type="select" sortOrder="20" showInDefault="1" canRestore="1"> <label>Login is Case Sensitive</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="session_lifetime" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="session_lifetime" translate="label comment" sortOrder="30" showInDefault="1" canRestore="1"> <label>Admin Session Lifetime (seconds)</label> <comment>Please enter at least 60 and at most 31536000 (one year).</comment> <backend_model>Magento\Backend\Model\Config\SessionLifetime\BackendModel</backend_model> <validate>validate-digits validate-digits-range digits-range-60-31536000</validate> </field> </group> - <group id="dashboard" translate="label" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="dashboard" translate="label" sortOrder="40" showInDefault="1"> <label>Dashboard</label> - <field id="enable_charts" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="enable_charts" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Enable Charts</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -455,7 +455,7 @@ <resource>Magento_Config::web</resource> <group id="url" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Url Options</label> - <field id="use_store" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_store" translate="label comment" type="select" sortOrder="10" showInDefault="1" canRestore="1"> <label>Add Store Code to Urls</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Store</backend_model> @@ -529,7 +529,7 @@ <backend_model>Magento\Config\Model\Config\Backend\Secure</backend_model> <comment>Enter https protocol to use Secure URLs on Storefront.</comment> </field> - <field id="use_in_adminhtml" translate="label comment" type="select" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_in_adminhtml" translate="label comment" type="select" sortOrder="60" showInDefault="1" canRestore="1"> <label>Use Secure URLs in Admin</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Config\Model\Config\Backend\Secure</backend_model> @@ -555,7 +555,7 @@ <field id="use_in_adminhtml">1</field> </depends> </field> - <field id="offloader_header" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="offloader_header" translate="label" type="text" sortOrder="90" showInDefault="1" canRestore="1"> <label>Offloader header</label> </field> </group> @@ -568,21 +568,21 @@ <label>Default No-route URL</label> </field> </group> - <group id="session" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="session" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1"> <label>Session Validation Settings</label> - <field id="use_remote_addr" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_remote_addr" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Validate REMOTE_ADDR</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="use_http_via" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_http_via" translate="label" type="select" sortOrder="20" showInDefault="1" canRestore="1"> <label>Validate HTTP_VIA</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="use_http_x_forwarded_for" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_http_x_forwarded_for" translate="label" type="select" sortOrder="30" showInDefault="1" canRestore="1"> <label>Validate HTTP_X_FORWARDED_FOR</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="use_http_user_agent" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_http_user_agent" translate="label" type="select" sortOrder="40" showInDefault="1" canRestore="1"> <label>Validate HTTP_USER_AGENT</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Backend/etc/config.xml b/app/code/Magento/Backend/etc/config.xml index 8283fa18dd370..6c568370d9610 100644 --- a/app/code/Magento/Backend/etc/config.xml +++ b/app/code/Magento/Backend/etc/config.xml @@ -11,9 +11,6 @@ <template> <minify_html>0</minify_html> </template> - <static> - <sign>1</sign> - </static> </dev> <system> <media_storage_configuration> diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/translate.js b/app/code/Magento/Backend/view/adminhtml/web/js/translate.js index d6e1547600c4e..eae1394c15027 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/translate.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/translate.js @@ -35,7 +35,7 @@ define([ * @return {String} */ this.translate = function (text) { - return _data[text] ? _data[text] : text; + return typeof _data[text] === 'string' ? _data[text] : text; }; return this; diff --git a/app/code/Magento/Backup/etc/adminhtml/system.xml b/app/code/Magento/Backup/etc/adminhtml/system.xml index 78e0aae1dd4c2..577762037ceb4 100644 --- a/app/code/Magento/Backup/etc/adminhtml/system.xml +++ b/app/code/Magento/Backup/etc/adminhtml/system.xml @@ -8,21 +8,21 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="system"> - <group id="backup" translate="label" type="text" sortOrder="500" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="backup" translate="label" type="text" sortOrder="500" showInDefault="1"> <label>Backup Settings</label> - <field id="functionality_enabled" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="functionality_enabled" translate="label" type="select" sortOrder="5" showInDefault="1"> <label>Enable Backup</label> <comment>Disabled by default for security reasons.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1"> <label>Enable Scheduled Backup</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> <field id="functionality_enabled">1</field> </depends> </field> - <field id="type" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="type" translate="label" type="select" sortOrder="20" showInDefault="1"> <label>Scheduled Backup Type</label> <depends> <field id="enabled">1</field> @@ -30,14 +30,14 @@ </depends> <source_model>Magento\Backup\Model\Config\Source\Type</source_model> </field> - <field id="time" translate="label" type="time" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="time" translate="label" type="time" sortOrder="30" showInDefault="1"> <label>Start Time</label> <depends> <field id="enabled">1</field> <field id="functionality_enabled">1</field> </depends> </field> - <field id="frequency" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="frequency" translate="label" type="select" sortOrder="40" showInDefault="1"> <label>Frequency</label> <depends> <field id="enabled">1</field> @@ -46,7 +46,7 @@ <source_model>Magento\Cron\Model\Config\Source\Frequency</source_model> <backend_model>Magento\Backup\Model\Config\Backend\Cron</backend_model> </field> - <field id="maintenance" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="maintenance" translate="label comment" type="select" sortOrder="50" showInDefault="1"> <label>Maintenance Mode</label> <comment>Please put your store into maintenance mode during backup.</comment> <depends> diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index bd4346e095c6d..06268536e880e 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="payment"> - <group id="braintree_section" sortOrder="6" showInDefault="0" showInWebsite="0" showInStore="0"> + <group id="braintree_section" sortOrder="6"> <group id="braintree" translate="label comment" type="text" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Braintree</label> <comment><![CDATA[Accept credit/debit cards and PayPal in your Magento store.<br/>No setup or monthly fees and your customers never leave your store to complete the purchase.]]></comment> @@ -16,7 +16,7 @@ <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> <attribute type="activity_path">payment/braintree/active</attribute> <attribute type="displayIn">recommended_solutions</attribute> - <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1"> <label>Enable this Solution</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree/active</config_path> @@ -24,7 +24,7 @@ <group id="braintree_required"/> </requires> </field> - <field id="active_braintree_paypal" translate="label" type="select" sortOrder="11" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="active_braintree_paypal" translate="label" type="select" sortOrder="11" showInDefault="1" showInWebsite="1"> <label>Enable PayPal through Braintree</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/active</config_path> @@ -32,7 +32,7 @@ <group id="braintree_required"/> </requires> </field> - <field id="braintree_cc_vault_active" translate="label" type="select" sortOrder="12" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="braintree_cc_vault_active" translate="label" type="select" sortOrder="12" showInDefault="1" showInWebsite="1"> <label>Vault Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_cc_vault/active</config_path> @@ -52,86 +52,86 @@ <label>Title</label> <config_path>payment/braintree/title</config_path> </field> - <field id="environment" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="environment" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Environment</label> <source_model>Magento\Braintree\Model\Adminhtml\Source\Environment</source_model> <config_path>payment/braintree/environment</config_path> </field> - <field id="payment_action" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="payment_action" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>Payment Action</label> <source_model>Magento\Braintree\Model\Adminhtml\Source\PaymentAction</source_model> <config_path>payment/braintree/payment_action</config_path> </field> - <field id="merchant_id" translate="label" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="merchant_id" translate="label" sortOrder="90" showInDefault="1" showInWebsite="1"> <label>Merchant ID</label> <config_path>payment/braintree/merchant_id</config_path> </field> - <field id="public_key" translate="label" type="obscure" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="public_key" translate="label" type="obscure" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Public Key</label> <config_path>payment/braintree/public_key</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="private_key" translate="label" type="obscure" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="private_key" translate="label" type="obscure" sortOrder="110" showInDefault="1" showInWebsite="1"> <label>Private Key</label> <config_path>payment/braintree/private_key</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> </group> - <group id="braintree_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="20"> + <group id="braintree_advanced" translate="label" showInDefault="1" showInWebsite="1" sortOrder="20"> <label>Advanced Braintree Settings</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - <field id="braintree_cc_vault_title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="braintree_cc_vault_title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1"> <label>Vault Title</label> <config_path>payment/braintree_cc_vault/title</config_path> </field> - <field id="merchant_account_id" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="merchant_account_id" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Merchant Account ID</label> <comment>If you don't specify the merchant account to use to process a transaction, Braintree will process it using your default merchant account.</comment> <config_path>payment/braintree/merchant_account_id</config_path> </field> - <field id="fraudprotection" translate="label comment" type="select" sortOrder="34" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="fraudprotection" translate="label comment" type="select" sortOrder="34" showInDefault="1" showInWebsite="1"> <label>Advanced Fraud Protection</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Be sure to Enable Advanced Fraud Protection in Your Braintree Account in Settings/Processing Section</comment> <config_path>payment/braintree/fraudprotection</config_path> </field> - <field id="debug" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree/debug</config_path> </field> - <field id="useccv" translate="label comment" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="useccv" translate="label comment" type="select" sortOrder="150" showInDefault="1" showInWebsite="1"> <label>CVV Verification</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Be sure to Enable AVS and/or CVV in Your Braintree Account in Settings/Processing Section.</comment> <config_path>payment/braintree/useccv</config_path> </field> - <field id="cctypes" translate="label" type="multiselect" sortOrder="160" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="cctypes" translate="label" type="multiselect" sortOrder="160" showInDefault="1" showInWebsite="1"> <label>Credit Card Types</label> <source_model>Magento\Braintree\Model\Adminhtml\Source\CcType</source_model> <config_path>payment/braintree/cctypes</config_path> </field> - <field id="sort_order" translate="label" type="text" sortOrder="230" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="230" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <frontend_class>validate-number</frontend_class> <config_path>payment/braintree/sort_order</config_path> </field> </group> - <group id="braintree_country_specific" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="30"> + <group id="braintree_country_specific" translate="label" showInDefault="1" showInWebsite="1" sortOrder="30"> <label>Country Specific Settings</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="200" showInDefault="1" showInWebsite="1"> <label>Payment from Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> <config_path>payment/braintree/allowspecific</config_path> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="210" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="210" showInDefault="1" showInWebsite="1"> <label>Payment from Specific Countries</label> <source_model>Magento\Braintree\Model\Adminhtml\System\Config\Country</source_model> <can_be_empty>1</can_be_empty> <config_path>payment/braintree/specificcountry</config_path> </field> - <field id="countrycreditcard" translate="label" sortOrder="220" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="countrycreditcard" translate="label" sortOrder="220" showInDefault="1" showInWebsite="1"> <label>Country Specific Credit Card Types</label> <frontend_model>Magento\Braintree\Block\Adminhtml\Form\Field\CountryCreditCard</frontend_model> <backend_model>Magento\Braintree\Model\Adminhtml\System\Config\CountryCreditCard</backend_model> @@ -146,7 +146,7 @@ <config_path>payment/braintree_paypal/title</config_path> <comment>It is recommended to set this value to "PayPal" per store views.</comment> </field> - <field id="braintree_paypal_vault_active" translate="label" type="select" sortOrder="21" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="braintree_paypal_vault_active" translate="label" type="select" sortOrder="21" showInDefault="1" showInWebsite="1"> <label>Vault Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal_vault/active</config_path> @@ -154,7 +154,7 @@ <group id="braintree_required"/> </requires> </field> - <field id="sort_order" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <frontend_class>validate-number</frontend_class> <config_path>payment/braintree_paypal/sort_order</config_path> @@ -163,68 +163,68 @@ <label>Override Merchant Name</label> <config_path>payment/braintree_paypal/merchant_name_override</config_path> </field> - <field id="payment_action" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="payment_action" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>Payment Action</label> <source_model>Magento\Braintree\Model\Adminhtml\Source\PaymentAction</source_model> <config_path>payment/braintree_paypal/payment_action</config_path> </field> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="70" showInDefault="1" showInWebsite="1"> <label>Payment from Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> <config_path>payment/braintree_paypal/allowspecific</config_path> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="80" showInDefault="1" showInWebsite="1"> <label>Payment from Specific Countries</label> <source_model>Magento\Braintree\Model\Adminhtml\System\Config\Country</source_model> <can_be_empty>1</can_be_empty> <config_path>payment/braintree_paypal/specificcountry</config_path> </field> - <field id="require_billing_address" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="require_billing_address" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1"> <label>Require Customer's Billing Address</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/require_billing_address</config_path> <comment>This feature needs be enabled first for the merchant account through PayPal technical support.</comment> </field> - <field id="allow_shipping_address_override" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="allow_shipping_address_override" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Allow to Edit Shipping Address Entered During Checkout on PayPal Side</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/allow_shipping_address_override</config_path> </field> - <field id="debug" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/debug</config_path> </field> - <field id="display_on_shopping_cart" translate="label comment" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="display_on_shopping_cart" translate="label comment" type="select" sortOrder="120" showInDefault="1" showInWebsite="1"> <label>Display on Shopping Cart</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/display_on_shopping_cart</config_path> <comment>Also affects mini-shopping cart.</comment> </field> - <field id="skip_order_review" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="skip_order_review" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1"> <label>Skip Order Review</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/skip_order_review</config_path> </field> </group> - <group id="braintree_3dsecure" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="41"> + <group id="braintree_3dsecure" translate="label" showInDefault="1" showInWebsite="1" sortOrder="41"> <label>3D Secure Verification Settings</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - <field id="verify_3dsecure" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="verify_3dsecure" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1"> <label>3D Secure Verification</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree/verify_3dsecure</config_path> </field> - <field id="threshold_amount" translate="label" type="text" sortOrder="151" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="threshold_amount" translate="label" type="text" sortOrder="151" showInDefault="1" showInWebsite="1"> <label>Threshold Amount</label> <config_path>payment/braintree/threshold_amount</config_path> </field> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="152" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="152" showInDefault="1" showInWebsite="1"> <label>Verify for Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> <config_path>payment/braintree/verify_all_countries</config_path> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="153" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="153" showInDefault="1" showInWebsite="1"> <label>Verify for Specific Countries</label> <source_model>Magento\Braintree\Model\Adminhtml\System\Config\Country</source_model> <can_be_empty>1</can_be_empty> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index ea5200e4ba51f..c0ad38173c52d 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -269,6 +269,7 @@ define([ */ onError: function () { self.showError($t('Payment ' + self.getTitle() + ' can\'t be initialized')); + self.reInitPayPal(); } }, self.paypalButtonSelector); }, diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php index 077ebd4422aab..a2fff5739f2f9 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php @@ -7,6 +7,7 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier; +use Magento\Framework\DB\Select; use Magento\Framework\Indexer\DimensionalIndexerInterface; use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; @@ -394,8 +395,8 @@ private function calculateBundleOptionPrice($priceTable, $dimensions) $connection = $this->getConnection(); $this->prepareBundleSelectionTable(); - $this->calculateBundleSelectionPrice($dimensions, \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED); - $this->calculateBundleSelectionPrice($dimensions, \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC); + $this->calculateFixedBundleSelectionPrice(); + $this->calculateDynamicBundleSelectionPrice($dimensions); $this->prepareBundleOptionTable(); @@ -426,84 +427,17 @@ private function calculateBundleOptionPrice($priceTable, $dimensions) } /** - * Calculate bundle product selections price by product type + * Get base select for bundle selection price * - * @param array $dimensions - * @param int $priceType - * @return void + * @return Select * @throws \Exception - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - private function calculateBundleSelectionPrice($dimensions, $priceType) + private function getBaseBundleSelectionPriceSelect(): Select { - $connection = $this->getConnection(); - - if ($priceType == \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED) { - $selectionPriceValue = $connection->getCheckSql( - 'bsp.selection_price_value IS NULL', - 'bs.selection_price_value', - 'bsp.selection_price_value' - ); - $selectionPriceType = $connection->getCheckSql( - 'bsp.selection_price_type IS NULL', - 'bs.selection_price_type', - 'bsp.selection_price_type' - ); - $priceExpr = new \Zend_Db_Expr( - $connection->getCheckSql( - $selectionPriceType . ' = 1', - 'ROUND(i.price * (' . $selectionPriceValue . ' / 100),4)', - $connection->getCheckSql( - 'i.special_price > 0 AND i.special_price < 100', - 'ROUND(' . $selectionPriceValue . ' * (i.special_price / 100),4)', - $selectionPriceValue - ) - ) . '* bs.selection_qty' - ); - - $tierExpr = $connection->getCheckSql( - 'i.base_tier IS NOT NULL', - $connection->getCheckSql( - $selectionPriceType . ' = 1', - 'ROUND(i.base_tier - (i.base_tier * (' . $selectionPriceValue . ' / 100)),4)', - $connection->getCheckSql( - 'i.tier_percent > 0', - 'ROUND((1 - i.tier_percent / 100) * ' . $selectionPriceValue . ',4)', - $selectionPriceValue - ) - ) . ' * bs.selection_qty', - 'NULL' - ); - - $priceExpr = $connection->getLeastSql( - [ - $priceExpr, - $connection->getIfNullSql($tierExpr, $priceExpr), - ] - ); - } else { - $price = 'idx.min_price * bs.selection_qty'; - $specialExpr = $connection->getCheckSql( - 'i.special_price > 0 AND i.special_price < 100', - 'ROUND(' . $price . ' * (i.special_price / 100), 4)', - $price - ); - $tierExpr = $connection->getCheckSql( - 'i.tier_percent IS NOT NULL', - 'ROUND((1 - i.tier_percent / 100) * ' . $price . ', 4)', - 'NULL' - ); - $priceExpr = $connection->getLeastSql( - [ - $specialExpr, - $connection->getIfNullSql($tierExpr, $price), - ] - ); - } - $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $linkField = $metadata->getLinkField(); - $select = $connection->select()->from( + + $select = $this->getConnection()->select()->from( ['i' => $this->getBundlePriceTable()], ['entity_id', 'customer_group_id', 'website_id'] )->join( @@ -518,22 +452,173 @@ private function calculateBundleSelectionPrice($dimensions, $priceType) ['bs' => $this->getTable('catalog_product_bundle_selection')], 'bs.option_id = bo.option_id', ['selection_id'] - )->joinLeft( + ); + + return $select; + } + + /** + * Apply selections price for fixed bundles + * + * @return void + * @throws \Exception + */ + private function applyFixedBundleSelectionPrice() + { + $connection = $this->getConnection(); + + $selectionPriceValue = 'bsp.selection_price_value'; + $selectionPriceType = 'bsp.selection_price_type'; + $priceExpr = new \Zend_Db_Expr( + $connection->getCheckSql( + $selectionPriceType . ' = 1', + 'ROUND(i.price * (' . $selectionPriceValue . ' / 100),4)', + $connection->getCheckSql( + 'i.special_price > 0 AND i.special_price < 100', + 'ROUND(' . $selectionPriceValue . ' * (i.special_price / 100),4)', + $selectionPriceValue + ) + ) . '* bs.selection_qty' + ); + $tierExpr = $connection->getCheckSql( + 'i.base_tier IS NOT NULL', + $connection->getCheckSql( + $selectionPriceType . ' = 1', + 'ROUND(i.base_tier - (i.base_tier * (' . $selectionPriceValue . ' / 100)),4)', + $connection->getCheckSql( + 'i.tier_percent > 0', + 'ROUND((1 - i.tier_percent / 100) * ' . $selectionPriceValue . ',4)', + $selectionPriceValue + ) + ) . ' * bs.selection_qty', + 'NULL' + ); + $priceExpr = $connection->getLeastSql( + [ + $priceExpr, + $connection->getIfNullSql($tierExpr, $priceExpr), + ] + ); + + $select = $this->getBaseBundleSelectionPriceSelect(); + $select->joinInner( ['bsp' => $this->getTable('catalog_product_bundle_selection_price')], 'bs.selection_id = bsp.selection_id AND bsp.website_id = i.website_id', - [''] - )->join( + [] + )->where( + 'i.price_type=?', + \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED + )->columns( + [ + 'group_type' => $connection->getCheckSql("bo.type = 'select' OR bo.type = 'radio'", '0', '1'), + 'is_required' => 'bo.required', + 'price' => $priceExpr, + 'tier_price' => $tierExpr, + ] + ); + $query = $select->crossUpdateFromSelect($this->getBundleSelectionTable()); + $connection->query($query); + } + + /** + * Calculate selections price for fixed bundles + * + * @return void + * @throws \Exception + */ + private function calculateFixedBundleSelectionPrice() + { + $connection = $this->getConnection(); + + $selectionPriceValue = 'bs.selection_price_value'; + $selectionPriceType = 'bs.selection_price_type'; + $priceExpr = new \Zend_Db_Expr( + $connection->getCheckSql( + $selectionPriceType . ' = 1', + 'ROUND(i.price * (' . $selectionPriceValue . ' / 100),4)', + $connection->getCheckSql( + 'i.special_price > 0 AND i.special_price < 100', + 'ROUND(' . $selectionPriceValue . ' * (i.special_price / 100),4)', + $selectionPriceValue + ) + ) . '* bs.selection_qty' + ); + $tierExpr = $connection->getCheckSql( + 'i.base_tier IS NOT NULL', + $connection->getCheckSql( + $selectionPriceType . ' = 1', + 'ROUND(i.base_tier - (i.base_tier * (' . $selectionPriceValue . ' / 100)),4)', + $connection->getCheckSql( + 'i.tier_percent > 0', + 'ROUND((1 - i.tier_percent / 100) * ' . $selectionPriceValue . ',4)', + $selectionPriceValue + ) + ) . ' * bs.selection_qty', + 'NULL' + ); + $priceExpr = $connection->getLeastSql( + [ + $priceExpr, + $connection->getIfNullSql($tierExpr, $priceExpr), + ] + ); + + $select = $this->getBaseBundleSelectionPriceSelect(); + $select->where( + 'i.price_type=?', + \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED + )->columns( + [ + 'group_type' => $connection->getCheckSql("bo.type = 'select' OR bo.type = 'radio'", '0', '1'), + 'is_required' => 'bo.required', + 'price' => $priceExpr, + 'tier_price' => $tierExpr, + ] + ); + $query = $select->insertFromSelect($this->getBundleSelectionTable()); + $connection->query($query); + + $this->applyFixedBundleSelectionPrice(); + } + + /** + * Calculate selections price for dynamic bundles + * + * @param array $dimensions + * @return void + * @throws \Exception + */ + private function calculateDynamicBundleSelectionPrice($dimensions) + { + $connection = $this->getConnection(); + + $price = 'idx.min_price * bs.selection_qty'; + $specialExpr = $connection->getCheckSql( + 'i.special_price > 0 AND i.special_price < 100', + 'ROUND(' . $price . ' * (i.special_price / 100), 4)', + $price + ); + $tierExpr = $connection->getCheckSql( + 'i.tier_percent IS NOT NULL', + 'ROUND((1 - i.tier_percent / 100) * ' . $price . ', 4)', + 'NULL' + ); + $priceExpr = $connection->getLeastSql( + [ + $specialExpr, + $connection->getIfNullSql($tierExpr, $price), + ] + ); + + $select = $this->getBaseBundleSelectionPriceSelect(); + $select->join( ['idx' => $this->getMainTable($dimensions)], 'bs.product_id = idx.entity_id AND i.customer_group_id = idx.customer_group_id' . ' AND i.website_id = idx.website_id', [] - )->join( - ['e' => $this->getTable('catalog_product_entity')], - 'bs.product_id = e.entity_id AND e.required_options=0', - [] )->where( 'i.price_type=?', - $priceType + \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC )->columns( [ 'group_type' => $connection->getCheckSql("bo.type = 'select' OR bo.type = 'radio'", '0', '1'), @@ -542,7 +627,6 @@ private function calculateBundleSelectionPrice($dimensions, $priceType) 'tier_price' => $tierExpr, ] ); - $query = $select->insertFromSelect($this->getBundleSelectionTable()); $connection->query($query); } diff --git a/app/code/Magento/Captcha/etc/adminhtml/system.xml b/app/code/Magento/Captcha/etc/adminhtml/system.xml index a05430989418d..ac4197c976ea0 100644 --- a/app/code/Magento/Captcha/etc/adminhtml/system.xml +++ b/app/code/Magento/Captcha/etc/adminhtml/system.xml @@ -8,34 +8,34 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="admin"> - <group id="captcha" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="captcha" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>CAPTCHA</label> - <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Enable CAPTCHA in Admin</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="font" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="font" translate="label" type="select" sortOrder="2" showInDefault="1" canRestore="1"> <label>Font</label> <source_model>Magento\Captcha\Model\Config\Font</source_model> <depends> <field id="enable">1</field> </depends> </field> - <field id="forms" translate="label" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="forms" translate="label" type="multiselect" sortOrder="3" showInDefault="1" canRestore="1"> <label>Forms</label> <source_model>Magento\Captcha\Model\Config\Form\Backend</source_model> <depends> <field id="enable">1</field> </depends> </field> - <field id="mode" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="mode" translate="label" type="select" sortOrder="4" showInDefault="1" canRestore="1"> <label>Displaying Mode</label> <source_model>Magento\Captcha\Model\Config\Mode</source_model> <depends> <field id="enable">1</field> </depends> </field> - <field id="failed_attempts_login" translate="label comment" type="text" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="failed_attempts_login" translate="label comment" type="text" sortOrder="5" showInDefault="1" canRestore="1"> <label>Number of Unsuccessful Attempts to Login</label> <comment>If 0 is specified, CAPTCHA on the Login form will be always available.</comment> <depends> @@ -44,14 +44,14 @@ </depends> <frontend_class>required-entry validate-digits</frontend_class> </field> - <field id="timeout" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="timeout" translate="label" type="text" sortOrder="6" showInDefault="1" canRestore="1"> <label>CAPTCHA Timeout (minutes)</label> <depends> <field id="enable">1</field> </depends> <frontend_class>required-entry validate-digits</frontend_class> </field> - <field id="length" translate="label comment" type="text" sortOrder="7" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="length" translate="label comment" type="text" sortOrder="7" showInDefault="1" canRestore="1"> <label>Number of Symbols</label> <comment>Please specify 8 symbols at the most. Range allowed (e.g. 3-5)</comment> <depends> @@ -59,7 +59,7 @@ </depends> <frontend_class>required-entry</frontend_class> </field> - <field id="symbols" translate="label comment" type="text" sortOrder="8" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="symbols" translate="label comment" type="text" sortOrder="8" showInDefault="1" canRestore="1"> <label>Symbols Used in CAPTCHA</label> <comment> <![CDATA[Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.<br />Similar looking characters (e.g. "i", "l", "1") decrease chance of correct recognition by customer.]]> @@ -69,7 +69,7 @@ </depends> <frontend_class>required-entry validate-alphanum</frontend_class> </field> - <field id="case_sensitive" translate="label" type="select" sortOrder="9" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="case_sensitive" translate="label" type="select" sortOrder="9" showInDefault="1" canRestore="1"> <label>Case Sensitive</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> @@ -79,20 +79,20 @@ </group> </section> <section id="customer"> - <group id="captcha" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="captcha" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1"> <label>CAPTCHA</label> - <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable CAPTCHA on Storefront</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="font" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="font" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Font</label> <source_model>Magento\Captcha\Model\Config\Font</source_model> <depends> <field id="enable">1</field> </depends> </field> - <field id="forms" translate="label comment" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="forms" translate="label comment" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Forms</label> <source_model>Magento\Captcha\Model\Config\Form\Frontend</source_model> <comment>CAPTCHA for "Create user" and "Forgot password" forms is always enabled if chosen.</comment> @@ -100,14 +100,14 @@ <field id="enable">1</field> </depends> </field> - <field id="mode" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="mode" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Displaying Mode</label> <source_model>Magento\Captcha\Model\Config\Mode</source_model> <depends> <field id="enable">1</field> </depends> </field> - <field id="failed_attempts_login" translate="label comment" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="failed_attempts_login" translate="label comment" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Number of Unsuccessful Attempts to Login</label> <comment>If 0 is specified, CAPTCHA on the Login form will be always available.</comment> <depends> @@ -116,14 +116,14 @@ </depends> <frontend_class>required-entry validate-digits</frontend_class> </field> - <field id="timeout" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="timeout" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" canRestore="1"> <label>CAPTCHA Timeout (minutes)</label> <depends> <field id="enable">1</field> </depends> <frontend_class>required-entry validate-digits</frontend_class> </field> - <field id="length" translate="label comment" type="text" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="length" translate="label comment" type="text" sortOrder="7" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Number of Symbols</label> <comment>Please specify 8 symbols at the most. Range allowed (e.g. 3-5)</comment> <depends> @@ -131,7 +131,7 @@ </depends> <frontend_class>required-entry validate-range range-1-8</frontend_class> </field> - <field id="symbols" translate="label comment" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="symbols" translate="label comment" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Symbols Used in CAPTCHA</label> <comment> <![CDATA[Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.<br />Similar looking characters (e.g. "i", "l", "1") decrease chance of correct recognition by customer.]]> @@ -141,7 +141,7 @@ </depends> <frontend_class>required-entry validate-alphanum</frontend_class> </field> - <field id="case_sensitive" translate="label" type="select" sortOrder="9" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="case_sensitive" translate="label" type="select" sortOrder="9" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Case Sensitive</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> diff --git a/app/code/Magento/CardinalCommerce/Test/Mftf/Test/AdminCardinalCommerceSettingsHiddenTest.xml b/app/code/Magento/CardinalCommerce/Test/Mftf/Test/AdminCardinalCommerceSettingsHiddenTest.xml index a41b96f0db6e4..c891a578cdcca 100644 --- a/app/code/Magento/CardinalCommerce/Test/Mftf/Test/AdminCardinalCommerceSettingsHiddenTest.xml +++ b/app/code/Magento/CardinalCommerce/Test/Mftf/Test/AdminCardinalCommerceSettingsHiddenTest.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCardinalCommerceSettingsHiddenTest"> <annotations> + <stories value="Cardinal Commerce Settings"/> <features value="CardinalCommerce"/> <title value="CardinalCommerce settings hidden" /> <description value="CardinalCommerce config shouldn't be visible if the 3D secure is disabled for Authorize.Net."/> diff --git a/app/code/Magento/CardinalCommerce/etc/adminhtml/system.xml b/app/code/Magento/CardinalCommerce/etc/adminhtml/system.xml index 046475baba676..6312696ba97e0 100644 --- a/app/code/Magento/CardinalCommerce/etc/adminhtml/system.xml +++ b/app/code/Magento/CardinalCommerce/etc/adminhtml/system.xml @@ -11,11 +11,11 @@ <label>3D Secure</label> <tab>sales</tab> <resource>Magento_Sales::three_d_secure</resource> - <group id="cardinal" type="text" sortOrder="13" showInDefault="1" showInWebsite="1" showInStore="0"> - <group id="config" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="cardinal" type="text" sortOrder="13" showInDefault="1" showInWebsite="1"> + <group id="config" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1"> <label>CardinalCommerce</label> <comment><![CDATA[Please visit <a href="https://www.cardinalcommerce.com/" target="_blank">www.cardinalcommerce.com</a> to get the CardinalCommerce credentials and find out more details about PSD2 SCA requirements. For support contact <a href="mailto:support@cardinalcommerce.com">support@cardinalcommerce.com</a>.]]></comment> - <field id="environment" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="environment" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> <label>Environment</label> <source_model>Magento\CardinalCommerce\Model\Adminhtml\Source\Environment</source_model> <config_path>three_d_secure/cardinal/environment</config_path> @@ -23,7 +23,7 @@ <field id="enabled_authorize">1</field> </depends> </field> - <field id="org_unit_id" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="org_unit_id" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Org Unit Id</label> <config_path>three_d_secure/cardinal/org_unit_id</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> @@ -31,7 +31,7 @@ <field id="enabled_authorize">1</field> </depends> </field> - <field id="api_key" translate="label" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="api_key" translate="label" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1"> <label>API Key</label> <config_path>three_d_secure/cardinal/api_key</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> @@ -39,7 +39,7 @@ <field id="enabled_authorize">1</field> </depends> </field> - <field id="api_identifier" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="api_identifier" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>API Identifier</label> <config_path>three_d_secure/cardinal/api_identifier</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> @@ -47,7 +47,7 @@ <field id="enabled_authorize">1</field> </depends> </field> - <field id="debug" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>three_d_secure/cardinal/debug</config_path> diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php index f5d0ec7da617e..8e6011c09a27f 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php @@ -95,7 +95,7 @@ protected function _prepareLayout() ); $this->getUploader()->getConfig()->setUrl( - $this->_urlBuilder->addSessionParam()->getUrl('catalog/product_gallery/upload') + $this->_urlBuilder->getUrl('catalog/product_gallery/upload') )->setFileField( 'image' )->setFilters( diff --git a/app/code/Magento/Catalog/Block/Rss/Category.php b/app/code/Magento/Catalog/Block/Rss/Category.php index 50967d2eb8dca..f149114f2eab8 100644 --- a/app/code/Magento/Catalog/Block/Rss/Category.php +++ b/app/code/Magento/Catalog/Block/Rss/Category.php @@ -10,9 +10,7 @@ use Magento\Framework\Exception\NoSuchEntityException; /** - * Class Category - * - * @package Magento\Catalog\Block\Rss + * Category feed block * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ diff --git a/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php b/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php index 20c4bef0845d6..9ade8b198656c 100644 --- a/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php +++ b/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php @@ -8,8 +8,7 @@ use Magento\Framework\App\Rss\DataProviderInterface; /** - * Class NewProducts - * @package Magento\Catalog\Block\Rss\Product + * New products feed block */ class NewProducts extends \Magento\Framework\View\Element\AbstractBlock implements DataProviderInterface { @@ -55,6 +54,8 @@ public function __construct( } /** + * Init + * * @return void */ protected function _construct() @@ -64,7 +65,7 @@ protected function _construct() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAllowed() { @@ -72,7 +73,7 @@ public function isAllowed() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRssData() { @@ -97,10 +98,13 @@ public function getRssData() $item->setAllowedInRss(true); $item->setAllowedPriceInRss(true); - $this->_eventManager->dispatch('rss_catalog_new_xml_callback', [ - 'row' => $item->getData(), - 'product' => $item - ]); + $this->_eventManager->dispatch( + 'rss_catalog_new_xml_callback', + [ + 'row' => $item->getData(), + 'product' => $item + ] + ); if (!$item->getAllowedInRss()) { continue; @@ -132,6 +136,8 @@ public function getRssData() } /** + * Get store id + * * @return int */ protected function getStoreId() @@ -177,7 +183,7 @@ protected function renderPriceHtml(\Magento\Catalog\Model\Product $product) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCacheLifetime() { @@ -185,6 +191,8 @@ public function getCacheLifetime() } /** + * Get feeds + * * @return array */ public function getFeeds() @@ -199,7 +207,7 @@ public function getFeeds() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAuthRequired() { diff --git a/app/code/Magento/Catalog/Block/Rss/Product/Special.php b/app/code/Magento/Catalog/Block/Rss/Product/Special.php index a9107f14cc5e4..5e459413bb5a2 100644 --- a/app/code/Magento/Catalog/Block/Rss/Product/Special.php +++ b/app/code/Magento/Catalog/Block/Rss/Product/Special.php @@ -9,8 +9,7 @@ use Magento\Framework\App\Rss\DataProviderInterface; /** - * Class Special - * @package Magento\Catalog\Block\Rss\Product + * Special products feed block * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Special extends \Magento\Framework\View\Element\AbstractBlock implements DataProviderInterface @@ -98,6 +97,8 @@ public function __construct( } /** + * Init + * * @return void */ protected function _construct() @@ -107,6 +108,8 @@ protected function _construct() } /** + * Get RSS data + * * @return array */ public function getRssData() @@ -156,6 +159,8 @@ public function getRssData() } /** + * Get entry data + * * @param \Magento\Catalog\Model\Product $item * @return array */ @@ -245,7 +250,7 @@ public function isAllowed() } /** - * {@inheritdoc} + * @inheritdoc */ public function getCacheLifetime() { @@ -253,6 +258,8 @@ public function getCacheLifetime() } /** + * Get feeds + * * @return array */ public function getFeeds() @@ -266,7 +273,7 @@ public function getFeeds() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAuthRequired() { diff --git a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php index dd2e23e67f3d7..6d96ba8e1880e 100644 --- a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php +++ b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php @@ -6,15 +6,17 @@ namespace Magento\Catalog\Block\Ui; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\ProductRenderFactory; +use Magento\Catalog\Model\ProductRepository; use Magento\Catalog\Ui\DataProvider\Product\ProductRenderCollectorComposite; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\Hydrator; use Magento\Framework\Registry; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Url; use Magento\Framework\View\Element\Template; use Magento\Store\Model\Store; -use Magento\Catalog\Model\ProductRenderFactory; -use Magento\Catalog\Model\ProductRepository; -use Magento\Framework\EntityManager\Hydrator; use Magento\Store\Model\StoreManager; /** @@ -25,6 +27,7 @@ * * @api * @since 101.1.0 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ProductViewCounter extends Template { @@ -68,6 +71,13 @@ class ProductViewCounter extends Template */ private $registry; + /** + * Core store config + * + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param Template\Context $context * @param ProductRepository $productRepository @@ -78,6 +88,8 @@ class ProductViewCounter extends Template * @param SerializerInterface $serialize * @param Url $url * @param Registry $registry + * @param ScopeConfigInterface|null $scopeConfig + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Template\Context $context, @@ -88,7 +100,8 @@ public function __construct( Hydrator $hydrator, SerializerInterface $serialize, Url $url, - Registry $registry + Registry $registry, + ?ScopeConfigInterface $scopeConfig = null ) { parent::__construct($context); $this->productRepository = $productRepository; @@ -99,6 +112,7 @@ public function __construct( $this->serialize = $serialize; $this->url = $url; $this->registry = $registry; + $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -116,6 +130,10 @@ public function getCurrentProductData() { /** @var ProductInterface $product */ $product = $this->registry->registry('product'); + $productsScope = $this->scopeConfig->getValue( + 'catalog/recently_products/scope', + \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE + ); /** @var Store $store */ $store = $this->storeManager->getStore(); @@ -123,7 +141,8 @@ public function getCurrentProductData() return $this->serialize->serialize([ 'items' => [], 'store' => $store->getId(), - 'currency' => $store->getCurrentCurrency()->getCode() + 'currency' => $store->getCurrentCurrency()->getCode(), + 'productCurrentScope' => $productsScope ]); } @@ -140,7 +159,8 @@ public function getCurrentProductData() $product->getId() => $data ], 'store' => $store->getId(), - 'currency' => $store->getCurrentCurrency()->getCode() + 'currency' => $store->getCurrentCurrency()->getCode(), + 'productCurrentScope' => $productsScope ]; return $this->serialize->serialize($currentProductData); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php index d43b313c43b3e..3e7cc3ee962b9 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php @@ -11,7 +11,7 @@ use Magento\Framework\Exception\LocalizedException; /** - * Class Upload + * Upload product image action controller */ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface { diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index eea448e0fdb0b..552af244f0097 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -9,6 +9,7 @@ use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Catalog\Helper\Category as CategoryHelper; use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Category\Attribute\LayoutUpdateManager; use Magento\Catalog\Model\Design; use Magento\Catalog\Model\Layer\Resolver; use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer; @@ -30,7 +31,6 @@ use Magento\Framework\View\Result\PageFactory; use Magento\Store\Model\StoreManagerInterface; use Psr\Log\LoggerInterface; -use Magento\Catalog\Model\Category\Attribute\LayoutUpdateManager; /** * View a category on storefront. Needs to be accessible by POST because of the store switching. @@ -208,8 +208,10 @@ protected function _initCategory() * @return ResultInterface * @throws NoSuchEntityException */ - public function execute() + public function execute(): ?ResultInterface { + $result = null; + if ($this->_request->getParam(ActionInterface::PARAM_NAME_URL_ENCODED)) { return $this->resultRedirectFactory->create()->setUrl($this->_redirect->getRedirectUrl()); } @@ -239,6 +241,7 @@ public function execute() $page->addPageLayoutHandles(['type' => $parentPageType], null, false); } $page->addPageLayoutHandles(['type' => $pageType], null, false); + $page->addPageLayoutHandles(['displaymode' => strtolower($category->getDisplayMode())], null, false); $page->addPageLayoutHandles(['id' => $category->getId()]); // apply custom layout update once layout is loaded @@ -250,8 +253,9 @@ public function execute() return $page; } elseif (!$this->getResponse()->isRedirect()) { - return $this->resultForwardFactory->create()->forward('noroute'); + $result = $this->resultForwardFactory->create()->forward('noroute'); } + return $result; } /** diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Index.php b/app/code/Magento/Catalog/Controller/Product/Compare/Index.php index c0aa32a56ed17..3a1b4e7702f70 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Index.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Index.php @@ -12,6 +12,8 @@ use Magento\Framework\View\Result\PageFactory; /** + * View products compare in frontend + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Index extends \Magento\Catalog\Controller\Product\Compare implements HttpGetActionInterface @@ -74,23 +76,12 @@ public function __construct( */ public function execute() { - $items = $this->getRequest()->getParam('items'); - $beforeUrl = $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED); if ($beforeUrl) { $this->_catalogSession->setBeforeCompareUrl( $this->urlDecoder->decode($beforeUrl) ); } - - if ($items) { - $items = explode(',', $items); - /** @var \Magento\Catalog\Model\Product\Compare\ListCompare $list */ - $list = $this->_catalogProductCompareList; - $list->addProducts($items); - $resultRedirect = $this->resultRedirectFactory->create(); - return $resultRedirect->setPath('*/*/*'); - } return $this->resultPageFactory->create(); } } diff --git a/app/code/Magento/Catalog/CustomerData/CompareProducts.php b/app/code/Magento/Catalog/CustomerData/CompareProducts.php index afbeab8c9070e..bdac4dfde64d1 100644 --- a/app/code/Magento/Catalog/CustomerData/CompareProducts.php +++ b/app/code/Magento/Catalog/CustomerData/CompareProducts.php @@ -6,7 +6,13 @@ namespace Magento\Catalog\CustomerData; use Magento\Customer\CustomerData\SectionSourceInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; +/** + * Catalog Product Compare Widget + */ class CompareProducts implements SectionSourceInterface { /** @@ -24,23 +30,33 @@ class CompareProducts implements SectionSourceInterface */ private $outputHelper; + /** + * Core store config + * + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param \Magento\Catalog\Helper\Product\Compare $helper * @param \Magento\Catalog\Model\Product\Url $productUrl * @param \Magento\Catalog\Helper\Output $outputHelper + * @param ScopeConfigInterface|null $scopeConfig */ public function __construct( \Magento\Catalog\Helper\Product\Compare $helper, \Magento\Catalog\Model\Product\Url $productUrl, - \Magento\Catalog\Helper\Output $outputHelper + \Magento\Catalog\Helper\Output $outputHelper, + ?ScopeConfigInterface $scopeConfig = null ) { $this->helper = $helper; $this->productUrl = $productUrl; $this->outputHelper = $outputHelper; + $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function getSectionData() { @@ -54,11 +70,18 @@ public function getSectionData() } /** + * Get the list of compared product items + * * @return array + * @throws LocalizedException */ protected function getItems() { $items = []; + $productsScope = $this->scopeConfig->getValue( + 'catalog/recently_products/scope', + \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE + ); /** @var \Magento\Catalog\Model\Product $item */ foreach ($this->helper->getItemCollection() as $item) { $items[] = [ @@ -66,6 +89,7 @@ protected function getItems() 'product_url' => $this->productUrl->getUrl($item), 'name' => $this->outputHelper->productAttribute($item, $item->getName(), 'name'), 'remove_url' => $this->helper->getPostDataRemove($item), + 'productScope' => $productsScope ]; } return $items; diff --git a/app/code/Magento/Catalog/Helper/Image.php b/app/code/Magento/Catalog/Helper/Image.php index 110b798df9df9..5b0aa0c496ecd 100644 --- a/app/code/Magento/Catalog/Helper/Image.php +++ b/app/code/Magento/Catalog/Helper/Image.php @@ -5,14 +5,19 @@ */ namespace Magento\Catalog\Helper; +use Magento\Catalog\Model\Config\CatalogMediaConfig; +use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\App\Helper\AbstractHelper; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Element\Block\ArgumentInterface; /** - * Catalog image helper + * Catalog image helper. * * @api * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ class Image extends AbstractHelper implements ArgumentInterface @@ -40,6 +45,7 @@ class Image extends AbstractHelper implements ArgumentInterface * Scheduled for rotate image * * @var bool + * @deprecated unused */ protected $_scheduleRotate = false; @@ -47,6 +53,7 @@ class Image extends AbstractHelper implements ArgumentInterface * Angle * * @var int + * @deprecated unused */ protected $_angle; @@ -129,31 +136,38 @@ class Image extends AbstractHelper implements ArgumentInterface protected $attributes = []; /** - * @var \Magento\Catalog\Model\View\Asset\PlaceholderFactory + * @var PlaceholderFactory */ private $viewAssetPlaceholderFactory; + /** + * @var CatalogMediaConfig + */ + private $mediaConfig; + /** * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Catalog\Model\Product\ImageFactory $productImageFactory * @param \Magento\Framework\View\Asset\Repository $assetRepo * @param \Magento\Framework\View\ConfigInterface $viewConfig - * @param \Magento\Catalog\Model\View\Asset\PlaceholderFactory $placeholderFactory + * @param PlaceholderFactory $placeholderFactory + * @param CatalogMediaConfig $mediaConfig */ public function __construct( \Magento\Framework\App\Helper\Context $context, \Magento\Catalog\Model\Product\ImageFactory $productImageFactory, \Magento\Framework\View\Asset\Repository $assetRepo, \Magento\Framework\View\ConfigInterface $viewConfig, - \Magento\Catalog\Model\View\Asset\PlaceholderFactory $placeholderFactory = null + PlaceholderFactory $placeholderFactory = null, + CatalogMediaConfig $mediaConfig = null ) { $this->_productImageFactory = $productImageFactory; parent::__construct($context); $this->_assetRepo = $assetRepo; $this->viewConfig = $viewConfig; $this->viewAssetPlaceholderFactory = $placeholderFactory - ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\View\Asset\PlaceholderFactory::class); + ?: ObjectManager::getInstance()->get(PlaceholderFactory::class); + $this->mediaConfig = $mediaConfig ?: ObjectManager::getInstance()->get(CatalogMediaConfig::class); } /** @@ -382,9 +396,10 @@ public function constrainOnly($flag) */ public function backgroundColor($colorRGB) { + $args = func_get_args(); // assume that 3 params were given instead of array if (!is_array($colorRGB)) { - $colorRGB = func_get_args(); + $colorRGB = $args; } $this->_getModel()->setBackgroundColor($colorRGB); return $this; @@ -395,6 +410,7 @@ public function backgroundColor($colorRGB) * * @param int $angle * @return $this + * @deprecated unused */ public function rotate($angle) { @@ -526,7 +542,16 @@ protected function isScheduledActionsAllowed() public function getUrl() { try { - $this->applyScheduledActions(); + switch ($this->mediaConfig->getMediaUrlFormat()) { + case CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS: + $this->initBaseFile(); + break; + case CatalogMediaConfig::HASH: + $this->applyScheduledActions(); + break; + default: + throw new LocalizedException(__("The specified Catalog media URL format is not supported.")); + } return $this->_getModel()->getUrl(); } catch (\Exception $e) { return $this->getDefaultPlaceholderUrl(); @@ -595,6 +620,7 @@ protected function _getModel() * * @param int $angle * @return $this + * @deprecated unused */ protected function setAngle($angle) { @@ -606,6 +632,7 @@ protected function setAngle($angle) * Get Rotation Angle * * @return int + * @deprecated unused */ protected function getAngle() { diff --git a/app/code/Magento/Catalog/Helper/Product/Compare.php b/app/code/Magento/Catalog/Helper/Product/Compare.php index d6d35c5c76dd8..49a90c590a440 100644 --- a/app/code/Magento/Catalog/Helper/Product/Compare.php +++ b/app/code/Magento/Catalog/Helper/Product/Compare.php @@ -14,6 +14,7 @@ * @api * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Compare extends \Magento\Framework\Url\Helper\Data @@ -145,16 +146,9 @@ public function __construct( */ public function getListUrl() { - $itemIds = []; - foreach ($this->getItemCollection() as $item) { - $itemIds[] = $item->getId(); - } - $params = [ - 'items' => implode(',', $itemIds), \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => $this->getEncodedUrl() ]; - return $this->_getUrl('catalog/product_compare', $params); } diff --git a/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php b/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php new file mode 100644 index 0000000000000..9e5394f0d6585 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Config; + +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Config for catalog media + */ +class CatalogMediaConfig +{ + private const XML_PATH_CATALOG_MEDIA_URL_FORMAT = 'web/url/catalog_media_url_format'; + + const IMAGE_OPTIMIZATION_PARAMETERS = 'image_optimization_parameters'; + const HASH = 'hash'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * Constructor + * + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * Get media URL format for catalog images + * + * @param string $scopeType + * @param null|int|string $scopeCode + * @return string + */ + public function getMediaUrlFormat($scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) + { + return $this->scopeConfig->getValue( + CatalogMediaConfig::XML_PATH_CATALOG_MEDIA_URL_FORMAT, + $scopeType, + $scopeCode + ); + } +} diff --git a/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php b/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php new file mode 100644 index 0000000000000..f24044fc92c95 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Config\Source\Web; + +use Magento\Catalog\Model\Config\CatalogMediaConfig; + +/** + * Option provider for catalog media URL format system setting. + */ +class CatalogMediaUrlFormat implements \Magento\Framework\Data\OptionSourceInterface +{ + /** + * Get a list of supported catalog media URL formats. + * + * @codeCoverageIgnore + */ + public function toOptionArray() + { + return [ + [ + 'value' => CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS, + 'label' => __('Image optimization based on query parameters') + ], + ['value' => CatalogMediaConfig::HASH, 'label' => __('Unique hash per image variant (Legacy mode)')] + ]; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index a0be36c5a327c..7c2a53768fd47 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -10,9 +10,11 @@ use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Image as MagentoImage; use Magento\Framework\Serialize\SerializerInterface; use Magento\Catalog\Model\Product\Image\ParamsBuilder; +use Magento\Framework\Filesystem\Driver\File as FilesystemDriver; /** * Image operations @@ -101,6 +103,7 @@ class Image extends \Magento\Framework\Model\AbstractModel /** * @var int + * @deprecated unused */ protected $_angle; @@ -199,6 +202,11 @@ class Image extends \Magento\Framework\Model\AbstractModel */ private $serializer; + /** + * @var FilesystemDriver + */ + private $filesystemDriver; + /** * Constructor * @@ -219,6 +227,8 @@ class Image extends \Magento\Framework\Model\AbstractModel * @param array $data * @param SerializerInterface $serializer * @param ParamsBuilder $paramsBuilder + * @param FilesystemDriver $filesystemDriver + * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ @@ -239,7 +249,8 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], SerializerInterface $serializer = null, - ParamsBuilder $paramsBuilder = null + ParamsBuilder $paramsBuilder = null, + FilesystemDriver $filesystemDriver = null ) { $this->_storeManager = $storeManager; $this->_catalogProductMediaConfig = $catalogProductMediaConfig; @@ -254,6 +265,7 @@ public function __construct( $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); $this->paramsBuilder = $paramsBuilder ?: ObjectManager::getInstance()->get(ParamsBuilder::class); + $this->filesystemDriver = $filesystemDriver ?: ObjectManager::getInstance()->get(FilesystemDriver::class); } /** @@ -524,6 +536,7 @@ public function resize() * * @param int $angle * @return $this + * @deprecated unused */ public function rotate($angle) { @@ -539,6 +552,7 @@ public function rotate($angle) * * @param int $angle * @return $this + * @deprecated unused */ public function setAngle($angle) { @@ -663,7 +677,12 @@ public function getDestinationSubdir() public function isCached() { $path = $this->imageAsset->getPath(); - return is_array($this->loadImageInfoFromCache($path)) || file_exists($path); + try { + $isCached = is_array($this->loadImageInfoFromCache($path)) || $this->filesystemDriver->isExists($path); + } catch (FileSystemException $e) { + $isCached = false; + } + return $isCached; } /** diff --git a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php index c0f4e83ef3de4..ecdb3b2829b90 100644 --- a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php +++ b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php @@ -132,9 +132,10 @@ private function getWatermark(string $type, int $scopeId = null): array if ($file) { $size = explode( 'x', - $this->scopeConfig->getValue( + (string) $this->scopeConfig->getValue( "design/watermark/{$type}_size", - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $scopeId ) ); $opacity = $this->scopeConfig->getValue( diff --git a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php index 24775a791e59f..ec2e697ccc849 100644 --- a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php +++ b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php @@ -125,9 +125,16 @@ private function filterNewestActions(array $productsData, $typeId) $lifetime = $this->getLifeTimeByNamespace($typeId); $actionsNumber = $lifetime * self::TIME_TO_DO_ONE_ACTION; - uasort($productsData, function (array $firstProduct, array $secondProduct) { - return $firstProduct['added_at'] > $secondProduct['added_at']; - }); + uasort( + $productsData, + function (array $firstProduct, array $secondProduct) { + if (isset($firstProduct['added_at'], $secondProduct['added_at'])) { + return $firstProduct['added_at'] > $secondProduct['added_at']; + } + + return false; + } + ); return array_slice($productsData, 0, $actionsNumber, true); } @@ -185,15 +192,17 @@ public function syncActions(array $productsData, $typeId) foreach ($productsData as $productId => $productData) { /** @var ProductFrontendActionInterface $action */ - $action = $this->productFrontendActionFactory->create([ - 'data' => [ - 'visitor_id' => $customerId ? null : $visitorId, - 'customer_id' => $this->session->getCustomerId(), - 'added_at' => $productData['added_at'], - 'product_id' => $productId, - 'type_id' => $typeId + $action = $this->productFrontendActionFactory->create( + [ + 'data' => [ + 'visitor_id' => $customerId ? null : $visitorId, + 'customer_id' => $this->session->getCustomerId(), + 'added_at' => $productData['added_at'], + 'product_id' => $productId, + 'type_id' => $typeId + ] ] - ]); + ); $this->entityManager->save($action); } diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php index 2760b0f9fddb6..8867cc7a690c1 100644 --- a/app/code/Magento/Catalog/Model/Product/Url.php +++ b/app/code/Magento/Catalog/Model/Product/Url.php @@ -101,15 +101,10 @@ public function getUrlInStore(\Magento\Catalog\Model\Product $product, $params = */ public function getProductUrl($product, $useSid = null) { - if ($useSid === null) { - $useSid = $this->sidResolver->getUseSessionInUrl(); - } - $params = []; if (!$useSid) { $params['_nosid'] = true; } - return $this->getUrl($product, $params); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index f3984b4a35f1a..e1c90017327cd 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -395,7 +395,7 @@ public function getApplyTo() /** * Retrieve source model * - * @return \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource + * @return \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource|string|null */ public function getSourceModel() { diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php index ca20f57c5d00e..d0a3af92126d3 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Action.php @@ -5,6 +5,7 @@ */ namespace Magento\Catalog\Model\ResourceModel\Product; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; /** @@ -14,6 +15,32 @@ */ class Action extends \Magento\Catalog\Model\ResourceModel\AbstractResource { + /** + * @var \Magento\Framework\Stdlib\DateTime\DateTime + */ + private $dateTime; + + /** + * @param \Magento\Eav\Model\Entity\Context $context + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Catalog\Model\Factory $modelFactory + * @param \Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface $uniqueValidator + * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param array $data + */ + public function __construct( + \Magento\Eav\Model\Entity\Context $context, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Catalog\Model\Factory $modelFactory, + \Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface $uniqueValidator, + \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, + $data = [] + ) { + parent::__construct($context, $storeManager, $modelFactory, $data, $uniqueValidator); + + $this->dateTime = $dateTime; + } + /** * Initialize connection * @@ -43,6 +70,7 @@ public function updateAttributes($entityIds, $attrData, $storeId) $object = new \Magento\Framework\DataObject(); $object->setStoreId($storeId); + $attrData[ProductInterface::UPDATED_AT] = $this->dateTime->gmtDate(); $this->getConnection()->beginTransaction(); try { foreach ($attrData as $attrCode => $value) { @@ -95,7 +123,7 @@ protected function _saveAttributeValue($object, $attribute, $value) * for default store id * In this case we clear all not default values */ - if ($this->_storeManager->hasSingleStore()) { + if ($this->_storeManager->hasSingleStore() && !$attribute->isStatic()) { $storeId = $this->getDefaultStoreId(); $connection->delete( $table, @@ -107,17 +135,24 @@ protected function _saveAttributeValue($object, $attribute, $value) ); } - $data = new \Magento\Framework\DataObject( - [ - 'attribute_id' => $attribute->getAttributeId(), - 'store_id' => $storeId, - $this->getLinkField() => $entityId, - 'value' => $this->_prepareValueForSave($value, $attribute), - ] - ); + $data = $attribute->isStatic() + ? new \Magento\Framework\DataObject( + [ + $this->getLinkField() => $entityId, + $attribute->getAttributeCode() => $this->_prepareValueForSave($value, $attribute), + ] + ) + : new \Magento\Framework\DataObject( + [ + 'attribute_id' => $attribute->getAttributeId(), + 'store_id' => $storeId, + $this->getLinkField() => $entityId, + 'value' => $this->_prepareValueForSave($value, $attribute), + ] + ); $bind = $this->_prepareDataForTable($data, $table); - if ($attribute->isScopeStore()) { + if ($attribute->isScopeStore() || $attribute->isStatic()) { /** * Update attribute value for store */ @@ -143,6 +178,8 @@ protected function _saveAttributeValue($object, $attribute, $value) } /** + * Resolve entity id + * * @param int $entityId * @return int */ diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php index ebe04fb63b217..f477c11896688 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php @@ -15,6 +15,13 @@ use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Framework\Search\Request\IndexScopeResolverInterface; +/** + * Class LinkedProductSelectBuilderByIndexPrice + * + * Provide Select object for retrieve product id by index price. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuilderInterface { /** @@ -83,13 +90,13 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ - public function build($productId) + public function build(int $productId, int $storeId) : array { $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); - $websiteId = $this->storeManager->getStore()->getWebsiteId(); + $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); $customerGroupId = $this->customerSession->getCustomerGroupId(); $priceSelect = $this->resource->getConnection()->select() diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php index 841fe17bdcf05..77e886f12723c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php @@ -74,7 +74,7 @@ public function __construct( /** * @inheritdoc */ - public function build($productId) + public function build(int $productId, int $storeId) : array { $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $priceAttribute = $this->eavConfig->getAttribute(Product::ENTITY, 'price'); @@ -104,7 +104,7 @@ public function build($productId) if (!$this->catalogHelper->isPriceGlobal()) { $priceSelectStore = clone $priceSelect; - $priceSelectStore->where('t.store_id = ?', $this->storeManager->getStore()->getId()); + $priceSelectStore->where('t.store_id = ?', $storeId); $selects[] = $priceSelectStore; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php index 5c47185a85bf4..ef0eeca10502a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php @@ -12,6 +12,10 @@ use Magento\Store\Model\Store; /** + * LinkedProductSelectBuilderBySpecialPrice + * + * Provide Select object for retrieve product id by special price + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBuilderInterface @@ -88,16 +92,16 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ - public function build($productId) + public function build(int $productId, int $storeId) : array { $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $connection = $this->resource->getConnection(); $specialPriceAttribute = $this->eavConfig->getAttribute(Product::ENTITY, 'special_price'); $specialPriceFromDate = $this->eavConfig->getAttribute(Product::ENTITY, 'special_from_date'); $specialPriceToDate = $this->eavConfig->getAttribute(Product::ENTITY, 'special_to_date'); - $timestamp = $this->localeDate->scopeTimeStamp($this->storeManager->getStore()); + $timestamp = $this->localeDate->scopeTimeStamp($this->storeManager->getStore($storeId)); $currentDate = $this->dateTime->formatDate($timestamp, false); $productTable = $this->resource->getTableName('catalog_product_entity'); @@ -145,7 +149,7 @@ public function build($productId) if (!$this->catalogHelper->isPriceGlobal()) { $priceSelectStore = clone $specialPrice; - $priceSelectStore->where('t.store_id = ?', $this->storeManager->getStore()->getId()); + $priceSelectStore->where('t.store_id = ?', $storeId); $selects[] = $priceSelectStore; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php index 37281193d6a1b..f104f92ea86c5 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php @@ -9,10 +9,19 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; +/** + * LinkedProductSelectBuilderByTierPrice + * + * Provide Select object for retrieve product id by tier price + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilderInterface { /** * Default website id + * + * Constant represents default website id */ const DEFAULT_WEBSITE_ID = 0; @@ -72,9 +81,9 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ - public function build($productId) + public function build(int $productId, int $storeId) : array { $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); @@ -103,7 +112,7 @@ public function build($productId) if (!$this->catalogHelper->isPriceGlobal()) { $priceSelectStore = clone $priceSelect; - $priceSelectStore->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()); + $priceSelectStore->where('t.website_id = ?', $this->storeManager->getStore($storeId)->getWebsiteId()); $selects[] = $priceSelectStore; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php index 5782834c06d85..17ca389777c5b 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderComposite.php @@ -6,6 +6,9 @@ namespace Magento\Catalog\Model\ResourceModel\Product; +/** + * Collect Select object for list of products + */ class LinkedProductSelectBuilderComposite implements LinkedProductSelectBuilderInterface { /** @@ -22,14 +25,15 @@ public function __construct($linkedProductSelectBuilder) } /** - * {@inheritdoc} + * @inheritdoc */ - public function build($productId) + public function build(int $productId, int $storeId) : array { $selects = []; foreach ($this->linkedProductSelectBuilder as $productSelectBuilder) { - $selects = array_merge($selects, $productSelectBuilder->build($productId)); + $selects[] = $productSelectBuilder->build($productId, $storeId); } + $selects = array_merge(...$selects); return $selects; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php index 3b870ed61f36d..3411c124bf5ce 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderInterface.php @@ -11,8 +11,11 @@ interface LinkedProductSelectBuilderInterface { /** + * Build Select objects + * * @param int $productId + * @param int $storeId * @return \Magento\Framework\DB\Select[] */ - public function build($productId); + public function build(int $productId, int $storeId) : array; } diff --git a/app/code/Magento/Catalog/Model/View/Asset/Image.php b/app/code/Magento/Catalog/Model/View/Asset/Image.php index c547ec612bb94..da1009ab1125c 100644 --- a/app/code/Magento/Catalog/Model/View/Asset/Image.php +++ b/app/code/Magento/Catalog/Model/View/Asset/Image.php @@ -6,11 +6,16 @@ namespace Magento\Catalog\Model\View\Asset; +use Magento\Catalog\Model\Config\CatalogMediaConfig; use Magento\Catalog\Model\Product\Media\ConfigInterface; use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Asset\ContextInterface; use Magento\Framework\View\Asset\LocalInterface; +use Magento\Catalog\Helper\Image as ImageHelper; +use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\StoreManagerInterface; /** * A locally available image file asset that can be referred with a file path @@ -58,6 +63,21 @@ class Image implements LocalInterface */ private $encryptor; + /** + * @var ImageHelper + */ + private $imageHelper; + + /** + * @var CatalogMediaConfig + */ + private $catalogMediaConfig; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * Image constructor. * @@ -66,13 +86,19 @@ class Image implements LocalInterface * @param EncryptorInterface $encryptor * @param string $filePath * @param array $miscParams + * @param ImageHelper $imageHelper + * @param CatalogMediaConfig $catalogMediaConfig + * @param StoreManagerInterface $storeManager */ public function __construct( ConfigInterface $mediaConfig, ContextInterface $context, EncryptorInterface $encryptor, $filePath, - array $miscParams + array $miscParams, + ImageHelper $imageHelper = null, + CatalogMediaConfig $catalogMediaConfig = null, + StoreManagerInterface $storeManager = null ) { if (isset($miscParams['image_type'])) { $this->sourceContentType = $miscParams['image_type']; @@ -85,14 +111,72 @@ public function __construct( $this->filePath = $filePath; $this->miscParams = $miscParams; $this->encryptor = $encryptor; + $this->imageHelper = $imageHelper ?: ObjectManager::getInstance()->get(ImageHelper::class); + $this->catalogMediaConfig = $catalogMediaConfig ?: ObjectManager::getInstance()->get(CatalogMediaConfig::class); + $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** - * @inheritdoc + * Get catalog image URL. + * + * @return string + * @throws LocalizedException */ public function getUrl() { - return $this->context->getBaseUrl() . DIRECTORY_SEPARATOR . $this->getImageInfo(); + $mediaUrlFormat = $this->catalogMediaConfig->getMediaUrlFormat(); + switch ($mediaUrlFormat) { + case CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS: + return $this->getUrlWithTransformationParameters(); + case CatalogMediaConfig::HASH: + return $this->context->getBaseUrl() . DIRECTORY_SEPARATOR . $this->getImageInfo(); + default: + throw new LocalizedException( + __("The specified Catalog media URL format '$mediaUrlFormat' is not supported.") + ); + } + } + + /** + * Get image URL with transformation parameters + * + * @return string + */ + private function getUrlWithTransformationParameters() + { + return $this->getOriginalImageUrl() . '?' . http_build_query($this->getImageTransformationParameters()); + } + + /** + * The list of parameters to be used during image transformations (e.g. resizing or applying watermarks). + * + * This method can be used as an extension point. + * + * @return string[] + */ + public function getImageTransformationParameters() + { + return [ + 'width' => $this->miscParams['image_width'], + 'height' => $this->miscParams['image_height'], + 'store' => $this->storeManager->getStore()->getCode(), + 'image-type' => $this->sourceContentType + ]; + } + + /** + * Get URL to the original version of the product image. + * + * @return string + */ + private function getOriginalImageUrl() + { + $originalImageFile = $this->getSourceFile(); + if (!$originalImageFile) { + return $this->imageHelper->getDefaultPlaceholderUrl(); + } else { + return $this->context->getBaseUrl() . $this->getFilePath(); + } } /** diff --git a/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php b/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php index 91d2868afab8c..54b655a217a08 100644 --- a/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php +++ b/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php @@ -10,7 +10,11 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Framework\App\State; use Magento\MediaStorage\Service\ImageResize; +use Magento\Catalog\Model\Config\CatalogMediaConfig; +/** + * Resize product images after the product is saved + */ class ImageResizeAfterProductSave implements ObserverInterface { /** @@ -23,17 +27,26 @@ class ImageResizeAfterProductSave implements ObserverInterface */ private $state; + /** + * @var CatalogMediaConfig + */ + private $catalogMediaConfig; + /** * Product constructor. + * * @param ImageResize $imageResize * @param State $state + * @param CatalogMediaConfig $catalogMediaConfig */ public function __construct( ImageResize $imageResize, - State $state + State $state, + CatalogMediaConfig $catalogMediaConfig ) { $this->imageResize = $imageResize; $this->state = $state; + $this->catalogMediaConfig = $catalogMediaConfig; } /** @@ -44,6 +57,12 @@ public function __construct( */ public function execute(\Magento\Framework\Event\Observer $observer) { + $catalogMediaUrlFormat = $this->catalogMediaConfig->getMediaUrlFormat(); + if ($catalogMediaUrlFormat == CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS) { + // Skip image resizing on the Magento side when it is offloaded to a web server or CDN + return; + } + /** @var $product \Magento\Catalog\Model\Product */ $product = $observer->getEvent()->getProduct(); diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/UpdateDefaultAttributeValue.php b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateDefaultAttributeValue.php index 1d58de1994a11..4500d86ad4a6c 100644 --- a/app/code/Magento/Catalog/Setup/Patch/Data/UpdateDefaultAttributeValue.php +++ b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateDefaultAttributeValue.php @@ -3,19 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Setup\Patch\Data; use Magento\Catalog\Setup\CategorySetup; use Magento\Catalog\Setup\CategorySetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class UpdateDefaultAttributeValue - * @package Magento\Catalog\Setup\Patch + * Updates 'is_anchor' attribute default value. */ class UpdateDefaultAttributeValue implements DataPatchInterface, PatchVersionInterface { @@ -30,7 +29,6 @@ class UpdateDefaultAttributeValue implements DataPatchInterface, PatchVersionInt private $categorySetupFactory; /** - * PatchInitial constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CategorySetupFactory $categorySetupFactory */ @@ -43,17 +41,24 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { /** @var CategorySetup $categorySetup */ $categorySetup = $this->categorySetupFactory->create(['setup' => $this->moduleDataSetup]); - $categorySetup->updateAttribute(3, 54, 'default_value', 1); + $categorySetup->updateAttribute( + CategorySetup::CATEGORY_ENTITY_TYPE_ID, + 'is_anchor', + 'default_value', + 1 + ); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -63,7 +68,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -71,7 +76,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml index e5cefda0aca96..12602615db8ef 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml @@ -25,14 +25,4 @@ <checkOption selector="{{AdminProductImagesSection.roleSwatch}}" stepKey="checkRoleSwatch"/> <click selector="{{AdminSlideOutDialogSection.closeButton}}" stepKey="clickCloseButton"/> </actionGroup> - <actionGroup name="AdminAssignImageRolesIfUnassignedActionGroup" extends="AdminAssignImageRolesActionGroup"> - <annotations> - <description>Requires the navigation to the Product Creation page. Assign the Base, Small, Thumbnail, and Swatch Roles to image.</description> - </annotations> - - <conditionalClick selector="{{AdminProductImagesSection.roleBase}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Base')}}" visible="false" stepKey="checkRoleBase"/> - <conditionalClick selector="{{AdminProductImagesSection.roleSmall}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Small')}}" visible="false" stepKey="checkRoleSmall"/> - <conditionalClick selector="{{AdminProductImagesSection.roleThumbnail}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Thumbnail')}}" visible="false" stepKey="checkRoleThumbnail"/> - <conditionalClick selector="{{AdminProductImagesSection.roleSwatch}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Swatch')}}" visible="false" stepKey="checkRoleSwatch"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesIfUnassignedActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesIfUnassignedActionGroup.xml new file mode 100644 index 0000000000000..ca82882b141cb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesIfUnassignedActionGroup.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="AdminAssignImageRolesIfUnassignedActionGroup" extends="AdminAssignImageRolesActionGroup"> + <annotations> + <description>Requires the navigation to the Product Creation page. Assign the Base, Small, Thumbnail, and Swatch Roles to image.</description> + </annotations> + + <conditionalClick selector="{{AdminProductImagesSection.roleBase}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Base')}}" visible="false" stepKey="checkRoleBase"/> + <conditionalClick selector="{{AdminProductImagesSection.roleSmall}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Small')}}" visible="false" stepKey="checkRoleSmall"/> + <conditionalClick selector="{{AdminProductImagesSection.roleThumbnail}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Thumbnail')}}" visible="false" stepKey="checkRoleThumbnail"/> + <conditionalClick selector="{{AdminProductImagesSection.roleSwatch}}" dependentSelector="{{AdminProductImagesSection.isRoleChecked('Swatch')}}" visible="false" stepKey="checkRoleSwatch"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyActionGroup.xml index 7107cc2a560d1..cf2e809fefa5e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyActionGroup.xml @@ -16,7 +16,7 @@ <argument name="value" type="string"/> </arguments> - <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSeoSection"/> + <conditionalClick selector="{{AdminCategorySEOSection.SectionHeader}}" dependentSelector="{{AdminCategorySEOSection.sectionBody}}" visible="false" stepKey="openSeoSection"/> <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{value}}" stepKey="enterURLKey"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccessMessage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryActionGroup.xml index 42813aef05be5..a65bb297971c7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryActionGroup.xml @@ -16,7 +16,7 @@ <argument name="value" type="string"/> </arguments> - <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSeoSection"/> + <conditionalClick selector="{{AdminCategorySEOSection.SectionHeader}}" dependentSelector="{{AdminCategorySEOSection.sectionBody}}" visible="false" stepKey="openSeoSection"/> <uncheckOption selector="{{AdminCategorySEOSection.UrlKeyDefaultValueCheckbox}}" stepKey="uncheckDefaultValue"/> <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{value}}" stepKey="enterURLKey"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml index aa44a517593af..22209b61d5316 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml @@ -27,5 +27,6 @@ <waitForPageLoad stepKey="waitForDeleteItemPopup" time="10"/> <click stepKey="clickOnOk" selector="{{ProductsPageSection.ok}}"/> <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{ProductsPageSection.deletedSuccessMessage}}" time="10"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectDropDownOptionValueActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectDropDownOptionValueActionGroup.xml new file mode 100644 index 0000000000000..31b18e1f0d37e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectDropDownOptionValueActionGroup.xml @@ -0,0 +1,22 @@ +<?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="StorefrontProductPageSelectDropDownOptionValueActionGroup"> + <annotations> + <description>Selects the provided Product Option Value under the provided DropDown Product Option Title on a Storefront Product page.</description> + </annotations> + <arguments> + <argument name="attributeLabel" type="string" defaultValue="{{ProductAttributeFrontendLabel.label}}"/> + <argument name="optionLabel" type="string" defaultValue="{{productAttributeOption1.label}}"/> + </arguments> + + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(attributeLabel)}}" userInput="{{optionLabel}}" stepKey="fillDropDownAttributeOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectRadioButtonOptionValueActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectRadioButtonOptionValueActionGroup.xml new file mode 100644 index 0000000000000..5f2a130956cbe --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageSelectRadioButtonOptionValueActionGroup.xml @@ -0,0 +1,22 @@ +<?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="StorefrontProductPageSelectRadioButtonOptionValueActionGroup"> + <annotations> + <description>Selects the provided Product Option Value under the provided Radio Button Product Option Title on a Storefront Product page.</description> + </annotations> + <arguments> + <argument name="attributeLabel" type="string" defaultValue="{{ProductAttributeFrontendLabel.label}}"/> + <argument name="optionLabel" type="string" defaultValue="{{productAttributeOption1.label}}"/> + </arguments> + + <checkOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsRadioButtonByName(attributeLabel, optionLabel)}}" stepKey="fillRadioButtonAttributeOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml index abf01f00dbbcc..be04c297cec25 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml @@ -1,63 +1,72 @@ -<?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="RememberPaginationCatalogStorefrontConfig" type="catalog_storefront_config"> - <requiredEntity type="grid_per_page_values">GridPerPageValues</requiredEntity> - <requiredEntity type="remember_pagination">RememberCategoryPagination</requiredEntity> - </entity> - - <entity name="GridPerPageValues" type="grid_per_page_values"> - <data key="value">9,12,20,24</data> - </entity> - - <entity name="RememberCategoryPagination" type="remember_pagination"> - <data key="value">1</data> - </entity> - - <entity name="DefaultCatalogStorefrontConfiguration" type="default_catalog_storefront_config"> - <requiredEntity type="catalogStorefrontFlagZero">DefaultCatalogStorefrontFlagZero</requiredEntity> - <data key="list_allow_all">DefaultListAllowAll</data> - <data key="flat_catalog_product">DefaultFlatCatalogProduct</data> - </entity> - - <entity name="DefaultCatalogStorefrontFlagZero" type="catalogStorefrontFlagZero"> - <data key="value">0</data> - </entity> - - <entity name="DefaultListAllowAll" type="list_allow_all"> - <data key="value">0</data> - </entity> - - <entity name="DefaultFlatCatalogProduct" type="flat_catalog_product"> - <data key="value">0</data> - </entity> - - <entity name="UseFlatCatalogCategoryAndProduct" type="catalog_storefront_config"> - <requiredEntity type="flat_catalog_product">UseFlatCatalogProduct</requiredEntity> - <requiredEntity type="flat_catalog_category">UseFlatCatalogCategory</requiredEntity> - </entity> - - <entity name="UseFlatCatalogProduct" type="flat_catalog_product"> - <data key="value">1</data> - </entity> - - <entity name="UseFlatCatalogCategory" type="flat_catalog_category"> - <data key="value">1</data> - </entity> - - <entity name="DefaultFlatCatalogCategoryAndProduct" type="catalog_storefront_config"> - <requiredEntity type="flat_catalog_product">DefaultFlatCatalogProduct</requiredEntity> - <requiredEntity type="flat_catalog_category">DefaultFlatCatalogCategory</requiredEntity> - </entity> - - <entity name="DefaultFlatCatalogCategory" type="flat_catalog_category"> - <data key="value">0</data> - </entity> -</entities> +<?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="RememberPaginationCatalogStorefrontConfig" type="catalog_storefront_config"> + <requiredEntity type="grid_per_page_values">GridPerPageValues</requiredEntity> + <requiredEntity type="remember_pagination">RememberCategoryPagination</requiredEntity> + </entity> + + <entity name="GridPerPageValues" type="grid_per_page_values"> + <data key="value">9,12,20,24</data> + </entity> + + <entity name="RememberCategoryPagination" type="remember_pagination"> + <data key="value">1</data> + </entity> + + <entity name="DefaultCatalogStorefrontConfiguration" type="default_catalog_storefront_config"> + <requiredEntity type="catalogStorefrontFlagZero">DefaultCatalogStorefrontFlagZero</requiredEntity> + <data key="list_allow_all">DefaultListAllowAll</data> + <data key="flat_catalog_product">DefaultFlatCatalogProduct</data> + </entity> + + <entity name="DefaultCatalogStorefrontFlagZero" type="catalogStorefrontFlagZero"> + <data key="value">0</data> + </entity> + + <entity name="DefaultListAllowAll" type="list_allow_all"> + <data key="value">0</data> + </entity> + + <entity name="DefaultFlatCatalogProduct" type="flat_catalog_product"> + <data key="value">0</data> + </entity> + + <entity name="UseFlatCatalogCategoryAndProduct" type="catalog_storefront_config"> + <requiredEntity type="flat_catalog_product">UseFlatCatalogProduct</requiredEntity> + <requiredEntity type="flat_catalog_category">UseFlatCatalogCategory</requiredEntity> + </entity> + + <entity name="UseFlatCatalogProduct" type="flat_catalog_product"> + <data key="value">1</data> + </entity> + + <entity name="UseFlatCatalogCategory" type="flat_catalog_category"> + <data key="value">1</data> + </entity> + + <entity name="DefaultFlatCatalogCategoryAndProduct" type="catalog_storefront_config"> + <requiredEntity type="flat_catalog_product">DefaultFlatCatalogProduct</requiredEntity> + <requiredEntity type="flat_catalog_category">DefaultFlatCatalogCategory</requiredEntity> + </entity> + + <entity name="DefaultFlatCatalogCategory" type="flat_catalog_category"> + <data key="value">0</data> + </entity> + + <entity name="DefaultGridPerPageValuesConfigData"> + <data key="path">catalog/frontend/grid_per_page_values</data> + <data key="value">12,24,36</data> + </entity> + <entity name="CustomGridPerPageValuesConfigData"> + <data key="path">catalog/frontend/grid_per_page_values</data> + <data key="value">1,2</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml index b5d5d61f6468b..bde7a94662d04 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml @@ -9,7 +9,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategorySEOSection"> - <element name="SectionHeader" type="button" selector="div[data-index='search_engine_optimization']" timeout="30"/> + <element name="SectionHeader" type="button" selector="div[data-index='search_engine_optimization'] .fieldset-wrapper-title" timeout="30"/> + <element name="sectionBody" type="text" selector="div[data-index='search_engine_optimization'] .admin__fieldset-wrapper-content"/> <element name="UrlKeyInput" type="input" selector="input[name='url_key']"/> <element name="UrlKeyDefaultValueCheckbox" type="button" selector="input[name='use_default[url_key]']"/> <element name="UrlKeyRedirectCheckbox" type="button" selector="[data-index='url_key_create_redirect'] input[type='checkbox']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml index 7ce795c78f25b..09eb4ad954274 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml @@ -12,5 +12,6 @@ <element name="previousPage" type="button" selector=".//*[@class='toolbar toolbar-products'][2]//a[contains(@class, 'previous')]" timeout="30"/> <element name="pageNumber" type="text" selector="//*[@class='toolbar toolbar-products'][2]//a[contains(@class, 'page')]//span[2][contains(text() ,'{{var1}}')]" parameterized="true"/> <element name="perPage" type="select" selector="//*[@class='toolbar toolbar-products'][2]//select[@id='limiter']"/> + <element name="currentPage" type="text" selector=".products.wrapper + .toolbar-products .pages .current span:nth-of-type(2)"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 631649e33b0fd..a5a02ad95b1f7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -62,6 +62,7 @@ <element name="productAttributeOptionsPrice" type="text" selector="//label[contains(.,'{{var1}}')]//span[@data-price-amount='{{var2}}']" parameterized="true"/> <element name="productAttributeOptionsDropDown" type="text" selector="//label[contains(.,'{{var1}}')]/../div[@class='control']//select//option[@price='{{var2}}']" parameterized="true"/> <element name="productAttributeOptionsRadioButtons" type="text" selector="//label[contains(.,'{{var1}}')]/../div[@class='control']//span[@data-price-amount='{{var2}}']" parameterized="true"/> + <element name="productAttributeOptionsRadioButtonByName" type="checkbox" selector="//*[@id='product-options-wrapper']//div[contains(@class,'fieldset')]//label[contains(.,'{{attributeName}}')]/../div[contains(@class,'control')]//label[contains(@class,'label') and contains(.,'{{optionName}}')]/preceding-sibling::input[@type='radio']" parameterized="true"/> <element name="productAttributeOptionsCheckbox" type="text" selector="//label[contains(.,'{{var1}}')]/../div[@class='control']//span[@data-price-amount='{{var2}}']" parameterized="true"/> <element name="productAttributeOptionsMultiselect" type="text" selector="//label[contains(.,'{{var1}}')]/../div[@class='control']//select//option[@price='{{var2}}']" parameterized="true"/> <element name="productAttributeOptionsData" type="text" selector="//span[contains(.,'{{var1}}')]/../span[@class='price-notice']//span[@data-price-amount='{{var2}}']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml index ee9e364758899..cef5bc622c6cf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCloneProductWithDuplicateUrlTest.xml @@ -38,7 +38,7 @@ <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> <!--Save and duplicated the product once--> <comment userInput="Save and duplicated the product once" stepKey="commentSaveAndDuplicateProduct"/> - <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductFormFirstTime"/> + <actionGroup ref="AdminFormSaveAndDuplicateActionGroup" stepKey="saveAndDuplicateProductFormFirstTime"/> <conditionalClick selector="{{AdminProductSEOSection.sectionHeader}}" dependentSelector="{{AdminProductSEOSection.urlKeyInput}}" visible="false" stepKey="openSEOSection"/> <grabValueFrom selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="grabDuplicatedProductUrlKey"/> <assertContains expected="$$createSimpleProduct.custom_attributes[url_key]$$" actual="$grabDuplicatedProductUrlKey" stepKey="assertDuplicatedProductUrlKey"/> @@ -65,7 +65,7 @@ <comment userInput="Save and duplicated the product second time" stepKey="commentSaveAndDuplicateProduct1"/> <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToProductEditPage1"/> <waitForPageLoad stepKey="waitForSimpleProductPageLoad2"/> - <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductFormSecondTime"/> + <actionGroup ref="AdminFormSaveAndDuplicateActionGroup" stepKey="saveAndDuplicateProductFormSecondTime"/> <conditionalClick selector="{{AdminProductSEOSection.sectionHeader}}" dependentSelector="{{AdminProductSEOSection.urlKeyInput}}" visible="false" stepKey="openProductSEOSection"/> <waitForElementVisible selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="waitForUrlKeyField"/> <grabValueFrom selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="grabSecondDuplicatedProductUrlKey"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml index f5e42bb84549c..eacb69db38e9a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml @@ -72,7 +72,7 @@ <argument name="product" value="$$createSimpleProduct$$"/> </actionGroup> <!--Save and duplicated the product once--> - <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductForm1"/> + <actionGroup ref="AdminFormSaveAndDuplicateActionGroup" stepKey="saveAndDuplicateProductForm1"/> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct2"> <argument name="product" value="$$createSimpleProduct$$"/> </actionGroup> @@ -80,6 +80,6 @@ <argument name="product" value="$$createSimpleProduct$$"/> </actionGroup> <!--Save and duplicated the product second time--> - <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductForm2"/> + <actionGroup ref="AdminFormSaveAndDuplicateActionGroup" stepKey="saveAndDuplicateProductForm2"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml index 976f714d7b3e1..4c3d519106389 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <group value="catalog"/> <group value="mtf_migrated"/> + <skip> + <issueId value="MC-30682"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml index fbc0354482a32..81d032850bf5a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml @@ -61,7 +61,7 @@ <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct2"> <argument name="product" value="$$product2$$"/> </actionGroup> - <actionGroup ref="AdminFormSaveAndClose" stepKey="saveAndCloseProduct"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="saveAndCloseProduct"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <seeInField selector="{{AdminDataGridPaginationSection.currentPage}}" userInput="2" stepKey="seeOnSecondPageOrderGrid"/> </test> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml index 551b3437cb856..f178d55b97fca 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml @@ -16,6 +16,9 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-76273"/> <group value="category"/> + <skip> + <issueId value="MC-30720"/> + </skip> </annotations> <before> <createData entity="SimpleSubCategory" stepKey="simpleSubCategoryOne"/> @@ -29,6 +32,7 @@ <createData entity="_defaultProduct" stepKey="productTwo"> <requiredEntity createDataKey="simpleSubCategoryOne"/> </createData> + <magentoCLI command="cron:run --group=index" stepKey="runIndexerCron"/> </before> <after> <actionGroup ref="logout" stepKey="logoutAdminUserAfterTest"/> @@ -119,4 +123,4 @@ <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$simpleSubCategoryOne.name$$" stepKey="seeSubCategoryWithParentInBreadcrumbsOnSubCategoryWithParent3"/> <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$productOne.name$$" stepKey="seeProductInBreadcrumbsOnSubCategoryOne3"/> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml index df2b525a56086..d9f894fa5736b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml @@ -48,7 +48,7 @@ <actionGroup ref="FilterProductGridBySetNewFromDateActionGroup" stepKey="filterProductGridToCheckSetAsNewColumn"/> <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnFirstRowProductGrid"/> <waitForPageLoad stepKey="waitForProductEditPageToLoad"/> - <actionGroup ref="AdminFormSaveAndClose" stepKey="saveAndCloseProductForm"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="saveAndCloseProductForm"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="expandFilters"/> <seeInField selector="{{AdminProductGridFilterSection.newFromDateFilter}}" userInput="05/16/2018" stepKey="checkForNewFromDate"/> <click selector="{{AdminProductGridFilterSection.columnsDropdown}}" stepKey="openColumnsDropdown2"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml index 1c9cdad681d98..44b4e60973907 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml @@ -33,8 +33,9 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <deleteData createDataKey="attribute" stepKey="deleteAttribute"/> <deleteData createDataKey="product" stepKey="deleteProduct"/> + <deleteData createDataKey="attribute" stepKey="deleteAttribute"/> + <magentoCLI command="cron:run --group=index" stepKey="runCron"/> <actionGroup ref="logout" stepKey="logout"/> </after> <!-- Assert attribute presence in storefront product additional information --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml index 10347584b4cda..04110dbd73a4c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml @@ -17,6 +17,9 @@ <severity value="CRITICAL"/> <group value="catalog"/> <group value="mtf_migrated"/> + <skip> + <issueId value="MC-30166"/> + </skip> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml index 70d2fb63941c7..c75abdbe43e24 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml @@ -7,13 +7,13 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ProductAttributeWithoutValueInCompareListTest"> <annotations> <features value="Catalog"/> + <stories value="Product Comparison"/> <title value="Product attribute without value in compare list test"/> - <description - value="The product attribute that has no value should output 'N/A' on the product comparison page."/> + <description value="The product attribute that has no value should output 'N/A' on the product comparison page."/> <severity value="MINOR"/> <group value="Catalog"/> </annotations> @@ -22,7 +22,7 @@ <createData entity="textProductAttribute" stepKey="createProductAttribute"/> <createData entity="CatalogAttributeSet" stepKey="createAttributeSet"/> <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$$createAttributeSet.attribute_set_id$$/" - stepKey="onAttributeSetEdit"/> + stepKey="onAttributeSetEdit"/> <actionGroup ref="AssignAttributeToGroupActionGroup" stepKey="assignAttributeToGroup"> <argument name="group" value="Product Details"/> <argument name="attribute" value="$$createProductAttribute.attribute_code$$"/> @@ -69,11 +69,11 @@ </actionGroup> <!--See attribute default value in the comparison list--> <see userInput="$createProductAttribute.defaultValue$" - selector="{{StorefrontProductCompareMainSection.ProductAttributeByCodeAndProductName(ProductAttributeFrontendLabel.label, $createProductCustom.name$)}}" - stepKey="assertAttributeValueForProductCustom"/> + selector="{{StorefrontProductCompareMainSection.ProductAttributeByCodeAndProductName(ProductAttributeFrontendLabel.label, $createProductCustom.name$)}}" + stepKey="assertAttributeValueForProductCustom"/> <!--See N/A if attribute has no value in the comparison list--> <see userInput="N/A" - selector="{{StorefrontProductCompareMainSection.ProductAttributeByCodeAndProductName(ProductAttributeFrontendLabel.label, $createProductDefault.name$)}}" - stepKey="assertNAForProductDefault"/> + selector="{{StorefrontProductCompareMainSection.ProductAttributeByCodeAndProductName(ProductAttributeFrontendLabel.label, $createProductDefault.name$)}}" + stepKey="assertNAForProductDefault"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php deleted file mode 100644 index 9a2199859a1df..0000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php +++ /dev/null @@ -1,442 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Test\Unit\Block\Adminhtml\Product\Helper\Form\Gallery; - -use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content; -use Magento\Catalog\Model\Entity\Attribute; -use Magento\Catalog\Model\Product; -use Magento\Framework\Phrase; -use Magento\MediaStorage\Helper\File\Storage\Database; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ContentTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject - */ - protected $fileSystemMock; - - /** - * @var \Magento\Framework\Filesystem\Directory\Read|\PHPUnit_Framework_MockObject_MockObject - */ - protected $readMock; - - /** - * @var Content|\PHPUnit_Framework_MockObject_MockObject - */ - protected $content; - - /** - * @var \Magento\Catalog\Model\Product\Media\Config|\PHPUnit_Framework_MockObject_MockObject - */ - protected $mediaConfigMock; - - /** - * @var \Magento\Framework\Json\EncoderInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $jsonEncoderMock; - - /** - * @var \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery|\PHPUnit_Framework_MockObject_MockObject - */ - protected $galleryMock; - - /** - * @var \Magento\Catalog\Helper\Image|\PHPUnit_Framework_MockObject_MockObject - */ - protected $imageHelper; - - /** - * @var \Magento\MediaStorage\Helper\File\Storage\Database|\PHPUnit_Framework_MockObject_MockObject - */ - protected $databaseMock; - - /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager - */ - protected $objectManager; - - public function setUp() - { - $this->fileSystemMock = $this->createPartialMock( - \Magento\Framework\Filesystem::class, - ['stat', 'getDirectoryRead'] - ); - $this->readMock = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadInterface::class); - $this->galleryMock = $this->createMock(\Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery::class); - $this->mediaConfigMock = $this->createPartialMock( - \Magento\Catalog\Model\Product\Media\Config::class, - ['getMediaUrl', 'getMediaPath'] - ); - $this->jsonEncoderMock = $this->getMockBuilder(\Magento\Framework\Json\EncoderInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->databaseMock = $this->getMockBuilder(Database::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->content = $this->objectManager->getObject( - \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content::class, - [ - 'mediaConfig' => $this->mediaConfigMock, - 'jsonEncoder' => $this->jsonEncoderMock, - 'filesystem' => $this->fileSystemMock, - 'fileStorageDatabase' => $this->databaseMock - ] - ); - } - - public function testGetImagesJson() - { - $url = [ - ['file_1.jpg', 'url_to_the_image/image_1.jpg'], - ['file_2.jpg', 'url_to_the_image/image_2.jpg'] - ]; - $mediaPath = [ - ['file_1.jpg', 'catalog/product/image_1.jpg'], - ['file_2.jpg', 'catalog/product/image_2.jpg'] - ]; - - $sizeMap = [ - ['catalog/product/image_1.jpg', ['size' => 399659]], - ['catalog/product/image_2.jpg', ['size' => 879394]] - ]; - - $imagesResult = [ - [ - 'value_id' => '2', - 'file' => 'file_2.jpg', - 'media_type' => 'image', - 'position' => '0', - 'url' => 'url_to_the_image/image_2.jpg', - 'size' => 879394 - ], - [ - 'value_id' => '1', - 'file' => 'file_1.jpg', - 'media_type' => 'image', - 'position' => '1', - 'url' => 'url_to_the_image/image_1.jpg', - 'size' => 399659 - ] - ]; - - $images = [ - 'images' => [ - [ - 'value_id' => '1', - 'file' => 'file_1.jpg', - 'media_type' => 'image', - 'position' => '1' - ] , - [ - 'value_id' => '2', - 'file' => 'file_2.jpg', - 'media_type' => 'image', - 'position' => '0' - ] - ] - ]; - - $this->content->setElement($this->galleryMock); - $this->galleryMock->expects($this->once())->method('getImages')->willReturn($images); - $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->willReturn($this->readMock); - - $this->mediaConfigMock->expects($this->any())->method('getMediaUrl')->willReturnMap($url); - $this->mediaConfigMock->expects($this->any())->method('getMediaPath')->willReturnMap($mediaPath); - $this->readMock->expects($this->any())->method('stat')->willReturnMap($sizeMap); - $this->jsonEncoderMock->expects($this->once())->method('encode')->willReturnCallback('json_encode'); - - $this->readMock->expects($this->any()) - ->method('isFile') - ->will($this->returnValue(true)); - $this->databaseMock->expects($this->any()) - ->method('checkDbUsage') - ->will($this->returnValue(false)); - - $this->assertSame(json_encode($imagesResult), $this->content->getImagesJson()); - } - - public function testGetImagesJsonWithoutImages() - { - $this->content->setElement($this->galleryMock); - $this->galleryMock->expects($this->once())->method('getImages')->willReturn(null); - - $this->assertSame('[]', $this->content->getImagesJson()); - } - - public function testGetImagesJsonWithException() - { - $this->imageHelper = $this->getMockBuilder(\Magento\Catalog\Helper\Image::class) - ->disableOriginalConstructor() - ->setMethods(['getDefaultPlaceholderUrl']) - ->getMock(); - - $this->objectManager->setBackwardCompatibleProperty( - $this->content, - 'imageHelper', - $this->imageHelper - ); - - $placeholderUrl = 'url_to_the_placeholder/placeholder.jpg'; - - $imagesResult = [ - [ - 'value_id' => '2', - 'file' => 'file_2.jpg', - 'media_type' => 'image', - 'position' => '0', - 'url' => 'url_to_the_placeholder/placeholder.jpg', - 'size' => 0 - ], - [ - 'value_id' => '1', - 'file' => 'file_1.jpg', - 'media_type' => 'image', - 'position' => '1', - 'url' => 'url_to_the_placeholder/placeholder.jpg', - 'size' => 0 - ] - ]; - - $images = [ - 'images' => [ - [ - 'value_id' => '1', - 'file' => 'file_1.jpg', - 'media_type' => 'image', - 'position' => '1' - ], - [ - 'value_id' => '2', - 'file' => 'file_2.jpg', - 'media_type' => 'image', - 'position' => '0' - ] - ] - ]; - - $this->content->setElement($this->galleryMock); - $this->galleryMock->expects($this->once())->method('getImages')->willReturn($images); - $this->fileSystemMock->expects($this->any())->method('getDirectoryRead')->willReturn($this->readMock); - $this->mediaConfigMock->expects($this->any())->method('getMediaUrl'); - $this->mediaConfigMock->expects($this->any())->method('getMediaPath'); - - $this->readMock->expects($this->any()) - ->method('isFile') - ->will($this->returnValue(true)); - $this->databaseMock->expects($this->any()) - ->method('checkDbUsage') - ->will($this->returnValue(false)); - - $this->readMock->expects($this->any())->method('stat')->willReturnOnConsecutiveCalls( - $this->throwException( - new \Magento\Framework\Exception\FileSystemException(new Phrase('test')) - ), - $this->throwException( - new \Magento\Framework\Exception\FileSystemException(new Phrase('test')) - ) - ); - $this->imageHelper->expects($this->any())->method('getDefaultPlaceholderUrl')->willReturn($placeholderUrl); - $this->jsonEncoderMock->expects($this->once())->method('encode')->willReturnCallback('json_encode'); - - $this->assertSame(json_encode($imagesResult), $this->content->getImagesJson()); - } - - /** - * Test GetImageTypes() will return value for given attribute from data persistor. - * - * @return void - */ - public function testGetImageTypesFromDataPersistor() - { - $attributeCode = 'thumbnail'; - $value = 'testImageValue'; - $scopeLabel = 'testScopeLabel'; - $label = 'testLabel'; - $name = 'testName'; - $expectedTypes = [ - $attributeCode => [ - 'code' => $attributeCode, - 'value' => $value, - 'label' => $label, - 'name' => $name, - ], - ]; - $product = $this->getMockBuilder(Product::class) - ->disableOriginalConstructor() - ->getMock(); - $product->expects($this->once()) - ->method('getData') - ->with($this->identicalTo($attributeCode)) - ->willReturn(null); - $mediaAttribute = $this->getMediaAttribute($label, $attributeCode); - $product->expects($this->once()) - ->method('getMediaAttributes') - ->willReturn([$mediaAttribute]); - $this->galleryMock->expects($this->exactly(2)) - ->method('getDataObject') - ->willReturn($product); - $this->galleryMock->expects($this->once()) - ->method('getImageValue') - ->with($this->identicalTo($attributeCode)) - ->willReturn($value); - $this->galleryMock->expects($this->once()) - ->method('getScopeLabel') - ->with($this->identicalTo($mediaAttribute)) - ->willReturn($scopeLabel); - $this->galleryMock->expects($this->once()) - ->method('getAttributeFieldName') - ->with($this->identicalTo($mediaAttribute)) - ->willReturn($name); - $this->getImageTypesAssertions($attributeCode, $scopeLabel, $expectedTypes); - } - - /** - * Test GetImageTypes() will return value for given attribute from product. - * - * @return void - */ - public function testGetImageTypesFromProduct() - { - $attributeCode = 'thumbnail'; - $value = 'testImageValue'; - $scopeLabel = 'testScopeLabel'; - $label = 'testLabel'; - $name = 'testName'; - $expectedTypes = [ - $attributeCode => [ - 'code' => $attributeCode, - 'value' => $value, - 'label' => $label, - 'name' => $name, - ], - ]; - $product = $this->getMockBuilder(Product::class) - ->disableOriginalConstructor() - ->getMock(); - $product->expects($this->once()) - ->method('getData') - ->with($this->identicalTo($attributeCode)) - ->willReturn($value); - $mediaAttribute = $this->getMediaAttribute($label, $attributeCode); - $product->expects($this->once()) - ->method('getMediaAttributes') - ->willReturn([$mediaAttribute]); - $this->galleryMock->expects($this->exactly(2)) - ->method('getDataObject') - ->willReturn($product); - $this->galleryMock->expects($this->never()) - ->method('getImageValue'); - $this->galleryMock->expects($this->once()) - ->method('getScopeLabel') - ->with($this->identicalTo($mediaAttribute)) - ->willReturn($scopeLabel); - $this->galleryMock->expects($this->once()) - ->method('getAttributeFieldName') - ->with($this->identicalTo($mediaAttribute)) - ->willReturn($name); - $this->getImageTypesAssertions($attributeCode, $scopeLabel, $expectedTypes); - } - - /** - * Perform assertions. - * - * @param string $attributeCode - * @param string $scopeLabel - * @param array $expectedTypes - * @return void - */ - private function getImageTypesAssertions(string $attributeCode, string $scopeLabel, array $expectedTypes) - { - $this->content->setElement($this->galleryMock); - $result = $this->content->getImageTypes(); - $scope = $result[$attributeCode]['scope']; - $this->assertSame($scopeLabel, $scope->getText()); - unset($result[$attributeCode]['scope']); - $this->assertSame($expectedTypes, $result); - } - - /** - * Get media attribute mock. - * - * @param string $label - * @param string $attributeCode - * @return \PHPUnit_Framework_MockObject_MockObject - */ - private function getMediaAttribute(string $label, string $attributeCode) - { - $frontend = $this->getMockBuilder(Product\Attribute\Frontend\Image::class) - ->disableOriginalConstructor() - ->getMock(); - $frontend->expects($this->once()) - ->method('getLabel') - ->willReturn($label); - $mediaAttribute = $this->getMockBuilder(Attribute::class) - ->disableOriginalConstructor() - ->getMock(); - $mediaAttribute->expects($this->any()) - ->method('getAttributeCode') - ->willReturn($attributeCode); - $mediaAttribute->expects($this->once()) - ->method('getFrontend') - ->willReturn($frontend); - - return $mediaAttribute; - } - - /** - * Test GetImagesJson() calls MediaStorage functions to obtain image from DB prior to stat call - * - * @return void - */ - public function testGetImagesJsonMediaStorageMode() - { - $images = [ - 'images' => [ - [ - 'value_id' => '0', - 'file' => 'file_1.jpg', - 'media_type' => 'image', - 'position' => '0' - ] - ] - ]; - - $mediaPath = [ - ['file_1.jpg', 'catalog/product/image_1.jpg'] - ]; - - $this->content->setElement($this->galleryMock); - - $this->galleryMock->expects($this->once()) - ->method('getImages') - ->willReturn($images); - $this->fileSystemMock->expects($this->once()) - ->method('getDirectoryRead') - ->willReturn($this->readMock); - $this->mediaConfigMock->expects($this->any()) - ->method('getMediaPath') - ->willReturnMap($mediaPath); - - $this->readMock->expects($this->any()) - ->method('isFile') - ->will($this->returnValue(false)); - $this->databaseMock->expects($this->any()) - ->method('checkDbUsage') - ->will($this->returnValue(true)); - - $this->databaseMock->expects($this->once()) - ->method('saveFileToFilesystem') - ->with('catalog/product/image_1.jpg'); - - $this->content->getImagesJson(); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php index e7e8ab5ea91a7..85ab52384740d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php @@ -6,19 +6,20 @@ namespace Magento\Catalog\Test\Unit\Block\Ui; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductRenderInterface; +use Magento\Catalog\Block\Ui\ProductViewCounter; +use Magento\Catalog\Model\ProductRenderFactory; use Magento\Catalog\Model\ProductRepository; use Magento\Catalog\Ui\DataProvider\Product\ProductRenderCollectorComposite; -use Magento\Catalog\Model\ProductRenderFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\EntityManager\Hydrator; +use Magento\Framework\Registry; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Url; use Magento\Framework\View\Element\Template\Context; -use Magento\Store\Model\StoreManager; use Magento\Store\Model\Store; -use Magento\Framework\Registry; -use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Api\Data\ProductRenderInterface; -use Magento\Catalog\Block\Ui\ProductViewCounter; +use Magento\Store\Model\StoreManager; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -70,6 +71,11 @@ class ProductViewCounterTest extends \PHPUnit\Framework\TestCase */ private $storeManagerMock; + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + /** * @var ProductRenderFactory|\PHPUnit_Framework_MockObject_MockObject */ @@ -104,6 +110,9 @@ protected function setUp() $this->storeManagerMock = $this->getMockBuilder(StoreManager::class) ->disableOriginalConstructor() ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->productViewCounter = new ProductViewCounter( $this->contextMock, @@ -114,7 +123,8 @@ protected function setUp() $this->hydratorMock, $this->serializeMock, $this->urlMock, - $this->registryMock + $this->registryMock, + $this->scopeConfigMock ); } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php index b826f25d7c591..595f81cc4ecd3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php @@ -178,7 +178,13 @@ protected function setUp() ); } - public function testApplyCustomLayoutUpdate() + /** + * Apply custom layout update is correct + * + * @dataProvider getInvocationData + * @return void + */ + public function testApplyCustomLayoutUpdate(array $expectedData): void { $categoryId = 123; $pageLayout = 'page_layout'; @@ -199,11 +205,70 @@ public function testApplyCustomLayoutUpdate() \Magento\Framework\DataObject::class, ['getPageLayout', 'getLayoutUpdates'] ); + $this->category->expects($this->at(1)) + ->method('hasChildren') + ->willReturn(true); + $this->category->expects($this->at(2)) + ->method('hasChildren') + ->willReturn($expectedData[1][0]['type'] === 'default' ? true : false); + $this->category->expects($this->once()) + ->method('getDisplayMode') + ->willReturn($expectedData[2][0]['displaymode']); + $this->expectationForPageLayoutHandles($expectedData); $settings->expects($this->atLeastOnce())->method('getPageLayout')->will($this->returnValue($pageLayout)); $settings->expects($this->once())->method('getLayoutUpdates')->willReturn(['update1', 'update2']); - $this->catalogDesign->expects($this->any())->method('getDesignSettings')->will($this->returnValue($settings)); $this->action->execute(); } + + /** + * Expected invocation for Layout Handles + * + * @param array $data + * @return void + */ + private function expectationForPageLayoutHandles($data): void + { + $index = 1; + + foreach ($data as $expectedData) { + $this->page->expects($this->at($index)) + ->method('addPageLayoutHandles') + ->with($expectedData[0], $expectedData[1], $expectedData[2]); + $index++; + } + } + + /** + * Data provider for execute method. + * + * @return array + */ + public function getInvocationData(): array + { + return [ + [ + 'layoutHandles' => [ + [['type' => 'default'], null, false], + [['type' => 'default_without_children'], null, false], + [['displaymode' => 'products'], null, false] + ] + ], + [ + 'layoutHandles' => [ + [['type' => 'default'], null, false], + [['type' => 'default_without_children'], null, false], + [['displaymode' => 'page'], null, false] + ] + ], + [ + 'layoutHandles' => [ + [['type' => 'default'], null, false], + [['type' => 'default'], null, false], + [['displaymode' => 'poducts_and_page'], null, false] + ] + ] + ]; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Product/Compare/IndexTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Product/Compare/IndexTest.php index bf6f6cff9ad80..a2e784eef9f1b 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Product/Compare/IndexTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Product/Compare/IndexTest.php @@ -129,7 +129,6 @@ public function testExecute() ->method('getParam') ->willReturnMap( [ - ['items', null, null], ['uenc', null, $beforeUrl], ] ); @@ -141,34 +140,7 @@ public function testExecute() ->method('setBeforeCompareUrl') ->with($beforeUrl . '1') ->willReturnSelf(); - $this->listCompareMock->expects($this->never())->method('addProducts'); $this->redirectFactoryMock->expects($this->never())->method('create'); $this->index->execute(); } - - public function testExecuteWithItems() - { - $this->request->expects($this->any()) - ->method('getParam') - ->willReturnMap( - [ - ['items', null, '1,2,3'], - ['uenc', null, null], - ] - ); - $this->decoderMock->expects($this->never())->method('decode'); - $this->catalogSession->expects($this->never())->method('setBeforeCompareUrl'); - - $this->listCompareMock->expects($this->once()) - ->method('addProducts') - ->with([1, 2, 3]); - $redirect = $this->createPartialMock(\Magento\Framework\Controller\Result\Redirect::class, ['setPath']); - $redirect->expects($this->once()) - ->method('setPath') - ->with('*/*/*'); - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($redirect); - $this->index->execute(); - } } diff --git a/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php b/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php index e30ddda0b70b9..6f5d927e333ec 100644 --- a/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php @@ -15,6 +15,7 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Url; use Magento\Catalog\Model\ResourceModel\Product\Compare\Item\Collection; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; class CompareProductsTest extends \PHPUnit\Framework\TestCase @@ -44,6 +45,11 @@ class CompareProductsTest extends \PHPUnit\Framework\TestCase */ private $objectManagerHelper; + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + /** * @var array */ @@ -65,6 +71,9 @@ protected function setUp() $this->outputHelperMock = $this->getMockBuilder(Output::class) ->disableOriginalConstructor() ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->objectManagerHelper = new ObjectManagerHelper($this); @@ -73,7 +82,8 @@ protected function setUp() [ 'helper' => $this->helperMock, 'productUrl' => $this->productUrlMock, - 'outputHelper' => $this->outputHelperMock + 'outputHelper' => $this->outputHelperMock, + 'scopeConfig' => $this->scopeConfigMock ] ); } @@ -109,6 +119,7 @@ private function prepareProductsWithCorrespondingMocks(array $dataSet) : array $urlMap = []; $outputMap = []; $helperMap = []; + $productScopeMap = []; $count = count($dataSet); @@ -119,6 +130,7 @@ private function prepareProductsWithCorrespondingMocks(array $dataSet) : array $outputMap[] = [$item, $data['name'], 'name', 'productName#' . $data['id']]; $helperMap[] = [$item, 'http://remove.url/' . $data['id']]; $urlMap[] = [$item, [], 'http://product.url/' . $data['id']]; + $productScopeMap[] = [$item, 'store-' . $data['id']]; } $this->productUrlMock->expects($this->exactly($count)) @@ -193,19 +205,22 @@ public function testGetSectionData() 'id' => 1, 'product_url' => 'http://product.url/1', 'name' => 'productName#1', - 'remove_url' => 'http://remove.url/1' + 'remove_url' => 'http://remove.url/1', + 'productScope' => null ], [ 'id' => 2, 'product_url' => 'http://product.url/2', 'name' => 'productName#2', - 'remove_url' => 'http://remove.url/2' + 'remove_url' => 'http://remove.url/2', + 'productScope' => null ], [ 'id' => 3, 'product_url' => 'http://product.url/3', 'name' => 'productName#3', - 'remove_url' => 'http://remove.url/3' + 'remove_url' => 'http://remove.url/3', + 'productScope' => null ] ] ], @@ -276,7 +291,8 @@ public function testGetSectionDataSingleItem() 'id' => 12345, 'product_url' => 'http://product.url/12345', 'name' => 'productName#12345', - 'remove_url' => 'http://remove.url/12345' + 'remove_url' => 'http://remove.url/12345', + 'productScope' => null ] ] ], diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php deleted file mode 100644 index 6552e85440008..0000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php +++ /dev/null @@ -1,154 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Test\Unit\Model; - -class ImageUploaderTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Catalog\Model\ImageUploader - */ - private $imageUploader; - - /** - * Core file storage database - * - * @var \Magento\MediaStorage\Helper\File\Storage\Database|\PHPUnit_Framework_MockObject_MockObject - */ - private $coreFileStorageDatabaseMock; - - /** - * Media directory object (writable). - * - * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject - */ - private $mediaDirectoryMock; - - /** - * Media directory object (writable). - * - * @var \Magento\Framework\Filesystem\Directory\WriteInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $mediaWriteDirectoryMock; - - /** - * Uploader factory - * - * @var \Magento\MediaStorage\Model\File\UploaderFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $uploaderFactoryMock; - - /** - * Store manager - * - * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $storeManagerMock; - - /** - * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $loggerMock; - - /** - * Base tmp path - * - * @var string - */ - private $baseTmpPath; - - /** - * Base path - * - * @var string - */ - private $basePath; - - /** - * Allowed extensions - * - * @var array - */ - private $allowedExtensions; - - /** - * Allowed mime types - * - * @var array - */ - private $allowedMimeTypes; - - protected function setUp() - { - $this->coreFileStorageDatabaseMock = $this->createMock( - \Magento\MediaStorage\Helper\File\Storage\Database::class - ); - $this->mediaDirectoryMock = $this->createMock( - \Magento\Framework\Filesystem::class - ); - $this->mediaWriteDirectoryMock = $this->createMock( - \Magento\Framework\Filesystem\Directory\WriteInterface::class - ); - $this->mediaDirectoryMock->expects($this->any())->method('getDirectoryWrite')->willReturn( - $this->mediaWriteDirectoryMock - ); - $this->uploaderFactoryMock = $this->createMock( - \Magento\MediaStorage\Model\File\UploaderFactory::class - ); - $this->storeManagerMock = $this->createMock( - \Magento\Store\Model\StoreManagerInterface::class - ); - $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - $this->baseTmpPath = 'base/tmp/'; - $this->basePath = 'base/real/'; - $this->allowedExtensions = ['.jpg']; - $this->allowedMimeTypes = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png']; - - $this->imageUploader = - new \Magento\Catalog\Model\ImageUploader( - $this->coreFileStorageDatabaseMock, - $this->mediaDirectoryMock, - $this->uploaderFactoryMock, - $this->storeManagerMock, - $this->loggerMock, - $this->baseTmpPath, - $this->basePath, - $this->allowedExtensions, - $this->allowedMimeTypes - ); - } - - public function testSaveFileToTmpDir() - { - $fileId = 'file.jpg'; - $allowedMimeTypes = [ - 'image/jpg', - 'image/jpeg', - 'image/gif', - 'image/png', - ]; - /** @var \Magento\MediaStorage\Model\File\Uploader|\PHPUnit_Framework_MockObject_MockObject $uploader */ - $uploader = $this->createMock(\Magento\MediaStorage\Model\File\Uploader::class); - $this->uploaderFactoryMock->expects($this->once())->method('create')->willReturn($uploader); - $uploader->expects($this->once())->method('setAllowedExtensions')->with($this->allowedExtensions); - $uploader->expects($this->once())->method('setAllowRenameFiles')->with(true); - $this->mediaWriteDirectoryMock->expects($this->once())->method('getAbsolutePath')->with($this->baseTmpPath) - ->willReturn($this->basePath); - $uploader->expects($this->once())->method('save')->with($this->basePath) - ->willReturn(['tmp_name' => $this->baseTmpPath, 'file' => $fileId, 'path' => $this->basePath]); - $uploader->expects($this->atLeastOnce())->method('checkMimeType')->with($allowedMimeTypes)->willReturn(true); - $storeMock = $this->createPartialMock( - \Magento\Store\Model\Store::class, - ['getBaseUrl'] - ); - $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); - $storeMock->expects($this->once())->method('getBaseUrl'); - $this->coreFileStorageDatabaseMock->expects($this->once())->method('saveFile'); - - $result = $this->imageUploader->saveFileToTmpDir($fileId); - - $this->assertArrayNotHasKey('path', $result); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/ParamsBuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/ParamsBuilderTest.php index 22e3a88574e03..68239c8fa8aed 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/ParamsBuilderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Image/ParamsBuilderTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Test\Unit\Model\Product\Image; @@ -11,10 +12,15 @@ use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Config\View; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\ConfigInterface; use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\TestCase; -class ParamsBuilderTest extends \PHPUnit\Framework\TestCase +/** + * Test product image params builder + */ +class ParamsBuilderTest extends TestCase { /** * @var ScopeConfigInterface @@ -30,10 +36,17 @@ class ParamsBuilderTest extends \PHPUnit\Framework\TestCase * @var ParamsBuilder */ private $model; + /** + * @var array + */ + private $scopeConfigData = []; + /** + * @inheritDoc + */ protected function setUp() { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $objectManager = new ObjectManager($this); $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); $this->viewConfig = $this->createMock(ConfigInterface::class); $this->model = $objectManager->getObject( @@ -43,28 +56,37 @@ protected function setUp() 'viewConfig' => $this->viewConfig, ] ); + $this->scopeConfigData = []; + $this->scopeConfig->method('getValue') + ->willReturnCallback( + function ($path, $scopeType, $scopeCode) { + return $this->scopeConfigData[$path][$scopeType][$scopeCode] ?? null; + } + ); } /** - * Test watermark location. + * Test build() with different parameters and config values + * + * @param int $scopeId + * @param array $config + * @param array $imageArguments + * @param array $expected + * @dataProvider buildDataProvider */ - public function testWatermarkLocation() + public function testBuild(int $scopeId, array $config, array $imageArguments, array $expected) { - $imageArguments = [ - 'type' => 'type', - 'height' => 'image_height', - 'width' => 'image_width', - 'angle' => 'angle', - 'background' => [1, 2, 3] + $this->scopeConfigData[Image::XML_PATH_JPEG_QUALITY][ScopeConfigInterface::SCOPE_TYPE_DEFAULT][null] = 80; + foreach ($config as $path => $value) { + $this->scopeConfigData[$path][ScopeInterface::SCOPE_STORE][$scopeId] = $value; + } + $imageArguments += [ + 'type' => 'image', + 'height' => '600', + 'width' => '400', + 'angle' => '45', + 'background' => [110, 64, 224] ]; - $scopeId = 1; - $quality = 100; - $file = 'file'; - $width = 'width'; - $height = 'height'; - $size = "{$width}x{$height}"; - $opacity = 'opacity'; - $position = 'position'; $viewMock = $this->createMock(View::class); $viewMock->expects($this->once()) @@ -77,51 +99,16 @@ public function testWatermarkLocation() ->with(['area' => Area::AREA_FRONTEND]) ->willReturn($viewMock); - $this->scopeConfig->expects($this->exactly(5))->method('getValue')->withConsecutive( - [ - Image::XML_PATH_JPEG_QUALITY - ], - [ - "design/watermark/{$imageArguments['type']}_image", - ScopeInterface::SCOPE_STORE, - $scopeId, - ], - [ - "design/watermark/{$imageArguments['type']}_size", - ScopeInterface::SCOPE_STORE], - [ - "design/watermark/{$imageArguments['type']}_imageOpacity", - ScopeInterface::SCOPE_STORE, - $scopeId - ], - [ - "design/watermark/{$imageArguments['type']}_position", - ScopeInterface::SCOPE_STORE, - $scopeId - ] - )->willReturnOnConsecutiveCalls( - $quality, - $file, - $size, - $opacity, - $position - ); - $actual = $this->model->build($imageArguments, $scopeId); - $expected = [ + $expected += [ 'image_type' => $imageArguments['type'], 'background' => $imageArguments['background'], 'angle' => $imageArguments['angle'], - 'quality' => $quality, + 'quality' => 80, 'keep_aspect_ratio' => true, 'keep_frame' => true, 'keep_transparency' => true, 'constrain_only' => true, - 'watermark_file' => $file, - 'watermark_image_opacity' => $opacity, - 'watermark_position' => $position, - 'watermark_width' => $width, - 'watermark_height' => $height, 'image_height' => $imageArguments['height'], 'image_width' => $imageArguments['width'], ]; @@ -131,4 +118,50 @@ public function testWatermarkLocation() $actual ); } + + /** + * Provides test scenarios for + * + * @return array + */ + public function buildDataProvider() + { + return [ + 'watermark config' => [ + 1, + [ + 'design/watermark/small_image_image' => 'stores/1/magento-logo.png', + 'design/watermark/small_image_size' => '60x40', + 'design/watermark/small_image_imageOpacity' => '50', + 'design/watermark/small_image_position' => 'bottom-right', + ], + [ + 'type' => 'small_image' + ], + [ + 'watermark_file' => 'stores/1/magento-logo.png', + 'watermark_image_opacity' => '50', + 'watermark_position' => 'bottom-right', + 'watermark_width' => '60', + 'watermark_height' => '40', + ] + ], + 'watermark config empty' => [ + 1, + [ + 'design/watermark/small_image_image' => 'stores/1/magento-logo.png', + ], + [ + 'type' => 'small_image' + ], + [ + 'watermark_file' => 'stores/1/magento-logo.png', + 'watermark_image_opacity' => null, + 'watermark_position' => null, + 'watermark_width' => null, + 'watermark_height' => null, + ] + ] + ]; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php index ef7aad2cbb802..c7b6402cd3877 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php @@ -159,12 +159,11 @@ public function testGetUrl( $this->assertEquals($requestPathProduct, $this->model->getUrlInStore($product, $routeParams)); break; case 'getProductUrl': - $this->assertEquals($requestPathProduct, $this->model->getProductUrl($product, true)); + $this->assertEquals($requestPathProduct, $this->model->getProductUrl($product, null)); $this->sidResolver - ->expects($this->once()) + ->expects($this->never()) ->method('getUseSessionInUrl') ->will($this->returnValue(true)); - $this->assertEquals($requestPathProduct, $this->model->getProductUrl($product, null)); break; } } @@ -212,7 +211,7 @@ public function getUrlDataProvider() 1, 1, [], - ['_direct' => '/product/url/path', '_query' => []], + ['_direct' => '/product/url/path', '_query' => [], '_nosid' => true], null, null, ] diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php index 6f3d8e1a84b17..945c61f44e99c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php @@ -37,6 +37,21 @@ class LinkedProductSelectBuilderByIndexPriceTest extends \PHPUnit\Framework\Test */ private $baseSelectProcessorMock; + /** + * @var \Magento\Framework\Search\Request\IndexScopeResolverInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $indexScopeResolverMock; + + /** + * @var \Magento\Framework\Indexer\Dimension|\PHPUnit\Framework\MockObject\MockObject + */ + private $dimensionMock; + + /** + * @var \Magento\Framework\Indexer\DimensionFactory|\PHPUnit\Framework\MockObject\MockObject + */ + private $dimensionFactoryMock; + /** * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\LinkedProductSelectBuilderByIndexPrice */ @@ -85,6 +100,7 @@ protected function setUp() public function testBuild() { $productId = 10; + $storeId = 1; $metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -108,6 +124,6 @@ public function testBuild() $metadata->expects($this->once())->method('getLinkField')->willReturn('row_id'); $this->resourceMock->expects($this->any())->method('getTableName'); $this->baseSelectProcessorMock->expects($this->once())->method('process')->willReturnSelf(); - $this->model->build($productId); + $this->model->build($productId, $storeId); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/Image/ContextTest.php b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/Image/ContextTest.php deleted file mode 100644 index e73a2f30e2b10..0000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/Image/ContextTest.php +++ /dev/null @@ -1,76 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Test\Unit\Model\View\Asset\Image; - -use Magento\Catalog\Model\Product\Media\ConfigInterface; -use Magento\Catalog\Model\View\Asset\Image\Context; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Filesystem; -use Magento\Framework\Filesystem\Directory\WriteInterface; - -/** - * Class ContextTest - */ -class ContextTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var Context - */ - protected $model; - - /** - * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $mediaDirectory; - - /** - * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $mediaConfig; - - /** - * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filesystem; - - protected function setUp() - { - $this->mediaConfig = $this->getMockBuilder(ConfigInterface::class)->getMockForAbstractClass(); - $this->mediaConfig->expects($this->any())->method('getBaseMediaPath')->willReturn('catalog/product'); - $this->mediaDirectory = $this->getMockBuilder(WriteInterface::class)->getMockForAbstractClass(); - $this->mediaDirectory->expects($this->once())->method('create')->with('catalog/product'); - $this->filesystem = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - $this->filesystem->expects($this->once()) - ->method('getDirectoryWrite') - ->with(DirectoryList::MEDIA) - ->willReturn($this->mediaDirectory); - $this->model = new Context( - $this->mediaConfig, - $this->filesystem - ); - } - - public function testGetPath() - { - $path = '/var/www/html/magento2ce/pub/media/catalog/product'; - $this->mediaDirectory->expects($this->once()) - ->method('getAbsolutePath') - ->with('catalog/product') - ->willReturn($path); - - $this->assertEquals($path, $this->model->getPath()); - } - - public function testGetUrl() - { - $baseUrl = 'http://localhost/pub/media/catalog/product'; - $this->mediaConfig->expects($this->once())->method('getBaseMediaUrl')->willReturn($baseUrl); - - $this->assertEquals($baseUrl, $this->model->getBaseUrl()); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php deleted file mode 100644 index 6832d5b3399d7..0000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php +++ /dev/null @@ -1,213 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Test\Unit\Model\View\Asset; - -use Magento\Catalog\Model\Product\Media\ConfigInterface; -use Magento\Catalog\Model\View\Asset\Image; -use Magento\Framework\Encryption\Encryptor; -use Magento\Framework\Encryption\EncryptorInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Framework\View\Asset\ContextInterface; -use Magento\Framework\View\Asset\Repository; - -/** - * Class ImageTest - */ -class ImageTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Catalog\Model\View\Asset\Image - */ - protected $model; - - /** - * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $mediaConfig; - - /** - * @var EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $encryptor; - - /** - * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $context; - - /** - * @var Repository|\PHPUnit_Framework_MockObject_MockObject - */ - private $assetRepo; - - private $objectManager; - - protected function setUp() - { - $this->mediaConfig = $this->createMock(ConfigInterface::class); - $this->encryptor = $this->createMock(EncryptorInterface::class); - $this->context = $this->createMock(ContextInterface::class); - $this->assetRepo = $this->createMock(Repository::class); - $this->objectManager = new ObjectManager($this); - $this->model = $this->objectManager->getObject( - Image::class, - [ - 'mediaConfig' => $this->mediaConfig, - 'imageContext' => $this->context, - 'encryptor' => $this->encryptor, - 'filePath' => '/somefile.png', - 'assetRepo' => $this->assetRepo, - 'miscParams' => [ - 'image_width' => 100, - 'image_height' => 50, - 'constrain_only' => false, - 'keep_aspect_ratio' => false, - 'keep_frame' => true, - 'keep_transparency' => false, - 'background' => '255,255,255', - 'image_type' => 'image', //thumbnail,small_image,image,swatch_image,swatch_thumb - 'quality' => 80, - 'angle' => null - ] - ] - ); - } - - public function testModuleAndContentAndContentType() - { - $contentType = 'image'; - $this->assertEquals($contentType, $this->model->getContentType()); - $this->assertEquals($contentType, $this->model->getSourceContentType()); - $this->assertNull($this->model->getContent()); - $this->assertEquals('cache', $this->model->getModule()); - } - - public function testGetFilePath() - { - $this->assertEquals('/somefile.png', $this->model->getFilePath()); - } - - public function testGetSoureFile() - { - $this->mediaConfig->expects($this->once())->method('getBaseMediaPath')->willReturn('catalog/product'); - $this->assertEquals('catalog/product/somefile.png', $this->model->getSourceFile()); - } - - public function testGetContext() - { - $this->assertInstanceOf(ContextInterface::class, $this->model->getContext()); - } - - /** - * @param string $filePath - * @param array $miscParams - * @param string $readableParams - * @dataProvider getPathDataProvider - */ - public function testGetPath($filePath, $miscParams, $readableParams) - { - $imageModel = $this->objectManager->getObject( - Image::class, - [ - 'mediaConfig' => $this->mediaConfig, - 'context' => $this->context, - 'encryptor' => $this->encryptor, - 'filePath' => $filePath, - 'assetRepo' => $this->assetRepo, - 'miscParams' => $miscParams - ] - ); - $absolutePath = '/var/www/html/magento2ce/pub/media/catalog/product'; - $hashPath = 'somehash'; - $this->context->method('getPath')->willReturn($absolutePath); - $this->encryptor->expects(static::once()) - ->method('hash') - ->with($readableParams, $this->anything()) - ->willReturn($hashPath); - static::assertEquals( - $absolutePath . '/cache/'. $hashPath . $filePath, - $imageModel->getPath() - ); - } - - /** - * @param string $filePath - * @param array $miscParams - * @param string $readableParams - * @dataProvider getPathDataProvider - */ - public function testGetUrl($filePath, $miscParams, $readableParams) - { - $imageModel = $this->objectManager->getObject( - Image::class, - [ - 'mediaConfig' => $this->mediaConfig, - 'context' => $this->context, - 'encryptor' => $this->encryptor, - 'filePath' => $filePath, - 'assetRepo' => $this->assetRepo, - 'miscParams' => $miscParams - ] - ); - $absolutePath = 'http://localhost/pub/media/catalog/product'; - $hashPath = 'somehash'; - $this->context->expects(static::once())->method('getBaseUrl')->willReturn($absolutePath); - $this->encryptor->expects(static::once()) - ->method('hash') - ->with($readableParams, $this->anything()) - ->willReturn($hashPath); - static::assertEquals( - $absolutePath . '/cache/' . $hashPath . $filePath, - $imageModel->getUrl() - ); - } - - /** - * @return array - */ - public function getPathDataProvider() - { - return [ - [ - '/some_file.png', - [], //default value for miscParams, - 'h:empty_w:empty_q:empty_r:empty_nonproportional_noframe_notransparency_notconstrainonly_nobackground', - ], - [ - '/some_file_2.png', - [ - 'image_type' => 'thumbnail', - 'image_height' => 75, - 'image_width' => 75, - 'keep_aspect_ratio' => true, - 'keep_frame' => true, - 'keep_transparency' => true, - 'constrain_only' => true, - 'background' => [233,1,0], - 'angle' => null, - 'quality' => 80, - ], - 'h:75_w:75_proportional_frame_transparency_doconstrainonly_rgb233,1,0_r:empty_q:80', - ], - [ - '/some_file_3.png', - [ - 'image_type' => 'thumbnail', - 'image_height' => 75, - 'image_width' => 75, - 'keep_aspect_ratio' => false, - 'keep_frame' => false, - 'keep_transparency' => false, - 'constrain_only' => false, - 'background' => [233,1,0], - 'angle' => 90, - 'quality' => 80, - ], - 'h:75_w:75_nonproportional_noframe_notransparency_notconstrainonly_rgb233,1,0_r:90_q:80', - ], - ]; - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php index 009cd690d4cd4..bd08a39fb2bed 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php @@ -99,9 +99,6 @@ public function testGet() ->method('create') ->willReturn($image); - $imageHelper->expects($this->once()) - ->method('getResizedImageInfo') - ->willReturn([11, 11]); $this->state->expects($this->once()) ->method('emulateAreaCode') ->with( @@ -111,12 +108,14 @@ public function testGet() ) ->willReturn($imageHelper); + $width = 5; + $height = 10; $imageHelper->expects($this->once()) ->method('getHeight') - ->willReturn(10); + ->willReturn($height); $imageHelper->expects($this->once()) ->method('getWidth') - ->willReturn(10); + ->willReturn($width); $imageHelper->expects($this->once()) ->method('getLabel') ->willReturn('Label'); @@ -132,10 +131,10 @@ public function testGet() ->with(); $image->expects($this->once()) ->method('setResizedHeight') - ->with(11); + ->with($height); $image->expects($this->once()) ->method('setResizedWidth') - ->with(11); + ->with($width); $productRenderInfoDto->expects($this->once()) ->method('setImages') diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/AttributeSetId.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/AttributeSetId.php new file mode 100644 index 0000000000000..5e9f7ba065be7 --- /dev/null +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/AttributeSetId.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Ui\Component\Listing\Columns; + +/** + * Attribute set listing column component + */ +class AttributeSetId extends \Magento\Ui\Component\Listing\Columns\Column +{ + /** + * @inheritDoc + */ + protected function applySorting() + { + $sorting = $this->getContext()->getRequestParam('sorting'); + $isSortable = $this->getData('config/sortable'); + if ($isSortable !== false + && !empty($sorting['field']) + && !empty($sorting['direction']) + && $sorting['field'] === $this->getName() + ) { + $collection = $this->getContext()->getDataProvider()->getCollection(); + $collection->joinField( + 'attribute_set', + 'eav_attribute_set', + 'attribute_set_name', + 'attribute_set_id=attribute_set_id', + null, + 'left' + ); + $collection->getSelect()->order('attribute_set_name ' . $sorting['direction']); + } + } +} diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php index 09c9782fc0e32..52773b4580256 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php @@ -9,7 +9,7 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; /** - * Class Thumbnail + * Column with thumbnail images * * @api * @since 100.0.2 @@ -20,6 +20,16 @@ class Thumbnail extends \Magento\Ui\Component\Listing\Columns\Column const ALT_FIELD = 'name'; + /** + * @var \Magento\Catalog\Helper\Image + */ + private $imageHelper; + + /** + * @var \Magento\Framework\UrlInterface + */ + private $urlBuilder; + /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php index 494b77724e5b7..c80b2663d1f69 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Catalog\Ui\Component\Listing\Columns; @@ -120,31 +119,32 @@ protected function applySorting() && !empty($sorting['direction']) && $sorting['field'] === $this->getName() ) { + /** @var \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection $collection */ $collection = $this->getContext()->getDataProvider()->getCollection(); - $collection - ->joinField( - 'websites_ids', - 'catalog_product_website', - 'website_id', - 'product_id=entity_id', - null, - 'left' - ) - ->joinTable( - 'store_website', - 'website_id = websites_ids', - ['name'], - null, - 'left' - ) - ->groupByAttribute('entity_id'); - $this->resourceHelper->addGroupConcatColumn( - $collection->getSelect(), - $this->websiteNames, - 'name' + + $select = $collection->getConnection()->select(); + $select->from( + ['cpw' => $collection->getTable('catalog_product_website')], + ['product_id'] + )->joinLeft( + ['sw' => $collection->getTable('store_website')], + 'cpw.website_id = sw.website_id', + [ + $this->websiteNames => new \Zend_Db_Expr( + 'GROUP_CONCAT(sw.name ORDER BY sw.website_id ASC SEPARATOR \',\')' + ) + ] + )->group( + 'cpw.product_id' ); - $collection->getSelect()->order($this->websiteNames . ' ' . $sorting['direction']); + $collection->getSelect()->joinLeft( + ['product_websites' => $select], + 'product_websites.product_id = e.entity_id', + [$this->websiteNames] + )->order( + 'product_websites.' . $this->websiteNames . ' ' . $sorting['direction'] + ); } } } 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..b822a5e3ef88a 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 + * Related products modifier * * @api * @@ -143,7 +143,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -182,7 +182,7 @@ public function modifyMeta(array $meta) } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyData(array $data) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php index d8f76c40e8fad..45383ed51f6fc 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php @@ -118,18 +118,14 @@ public function collect(ProductInterface $product, ProductRenderInterface $produ [$product, $imageCode, (int) $productRender->getStoreId(), $image] ); - try { - $resizedInfo = $helper->getResizedImageInfo(); - } catch (NotLoadInfoImageException $exception) { - $resizedInfo = [$helper->getWidth(), $helper->getHeight()]; - } - $image->setCode($imageCode); - $image->setHeight($helper->getHeight()); - $image->setWidth($helper->getWidth()); + $height = $helper->getHeight(); + $image->setHeight($height); + $width = $helper->getWidth(); + $image->setWidth($width); $image->setLabel($helper->getLabel()); - $image->setResizedHeight($resizedInfo[1]); - $image->setResizedWidth($resizedInfo[0]); + $image->setResizedHeight($height); + $image->setResizedWidth($width); $images[] = $image; } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php index 4de0b94d06801..3289e4806df3a 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php @@ -8,12 +8,14 @@ use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\SearchCriteriaBuilder; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\View\Element\UiComponent\DataProvider\Reporting; use Magento\Store\Model\StoreManager; /** * Provide information about current store and currency for product listing ui component + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider { @@ -22,6 +24,13 @@ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvi */ private $storeManager; + /** + * Core store config + * + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param string $name * @param Reporting $reporting @@ -56,6 +65,7 @@ public function __construct( $this->name = $name; $this->storeManager = $storeManager; + $this->scopeConfig = $data['config']['scopeConfig']; } /** @@ -65,9 +75,13 @@ public function getData() { $data = []; $store = $this->storeManager->getStore(); + $productsScope = $this->scopeConfig->getValue( + 'catalog/recently_products/scope', + \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE + ); $data['store'] = $store->getId(); $data['currency'] = $store->getCurrentCurrency()->getCode(); - + $data['productCurrentScope'] = $productsScope; return $data; } } diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index c80363038ac60..f59990cdcea96 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -20,30 +20,30 @@ <resource>Magento_Catalog::config_catalog</resource> <group id="fields_masks" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Product Fields Auto-Generation</label> - <field id="sku" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="sku" translate="label comment" type="text" sortOrder="10" showInDefault="1" canRestore="1"> <label>Mask for SKU</label> <comment>Use {{name}} as Product Name placeholder</comment> </field> - <field id="meta_title" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="meta_title" translate="label comment" type="text" sortOrder="20" showInDefault="1" canRestore="1"> <label>Mask for Meta Title</label> <comment>Use {{name}} as Product Name placeholder</comment> </field> - <field id="meta_keyword" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="meta_keyword" translate="label comment" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>Mask for Meta Keywords</label> <comment>Use {{name}} as Product Name or {{sku}} as Product SKU placeholders</comment> </field> - <field id="meta_description" translate="label comment" type="text" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="meta_description" translate="label comment" type="text" sortOrder="40" showInDefault="1" canRestore="1"> <label>Mask for Meta Description</label> <comment>Use {{name}} and {{description}} as Product Name and Product Description placeholders</comment> </field> </group> - <group id="recently_products" translate="label" type="text" sortOrder="350" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="recently_products" translate="label" type="text" sortOrder="350" showInDefault="1" showInWebsite="1"> <label>Recently Viewed/Compared Products</label> - <field id="recently_viewed_lifetime" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="recently_viewed_lifetime" translate="label" type="text" sortOrder="40" showInDefault="1" canRestore="1"> <label>Lifetime of products in Recently Viewed Widget</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="recently_compared_lifetime" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="recently_compared_lifetime" translate="label" type="text" sortOrder="40" showInDefault="1" canRestore="1"> <label>Lifetime of products in Recently Compared Widget</label> <validate>validate-number validate-zero-or-greater</validate> </field> @@ -78,12 +78,12 @@ <comment>Must be in the allowed values list.</comment> <validate>validate-per-page-value</validate> </field> - <field id="flat_catalog_category" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="flat_catalog_category" translate="label" type="select" sortOrder="100" showInDefault="1" canRestore="1"> <label>Use Flat Catalog Category</label> <backend_model>Magento\Catalog\Model\Indexer\Category\Flat\System\Config\Mode</backend_model> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="flat_catalog_product" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="flat_catalog_product" translate="label" type="select" sortOrder="100" showInDefault="1" canRestore="1"> <label>Use Flat Catalog Product</label> <backend_model>Magento\Catalog\Model\Indexer\Product\Flat\System\Config\Mode</backend_model> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -93,12 +93,12 @@ <comment>Applies to category pages.</comment> <source_model>Magento\Catalog\Model\Config\Source\ListSort</source_model> </field> - <field id="list_allow_all" translate="label comment" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="list_allow_all" translate="label comment" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Allow All Products per Page</label> <comment>Whether to show "All" option in the "Show X Per Page" dropdown.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="remember_pagination" translate="label comment" type="select" sortOrder="7" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="remember_pagination" translate="label comment" type="select" sortOrder="7" showInDefault="1" canRestore="1"> <label>Remember Category Pagination</label> <comment>Changing may affect SEO and cache storage consumption.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -128,9 +128,9 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> - <group id="price" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="price" translate="label" type="text" sortOrder="400" showInDefault="1"> <label>Price</label> - <field id="scope" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="scope" translate="label comment" type="select" sortOrder="1" showInDefault="1"> <label>Catalog Price Scope</label> <comment><![CDATA[This defines the base currency scope ("Currency Setup" > "Currency Options" > "Base Currency").]]></comment> <backend_model>Magento\Catalog\Model\Indexer\Product\Price\System\Config\PriceScope</backend_model> @@ -169,10 +169,13 @@ </section> <section id="cms"> <group id="wysiwyg"> - <field id="use_static_urls_in_catalog" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="use_static_urls_in_catalog" translate="label comment" type="select" sortOrder="10" showInDefault="1"> <label>Use Static URLs for Media Content in WYSIWYG</label> <comment>Media content will be inserted into the editor as a static URL. Media content is not updated if the system configuration base URL changes.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="enabled" negative="1">disabled</field> + </depends> </field> </group> </section> @@ -205,6 +208,13 @@ <source_model>Magento\Catalog\Model\Config\Source\LayoutList</source_model> </field> </group> + <group id="url"> + <field id="catalog_media_url_format" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <label>Catalog media URL format</label> + <source_model>Magento\Catalog\Model\Config\Source\Web\CatalogMediaUrlFormat</source_model> + <comment><![CDATA[Images should be optimized based on query parameters by your CDN or web server. Use the legacy mode for backward compatibility. <a href="https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options">Learn more</a> about catalog URL formats.<br/><br/><strong style="color:red">Warning!</strong> If you switch back to legacy mode, you must <a href="https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/themes/theme-images.html#resize-catalog-images">use the CLI to regenerate images</a>.]]></comment> + </field> + </group> </section> <section id="system" translate="label" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> <class>separator-top</class> @@ -213,7 +223,7 @@ <resource>Magento_Config::config_system</resource> <group id="upload_configuration" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Images Upload Configuration</label> - <field id="jpeg_quality" translate="label comment" type="text" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="jpeg_quality" translate="label comment" type="text" sortOrder="100" showInDefault="1" canRestore="1"> <label>Quality</label> <validate>validate-digits validate-digits-range digits-range-1-100 required-entry</validate> <comment>Jpeg quality for resized images 1-100%.</comment> diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 8506d2ae03032..68289904db0cf 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -28,7 +28,9 @@ <grid_per_page>12</grid_per_page> <list_per_page>10</list_per_page> <flat_catalog_category>0</flat_catalog_category> + <flat_catalog_product>0</flat_catalog_product> <default_sort_by>position</default_sort_by> + <list_allow_all>0</list_allow_all> <parse_url_directives>1</parse_url_directives> <remember_pagination>0</remember_pagination> </frontend> @@ -78,6 +80,11 @@ <thumbnail_position>stretch</thumbnail_position> </watermark> </design> + <web> + <url> + <catalog_media_url_format>hash</catalog_media_url_format> + </url> + </web> <general> <validator_data> <input_types> diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml index d2d6f098125ce..88bb578712056 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml @@ -144,7 +144,7 @@ <label translate="true">Type</label> </settings> </column> - <column name="attribute_set_id" component="Magento_Ui/js/grid/columns/select" sortOrder="50"> + <column name="attribute_set_id" class="Magento\Catalog\Ui\Component\Listing\Columns\AttributeSetId" component="Magento_Ui/js/grid/columns/select" sortOrder="50"> <settings> <options class="Magento\Catalog\Model\Product\AttributeSet\Options"/> <filter>select</filter> diff --git a/app/code/Magento/Catalog/view/base/web/template/product/list/listing.html b/app/code/Magento/Catalog/view/base/web/template/product/list/listing.html index c80f591bd6590..ea0124f1c7c4d 100644 --- a/app/code/Magento/Catalog/view/base/web/template/product/list/listing.html +++ b/app/code/Magento/Catalog/view/base/web/template/product/list/listing.html @@ -25,15 +25,15 @@ <render args="$col.getBody()"/> </fastForEach> - <div if="getRegion('action-primary-area')().length || getRegion('action-secondary-area')().length" + <div if="regionHasElements('action-primary-area') || regionHasElements('action-secondary-area')" class="product-item-actions"> - <div class="actions-primary" if="getRegion('action-primary-area')().length"> + <div class="actions-primary" if="regionHasElements('action-primary-area')"> <fastForEach args="data: getRegion('action-primary-area'), as: '$col'" > <render args="$col.getBody()"/> </fastForEach> </div> - <div if="getRegion('action-secondary-area')().length" + <div if="regionHasElements('action-secondary-area')" class="actions-secondary" data-role="add-to-links"> <fastForEach args="data: getRegion('action-secondary-area'), as: '$col'" > @@ -42,7 +42,7 @@ </div> </div> - <div if="getRegion('description-area')().length" + <div if="regionHasElements('description-area')" class="product-item-description"> <fastForEach args="data: getRegion('description-area'), as: '$col'" > <render args="$col.getBody()"/> @@ -54,4 +54,4 @@ </ol> </div> </div> -</div> \ No newline at end of file +</div> diff --git a/app/code/Magento/Catalog/view/frontend/layout/default.xml b/app/code/Magento/Catalog/view/frontend/layout/default.xml index 3dff3e9b3c1f8..8f414724f51db 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/default.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/default.xml @@ -26,6 +26,9 @@ <item name="updateRequestConfig" xsi:type="array"> <item name="url" xsi:type="serviceUrl" path="/products-render-info"/> </item> + <item name="requestConfig" xsi:type="array"> + <item name="syncUrl" xsi:type="url" path="catalog/product/frontend_action_synchronize"/> + </item> </item> </argument> </arguments> diff --git a/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_compared.xml b/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_compared.xml index 7cced8bb613c3..b0a275f720670 100644 --- a/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_compared.xml +++ b/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_compared.xml @@ -32,6 +32,7 @@ <item name="namespace" xsi:type="string">recently_compared_product</item> <item name="provider" xsi:type="string">compare-products</item> </item> + <item name="scopeConfig" xsi:type="object">Magento\Framework\App\Config\ScopeConfigInterface</item> </item> </argument> </argument> diff --git a/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_viewed.xml b/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_viewed.xml index efad08eef8c12..2af3b1210b18b 100644 --- a/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_viewed.xml +++ b/app/code/Magento/Catalog/view/frontend/ui_component/widget_recently_viewed.xml @@ -31,6 +31,7 @@ <item name="identifiersConfig" xsi:type="array"> <item name="namespace" xsi:type="string">recently_viewed_product</item> </item> + <item name="scopeConfig" xsi:type="object">Magento\Framework\App\Config\ScopeConfigInterface</item> </item> </argument> </argument> diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js b/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js index ca9381c45e2ab..f246a8e3a0f9f 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/provider.js @@ -138,13 +138,17 @@ define([ filterIds: function (ids) { var _ids = {}, currentTime = new Date().getTime() / 1000, - currentProductIds = productResolver($('#product_addtocart_form')); + currentProductIds = productResolver($('#product_addtocart_form')), + productCurrentScope = this.data.productCurrentScope, + scopeId = productCurrentScope === 'store' ? window.checkout.storeId : + productCurrentScope === 'group' ? window.checkout.storeGroupId : + window.checkout.websiteId; - _.each(ids, function (id) { + _.each(ids, function (id, key) { if ( - currentTime - id['added_at'] < ~~this.idsStorage.lifetime && - !_.contains(currentProductIds, id['product_id']) && - (!id.hasOwnProperty('website_id') || id['website_id'] === window.checkout.websiteId) + currentTime - ids[key]['added_at'] < ~~this.idsStorage.lifetime && + !_.contains(currentProductIds, ids[key]['product_id']) && + (!id.hasOwnProperty('scope_id') || ids[key]['scope_id'] === scopeId) ) { _ids[id['product_id']] = id; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js index a904d8ed3b3da..bd92c5d452423 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/ids-storage-compare.js @@ -67,14 +67,26 @@ define([ * @returns {Object} data */ prepareData: function (data) { - var result = {}; + var result = {}, + scopeId; _.each(data, function (item) { - result[item.id] = { - 'added_at': new Date().getTime() / 1000, - 'product_id': item.id, - 'website_id': window.checkout.websiteId - }; + if (typeof item.productScope !== 'undefined') { + scopeId = item.productScope === 'store' ? window.checkout.storeId : + item.productScope === 'group' ? window.checkout.storeGroupId : + window.checkout.websiteId; + + result[item.productScope + '-' + scopeId + '-' + item.id] = { + 'added_at': new Date().getTime() / 1000, + 'product_id': item.id, + 'scope_id': scopeId + }; + } else { + result[item.id] = { + 'added_at': new Date().getTime() / 1000, + 'product_id': item.id + }; + } }); return result; diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js b/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js index f4ce882dd668b..5bcf57c035929 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/view/provider.js @@ -85,13 +85,17 @@ define([ * @returns {Object} */ getIdentifiers: function () { - var result = {}; + var result = {}, + productCurrentScope = this.data.productCurrentScope, + scopeId = productCurrentScope === 'store' ? window.checkout.storeId : + productCurrentScope === 'group' ? window.checkout.storeGroupId : + window.checkout.websiteId; _.each(this.data.items, function (item, key) { - result[key] = { + result[productCurrentScope + '-' + scopeId + '-' + key] = { 'added_at': new Date().getTime() / 1000, 'product_id': key, - 'website_id': window.checkout.websiteId + 'scope_id': scopeId }; }, this); diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index 0e92bbbab4259..53d3b624285e9 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -129,7 +129,7 @@ private function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bo ? $this->visibility->getVisibleInSearchIds() : $this->visibility->getVisibleInCatalogIds(); - $this->addFilter($searchCriteria, 'visibility', $visibilityIds); + $this->addFilter($searchCriteria, 'visibility', $visibilityIds, 'in'); } /** @@ -155,13 +155,20 @@ private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria * @param SearchCriteriaInterface $searchCriteria * @param string $field * @param mixed $value + * @param string|null $condition */ - private function addFilter(SearchCriteriaInterface $searchCriteria, string $field, $value): void - { + private function addFilter( + SearchCriteriaInterface $searchCriteria, + string $field, + $value, + ?string $condition = null + ): void { $filter = $this->filterBuilder ->setField($field) ->setValue($value) + ->setConditionType($condition) ->create(); + $this->filterGroupBuilder->addFilter($filter); $filterGroups = $searchCriteria->getFilterGroups(); $filterGroups[] = $this->filterGroupBuilder->create(); diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php index abc5ae7e1da7f..85b86f313de4d 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php @@ -7,16 +7,12 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Category; -use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder; -use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search; -use Magento\Framework\App\ObjectManager; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; -use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Filter; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\CatalogGraphQl\Model\Resolver\Products\Query\ProductQueryInterface; /** * Category products resolver, used by GraphQL endpoints to retrieve products assigned to a category @@ -24,24 +20,7 @@ class Products implements ResolverInterface { /** - * @var \Magento\Catalog\Api\ProductRepositoryInterface - */ - private $productRepository; - - /** - * @var Builder - * @deprecated - */ - private $searchCriteriaBuilder; - - /** - * @var Filter - * @deprecated - */ - private $filterQuery; - - /** - * @var Search + * @var ProductQueryInterface */ private $searchQuery; @@ -51,25 +30,15 @@ class Products implements ResolverInterface private $searchApiCriteriaBuilder; /** - * @param ProductRepositoryInterface $productRepository - * @param Builder $searchCriteriaBuilder - * @param Filter $filterQuery - * @param Search $searchQuery + * @param ProductQueryInterface $searchQuery * @param SearchCriteriaBuilder $searchApiCriteriaBuilder */ public function __construct( - ProductRepositoryInterface $productRepository, - Builder $searchCriteriaBuilder, - Filter $filterQuery, - Search $searchQuery = null, - SearchCriteriaBuilder $searchApiCriteriaBuilder = null + ProductQueryInterface $searchQuery, + SearchCriteriaBuilder $searchApiCriteriaBuilder ) { - $this->productRepository = $productRepository; - $this->searchCriteriaBuilder = $searchCriteriaBuilder; - $this->filterQuery = $filterQuery; - $this->searchQuery = $searchQuery ?? ObjectManager::getInstance()->get(Search::class); - $this->searchApiCriteriaBuilder = $searchApiCriteriaBuilder ?? - ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); + $this->searchQuery = $searchQuery; + $this->searchApiCriteriaBuilder = $searchApiCriteriaBuilder; } /** @@ -94,18 +63,17 @@ public function resolve( 'eq' => $value['id'] ] ]; - $searchCriteria = $this->searchApiCriteriaBuilder->build($args, false); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); + $searchResult = $this->searchQuery->getResult($args, $info); //possible division by 0 - if ($searchCriteria->getPageSize()) { - $maxPages = ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize()); + if ($searchResult->getPageSize()) { + $maxPages = ceil($searchResult->getTotalCount() / $searchResult->getPageSize()); } else { $maxPages = 0; } - $currentPage = $searchCriteria->getCurrentPage(); - if ($searchCriteria->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) { + $currentPage = $searchResult->getCurrentPage(); + if ($searchResult->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) { $currentPage = new GraphQlInputException( __( 'currentPage value %1 specified is greater than the number of pages available.', @@ -118,7 +86,7 @@ public function resolve( 'total_count' => $searchResult->getTotalCount(), 'items' => $searchResult->getProductsSearchResult(), 'page_info' => [ - 'page_size' => $searchCriteria->getPageSize(), + 'page_size' => $searchResult->getPageSize(), 'current_page' => $currentPage, 'total_pages' => $maxPages ] diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php index 9396b1f02b975..dbb52f2010930 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/PriceRange.php @@ -86,7 +86,7 @@ private function getMinimumProductPrice(SaleableInterface $product, StoreInterfa $priceProvider = $this->priceProviderPool->getProviderByProductType($product->getTypeId()); $regularPrice = $priceProvider->getMinimalRegularPrice($product)->getValue(); $finalPrice = $priceProvider->getMinimalFinalPrice($product)->getValue(); - $minPriceArray = $this->formatPrice($regularPrice, $finalPrice, $store); + $minPriceArray = $this->formatPrice((float) $regularPrice, (float) $finalPrice, $store); $minPriceArray['model'] = $product; return $minPriceArray; } @@ -103,7 +103,7 @@ private function getMaximumProductPrice(SaleableInterface $product, StoreInterfa $priceProvider = $this->priceProviderPool->getProviderByProductType($product->getTypeId()); $regularPrice = $priceProvider->getMaximalRegularPrice($product)->getValue(); $finalPrice = $priceProvider->getMaximalFinalPrice($product)->getValue(); - $maxPriceArray = $this->formatPrice($regularPrice, $finalPrice, $store); + $maxPriceArray = $this->formatPrice((float) $regularPrice, (float) $finalPrice, $store); $maxPriceArray['model'] = $product; return $maxPriceArray; } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 691f93e4148bc..e3d9ba2a9b3c6 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\CatalogGraphQl\Model\Resolver\Products\Query\ProductQueryInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Filter; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search; @@ -24,51 +25,24 @@ class Products implements ResolverInterface { /** - * @var Builder - * @deprecated - */ - private $searchCriteriaBuilder; - - /** - * @var Search + * @var ProductQueryInterface */ private $searchQuery; - /** - * @var Filter - * @deprecated - */ - private $filterQuery; - - /** - * @var SearchFilter - * @deprecated - */ - private $searchFilter; - /** * @var SearchCriteriaBuilder */ private $searchApiCriteriaBuilder; /** - * @param Builder $searchCriteriaBuilder - * @param Search $searchQuery - * @param Filter $filterQuery - * @param SearchFilter $searchFilter + * @param ProductQueryInterface $searchQuery * @param SearchCriteriaBuilder|null $searchApiCriteriaBuilder */ public function __construct( - Builder $searchCriteriaBuilder, - Search $searchQuery, - Filter $filterQuery, - SearchFilter $searchFilter, + ProductQueryInterface $searchQuery, SearchCriteriaBuilder $searchApiCriteriaBuilder = null ) { - $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->searchQuery = $searchQuery; - $this->filterQuery = $filterQuery; - $this->searchFilter = $searchFilter; $this->searchApiCriteriaBuilder = $searchApiCriteriaBuilder ?? \Magento\Framework\App\ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); } @@ -95,11 +69,7 @@ public function resolve( ); } - //get product children fields queried - $productFields = (array)$info->getFieldSelection(1); - $includeAggregations = isset($productFields['filters']) || isset($productFields['aggregations']); - $searchCriteria = $this->searchApiCriteriaBuilder->build($args, $includeAggregations); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info, $args); + $searchResult = $this->searchQuery->getResult($args, $info); if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) { throw new GraphQlInputException( diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php index cc25af44fdfbe..670eee9c4583e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php @@ -7,16 +7,23 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Catalog\Model\Layer\Resolver as LayerResolver; +use Magento\Catalog\Model\Product; use Magento\Framework\Api\SearchCriteriaInterface; -use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product; +use Magento\Framework\Exception\InputException; +use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder as SearchCriteriaBuilder; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product as ProductProvider; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory; +use Magento\Search\Model\Query; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; /** * Retrieve filtered product data based off given search criteria in a format that GraphQL can interpret. */ -class Filter +class Filter implements ProductQueryInterface { /** * @var SearchResultFactory @@ -24,12 +31,12 @@ class Filter private $searchResultFactory; /** - * @var \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product + * @var ProductProvider */ private $productDataProvider; /** - * @var \Magento\Catalog\Model\Layer\Resolver + * @var LayerResolver */ private $layerResolver; @@ -38,50 +45,154 @@ class Filter */ private $fieldSelection; + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param SearchResultFactory $searchResultFactory - * @param Product $productDataProvider - * @param \Magento\Catalog\Model\Layer\Resolver $layerResolver + * @param ProductProvider $productDataProvider + * @param LayerResolver $layerResolver * @param FieldSelection $fieldSelection + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param ScopeConfigInterface $scopeConfig */ public function __construct( SearchResultFactory $searchResultFactory, - Product $productDataProvider, - \Magento\Catalog\Model\Layer\Resolver $layerResolver, - FieldSelection $fieldSelection + ProductProvider $productDataProvider, + LayerResolver $layerResolver, + FieldSelection $fieldSelection, + SearchCriteriaBuilder $searchCriteriaBuilder, + ScopeConfigInterface $scopeConfig ) { $this->searchResultFactory = $searchResultFactory; $this->productDataProvider = $productDataProvider; $this->layerResolver = $layerResolver; $this->fieldSelection = $fieldSelection; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->scopeConfig = $scopeConfig; } /** * Filter catalog product data based off given search criteria * - * @param SearchCriteriaInterface $searchCriteria + * @param array $args * @param ResolveInfo $info - * @param bool $isSearch * @return SearchResult */ public function getResult( - SearchCriteriaInterface $searchCriteria, - ResolveInfo $info, - bool $isSearch = false + array $args, + ResolveInfo $info ): SearchResult { $fields = $this->fieldSelection->getProductsFieldSelection($info); - $products = $this->productDataProvider->getList($searchCriteria, $fields, $isSearch); + try { + $searchCriteria = $this->buildSearchCriteria($args, $info); + $searchResults = $this->productDataProvider->getList($searchCriteria, $fields); + } catch (InputException $e) { + return $this->createEmptyResult($args); + } + $productArray = []; - /** @var \Magento\Catalog\Model\Product $product */ - foreach ($products->getItems() as $product) { + /** @var Product $product */ + foreach ($searchResults->getItems() as $product) { $productArray[$product->getId()] = $product->getData(); $productArray[$product->getId()]['model'] = $product; } + //possible division by 0 + if ($searchCriteria->getPageSize()) { + $maxPages = (int)ceil($searchResults->getTotalCount() / $searchCriteria->getPageSize()); + } else { + $maxPages = 0; + } + + return $this->searchResultFactory->create( + [ + 'totalCount' => $searchResults->getTotalCount(), + 'productsSearchResult' => $productArray, + 'pageSize' => $searchCriteria->getPageSize(), + 'currentPage' => $searchCriteria->getCurrentPage(), + 'totalPages' => $maxPages, + ] + ); + } + + /** + * Build search criteria from query input args + * + * @param array $args + * @param ResolveInfo $info + * @return SearchCriteriaInterface + */ + private function buildSearchCriteria(array $args, ResolveInfo $info): SearchCriteriaInterface + { + if (!empty($args['filter'])) { + $args['filter'] = $this->formatFilters($args['filter']); + } + + $criteria = $this->searchCriteriaBuilder->build($info->fieldName, $args); + $criteria->setCurrentPage($args['currentPage']); + $criteria->setPageSize($args['pageSize']); + + return $criteria; + } + + /** + * Reformat filters + * + * @param array $filters + * @return array + * @throws InputException + */ + private function formatFilters(array $filters): array + { + $formattedFilters = []; + $minimumQueryLength = $this->scopeConfig->getValue( + Query::XML_PATH_MIN_QUERY_LENGTH, + ScopeInterface::SCOPE_STORE + ); + + foreach ($filters as $field => $filter) { + foreach ($filter as $condition => $value) { + if ($condition === 'match') { + // reformat 'match' filter so MySQL filtering behaves like SearchAPI filtering + $condition = 'like'; + $value = str_replace('%', '', trim($value)); + if (strlen($value) < $minimumQueryLength) { + throw new InputException(__('Invalid match filter')); + } + $value = '%' . preg_replace('/ +/', '%', $value) . '%'; + } + $formattedFilters[$field] = [$condition => $value]; + } + } + + return $formattedFilters; + } + + /** + * Return and empty SearchResult object + * + * Used for handling exceptions gracefully + * + * @param array $args + * @return SearchResult + */ + private function createEmptyResult(array $args): SearchResult + { return $this->searchResultFactory->create( [ - 'totalCount' => $products->getTotalCount(), - 'productsSearchResult' => $productArray + 'totalCount' => 0, + 'productsSearchResult' => [], + 'pageSize' => $args['pageSize'], + 'currentPage' => $args['currentPage'], + 'totalPages' => 0, ] ); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/ProductQueryInterface.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/ProductQueryInterface.php new file mode 100644 index 0000000000000..580af5d87be26 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/ProductQueryInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query; + +use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Search for products by criteria + */ +interface ProductQueryInterface +{ + /** + * Get product search result + * + * @param array $args + * @param ResolveInfo $info + * @return SearchResult + */ + public function getResult(array $args, ResolveInfo $info): SearchResult; +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index ef83cc6132ecc..8377cd9baa5b4 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query; +use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\Api\Search\SearchCriteriaInterface; @@ -14,11 +15,12 @@ use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory; use Magento\Search\Api\SearchInterface; use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory; +use Magento\Search\Model\Search\PageSizeProvider; /** * Full text search for catalog using given search criteria. */ -class Search +class Search implements ProductQueryInterface { /** * @var SearchInterface @@ -31,7 +33,7 @@ class Search private $searchResultFactory; /** - * @var \Magento\Search\Model\Search\PageSizeProvider + * @var PageSizeProvider */ private $pageSizeProvider; @@ -50,21 +52,28 @@ class Search */ private $productsProvider; + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + /** * @param SearchInterface $search * @param SearchResultFactory $searchResultFactory - * @param \Magento\Search\Model\Search\PageSizeProvider $pageSize + * @param PageSizeProvider $pageSize * @param SearchCriteriaInterfaceFactory $searchCriteriaFactory * @param FieldSelection $fieldSelection * @param ProductSearch $productsProvider + * @param SearchCriteriaBuilder $searchCriteriaBuilder */ public function __construct( SearchInterface $search, SearchResultFactory $searchResultFactory, - \Magento\Search\Model\Search\PageSizeProvider $pageSize, + PageSizeProvider $pageSize, SearchCriteriaInterfaceFactory $searchCriteriaFactory, FieldSelection $fieldSelection, - ProductSearch $productsProvider + ProductSearch $productsProvider, + SearchCriteriaBuilder $searchCriteriaBuilder ) { $this->search = $search; $this->searchResultFactory = $searchResultFactory; @@ -72,21 +81,23 @@ public function __construct( $this->searchCriteriaFactory = $searchCriteriaFactory; $this->fieldSelection = $fieldSelection; $this->productsProvider = $productsProvider; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; } /** - * Return results of full text catalog search of given term, and will return filtered results if filter is specified + * Return product search results using Search API * - * @param SearchCriteriaInterface $searchCriteria + * @param array $args * @param ResolveInfo $info * @return SearchResult * @throws \Exception */ public function getResult( - SearchCriteriaInterface $searchCriteria, + array $args, ResolveInfo $info ): SearchResult { $queryFields = $this->fieldSelection->getProductsFieldSelection($info); + $searchCriteria = $this->buildSearchCriteria($args, $info); $realPageSize = $searchCriteria->getPageSize(); $realCurrentPage = $searchCriteria->getCurrentPage(); @@ -131,4 +142,20 @@ public function getResult( ] ); } + + /** + * Build search criteria from query input args + * + * @param array $args + * @param ResolveInfo $info + * @return SearchCriteriaInterface + */ + private function buildSearchCriteria(array $args, ResolveInfo $info): SearchCriteriaInterface + { + $productFields = (array)$info->getFieldSelection(1); + $includeAggregations = isset($productFields['filters']) || isset($productFields['aggregations']); + $searchCriteria = $this->searchCriteriaBuilder->build($args, $includeAggregations); + + return $searchCriteria; + } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php index e3b3588166163..92888a2775e17 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php @@ -9,11 +9,9 @@ use Magento\Catalog\Model\CategoryFactory; use Magento\Catalog\Model\ResourceModel\Category as CategoryResourceModel; -use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Framework\Api\Filter; use Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor\CustomFilterInterface; use Magento\Framework\Data\Collection\AbstractDb; -use Magento\Framework\Exception\LocalizedException; /** * Category filter allows to filter products collection using 'category_id' filter from search criteria. @@ -50,22 +48,23 @@ public function __construct( * @param Filter $filter * @param AbstractDb $collection * @return bool Whether the filter is applied - * @throws LocalizedException */ public function apply(Filter $filter, AbstractDb $collection) { - $conditionType = $filter->getConditionType(); - - if ($conditionType !== 'eq') { - throw new LocalizedException(__("'category_id' only supports 'eq' condition type.")); + $categoryIds = $filter->getValue(); + if (!is_array($categoryIds)) { + $categoryIds = [$categoryIds]; } - $categoryId = $filter->getValue(); - /** @var Collection $collection */ - $category = $this->categoryFactory->create(); - $this->categoryResourceModel->load($category, $categoryId); - $collection->addCategoryFilter($category); + $categoryProducts = []; + foreach ($categoryIds as $categoryId) { + $category = $this->categoryFactory->create(); + $this->categoryResourceModel->load($category, $categoryId); + $categoryProducts[$categoryId] = $category->getProductCollection()->getAllIds(); + } + $categoryProductIds = array_unique(array_merge(...$categoryProducts)); + $collection->addIdFilter($categoryProductIds); return true; } } diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml index 1fe62fc442ecf..d6f75259e30d7 100644 --- a/app/code/Magento/CatalogGraphQl/etc/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/di.xml @@ -71,4 +71,6 @@ </type> <preference type="\Magento\CatalogGraphQl\Model\Resolver\Product\Price\Provider" for="\Magento\CatalogGraphQl\Model\Resolver\Product\Price\ProviderInterface"/> + + <preference type="Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search" for="Magento\CatalogGraphQl\Model\Resolver\Products\Query\ProductQueryInterface"/> </config> diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 5baa4b4274be5..530bf6b1a0057 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -5,12 +5,13 @@ */ namespace Magento\CatalogImportExport\Model\Export; +use Magento\Catalog\Model\Product as ProductEntity; use Magento\Catalog\Model\ResourceModel\Product\Option\Collection; +use Magento\CatalogImportExport\Model\Import\Product as ImportProduct; use Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor; +use Magento\Framework\App\ObjectManager; use Magento\ImportExport\Model\Import; -use \Magento\Store\Model\Store; -use \Magento\CatalogImportExport\Model\Import\Product as ImportProduct; -use Magento\Catalog\Model\Product as ProductEntity; +use Magento\Store\Model\Store; /** * Export entity product model @@ -21,6 +22,8 @@ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @SuppressWarnings(PHPMD.ExcessiveClassLength) + * @SuppressWarnings(PHPMD.TooManyMethods) * @since 100.0.2 */ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity @@ -348,6 +351,10 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity * @var string */ private $productEntityLinkField; + /** + * @var ProductFilterInterface + */ + private $filter; /** * Product constructor. @@ -369,6 +376,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity * @param ProductEntity\LinkTypeProvider $linkTypeProvider * @param RowCustomizerInterface $rowCustomizer * @param array $dateAttrCodes + * @param ProductFilterInterface $filter * @throws \Magento\Framework\Exception\LocalizedException */ public function __construct( @@ -388,7 +396,8 @@ public function __construct( \Magento\CatalogImportExport\Model\Export\Product\Type\Factory $_typeFactory, \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider, \Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer, - array $dateAttrCodes = [] + array $dateAttrCodes = [], + ?ProductFilterInterface $filter = null ) { $this->_entityCollectionFactory = $collectionFactory; $this->_exportConfig = $exportConfig; @@ -404,6 +413,7 @@ public function __construct( $this->_linkTypeProvider = $linkTypeProvider; $this->rowCustomizer = $rowCustomizer; $this->dateAttrCodes = array_merge($this->dateAttrCodes, $dateAttrCodes); + $this->filter = $filter ?? ObjectManager::getInstance()->get(ProductFilterInterface::class); parent::__construct($localeDate, $config, $resource, $storeManager); @@ -819,9 +829,11 @@ protected function getItemsPerPage() case 'g': $memoryLimit *= 1024; // fall-through intentional + // no break case 'm': $memoryLimit *= 1024; // fall-through intentional + // no break case 'k': $memoryLimit *= 1024; break; @@ -913,12 +925,7 @@ protected function _prepareEntityCollection(\Magento\Eav\Model\Entity\Collection $exportFilter = !empty($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]) ? $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP] : []; - if (isset($exportFilter['category_ids']) - && trim($exportFilter['category_ids']) - && $collection instanceof \Magento\Catalog\Model\ResourceModel\Product\Collection - ) { - $collection->addCategoriesFilter(['in' => explode(',', $exportFilter['category_ids'])]); - } + $collection = $this->filter->filter($collection, $exportFilter); return parent::_prepareEntityCollection($collection); } @@ -979,7 +986,6 @@ protected function loadCollection(): array $collection = $this->_getEntityCollection(); foreach (array_keys($this->_storeIdToCode) as $storeId) { $collection->setOrder('entity_id', 'asc'); - $this->_prepareEntityCollection($collection); $collection->setStoreId($storeId); $collection->load(); foreach ($collection as $itemId => $item) { diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product/CategoryFilter.php b/app/code/Magento/CatalogImportExport/Model/Export/Product/CategoryFilter.php new file mode 100644 index 0000000000000..2907135695b20 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product/CategoryFilter.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Export\Product; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\CatalogImportExport\Model\Export\ProductFilterInterface; + +/** + * Category filter for products export + */ +class CategoryFilter implements ProductFilterInterface +{ + private const NAME = 'category_ids'; + + /** + * @inheritDoc + */ + public function filter(Collection $collection, array $filters): Collection + { + $value = trim($filters[self::NAME] ?? ''); + if ($value) { + $collection->addCategoriesFilter(['in' => explode(',', $value)]); + } + return $collection; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product/Stock.php b/app/code/Magento/CatalogImportExport/Model/Export/Product/Stock.php new file mode 100644 index 0000000000000..3648487df02ec --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product/Stock.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Export\Product; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\CatalogInventory\Model\Configuration; +use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as StockItemResourceModel; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * Stock status collection filter + */ +class Stock +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** + * @var StockItemResourceModel + */ + private $stockItemResourceModel; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param StockItemResourceModel $stockItemResourceModel + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + StockItemResourceModel $stockItemResourceModel + ) { + $this->scopeConfig = $scopeConfig; + $this->stockItemResourceModel = $stockItemResourceModel; + } + + /** + * Filter provided collection to return only "in stock" products + * + * @param Collection $collection + * @return Collection + */ + public function addInStockFilterToCollection(Collection $collection): Collection + { + $manageStock = $this->scopeConfig->getValue( + Configuration::XML_PATH_MANAGE_STOCK, + ScopeInterface::SCOPE_STORE + ); + $cond = [ + '{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=1 AND {{table}}.is_in_stock=1', + '{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=0' + ]; + + if ($manageStock) { + $cond[] = '{{table}}.use_config_manage_stock = 1 AND {{table}}.is_in_stock=1'; + } else { + $cond[] = '{{table}}.use_config_manage_stock = 1'; + } + return $this->addFilterToCollection($collection, '(' . join(') OR (', $cond) . ')'); + } + + /** + * Filter provided collection to return only "out of stock" products + * + * @param Collection $collection + * @return Collection + */ + public function addOutOfStockFilterToCollection(Collection $collection): Collection + { + $manageStock = $this->scopeConfig->getValue( + Configuration::XML_PATH_MANAGE_STOCK, + ScopeInterface::SCOPE_STORE + ); + $cond = [ + '{{table}}.use_config_manage_stock = 0 AND {{table}}.manage_stock=1 AND {{table}}.is_in_stock=0', + ]; + + if ($manageStock) { + $cond[] = '{{table}}.use_config_manage_stock = 1 AND {{table}}.is_in_stock=0'; + } + return $this->addFilterToCollection($collection, '(' . join(') OR (', $cond) . ')'); + } + + /** + * Add stock status filter to the collection + * + * @param Collection $collection + * @param string $condition + * @return Collection + */ + private function addFilterToCollection(Collection $collection, string $condition): Collection + { + $condition = str_replace( + '{{table}}', + 'inventory_stock_item_filter', + '({{table}}.product_id=e.entity_id) AND (' . $condition . ')' + ); + $collection->getSelect() + ->joinInner( + ['inventory_stock_item_filter' => $this->stockItemResourceModel->getMainTable()], + $condition, + [] + ); + return $collection; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product/StockStatusFilter.php b/app/code/Magento/CatalogImportExport/Model/Export/Product/StockStatusFilter.php new file mode 100644 index 0000000000000..9a57e58669729 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product/StockStatusFilter.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Export\Product; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\CatalogImportExport\Model\Export\ProductFilterInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Stock status filter for products export + */ +class StockStatusFilter implements ProductFilterInterface +{ + private const NAME = 'quantity_and_stock_status'; + private const IN_STOCK = '1'; + private const OUT_OF_STOCK = '0'; + /** + * @var Stock + */ + private $stockHelper; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param Stock $stockHelper + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + Stock $stockHelper, + ScopeConfigInterface $scopeConfig + ) { + $this->stockHelper = $stockHelper; + $this->scopeConfig = $scopeConfig; + } + /** + * @inheritDoc + */ + public function filter(Collection $collection, array $filters): Collection + { + $value = $filters[self::NAME] ?? ''; + switch ($value) { + case self::IN_STOCK: + $this->stockHelper->addInStockFilterToCollection($collection); + break; + case self::OUT_OF_STOCK: + $this->stockHelper->addOutOfStockFilterToCollection($collection); + break; + } + return $collection; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Export/ProductFilterInterface.php b/app/code/Magento/CatalogImportExport/Model/Export/ProductFilterInterface.php new file mode 100644 index 0000000000000..30985f3dc8cd7 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Export/ProductFilterInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Export; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; + +interface ProductFilterInterface +{ + /** + * Filter provided product collection + * + * @param Collection $collection + * @param array $filters + * @return Collection + */ + public function filter(Collection $collection, array $filters): Collection; +} diff --git a/app/code/Magento/CatalogImportExport/Model/Export/ProductFilters.php b/app/code/Magento/CatalogImportExport/Model/Export/ProductFilters.php new file mode 100644 index 0000000000000..49e1be8496d30 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Export/ProductFilters.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Export; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; + +/** + * Product filters pool for export + */ +class ProductFilters implements ProductFilterInterface +{ + /** + * @var ProductFilterInterface[] + */ + private $filters; + /** + * @param ProductFilterInterface[] $filters + */ + public function __construct(array $filters = []) + { + $this->filters = $filters; + } + + /** + * @inheritDoc + */ + public function filter(Collection $collection, array $filters): Collection + { + foreach ($this->filters as $filter) { + $collection = $filter->filter($collection, $filters); + } + return $collection; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 7ebc397cbe650..ae5f0f5d79e2a 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -13,6 +13,8 @@ use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor; use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface; +use Magento\CatalogImportExport\Model\Import\Product\StatusProcessor; +use Magento\CatalogImportExport\Model\Import\Product\StockProcessor; use Magento\CatalogImportExport\Model\StockItemImporterInterface; use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\Framework\App\Filesystem\DirectoryList; @@ -746,6 +748,15 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $productRepository; + /** + * @var StatusProcessor + */ + private $statusProcessor; + /** + * @var StockProcessor + */ + private $stockProcessor; + /** * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData @@ -791,6 +802,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param StockItemImporterInterface|null $stockItemImporter * @param DateTimeFactory $dateTimeFactory * @param ProductRepositoryInterface|null $productRepository + * @param StatusProcessor|null $statusProcessor + * @param StockProcessor|null $stockProcessor * @throws LocalizedException * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -840,7 +853,9 @@ public function __construct( MediaGalleryProcessor $mediaProcessor = null, StockItemImporterInterface $stockItemImporter = null, DateTimeFactory $dateTimeFactory = null, - ProductRepositoryInterface $productRepository = null + ProductRepositoryInterface $productRepository = null, + StatusProcessor $statusProcessor = null, + StockProcessor $stockProcessor = null ) { $this->_eventManager = $eventManager; $this->stockRegistry = $stockRegistry; @@ -876,6 +891,10 @@ public function __construct( $this->mediaProcessor = $mediaProcessor ?: ObjectManager::getInstance()->get(MediaGalleryProcessor::class); $this->stockItemImporter = $stockItemImporter ?: ObjectManager::getInstance() ->get(StockItemImporterInterface::class); + $this->statusProcessor = $statusProcessor ?: ObjectManager::getInstance() + ->get(StatusProcessor::class); + $this->stockProcessor = $stockProcessor ?: ObjectManager::getInstance() + ->get(StockProcessor::class); parent::__construct( $jsonHelper, $importExportData, @@ -1290,12 +1309,18 @@ protected function _saveLinks() protected function _saveProductAttributes(array $attributesData) { $linkField = $this->getProductEntityLinkField(); + $statusAttributeId = (int) $this->retrieveAttributeByCode('status')->getId(); foreach ($attributesData as $tableName => $skuData) { + $linkIdBySkuForStatusChanged = []; $tableData = []; foreach ($skuData as $sku => $attributes) { $linkId = $this->_oldSku[strtolower($sku)][$linkField]; foreach ($attributes as $attributeId => $storeValues) { foreach ($storeValues as $storeId => $storeValue) { + if ($attributeId === $statusAttributeId) { + $this->statusProcessor->setStatus($sku, $storeId, $storeValue); + $linkIdBySkuForStatusChanged[strtolower($sku)] = $linkId; + } $tableData[] = [ $linkField => $linkId, 'attribute_id' => $attributeId, @@ -1305,6 +1330,9 @@ protected function _saveProductAttributes(array $attributesData) } } } + if ($linkIdBySkuForStatusChanged) { + $this->statusProcessor->loadOldStatus($linkIdBySkuForStatusChanged); + } $this->_connection->insertOnDuplicate($tableName, $tableData, ['value']); } @@ -2188,6 +2216,7 @@ protected function _saveStockItem() while ($bunch = $this->_dataSourceModel->getNextBunch()) { $stockData = []; $productIdsToReindex = []; + $stockChangedProductIds = []; // Format bunch to stock data rows foreach ($bunch as $rowNum => $rowData) { if (!$this->isRowAllowedToImport($rowData, $rowNum)) { @@ -2197,8 +2226,16 @@ protected function _saveStockItem() $row = []; $sku = $rowData[self::COL_SKU]; if ($this->skuProcessor->getNewSku($sku) !== null) { + $stockItem = $this->getRowExistingStockItem($rowData); + $existingStockItemData = $stockItem->getData(); $row = $this->formatStockDataForRow($rowData); $productIdsToReindex[] = $row['product_id']; + $storeId = $this->getRowStoreId($rowData); + if (!empty(array_diff_assoc($row, $existingStockItemData)) + || $this->statusProcessor->isStatusChanged($sku, $storeId) + ) { + $stockChangedProductIds[] = $row['product_id']; + } } if (!isset($stockData[$sku])) { @@ -2211,11 +2248,24 @@ protected function _saveStockItem() $this->stockItemImporter->import($stockData); } + $this->reindexStockStatus($stockChangedProductIds); $this->reindexProducts($productIdsToReindex); } return $this; } + /** + * Reindex stock status for provided product IDs + * + * @param array $productIds + */ + private function reindexStockStatus(array $productIds): void + { + if ($productIds) { + $this->stockProcessor->reindexList($productIds); + } + } + /** * Initiate product reindex by product ids * @@ -2471,7 +2521,7 @@ public function validateRow(array $rowData, $rowNum) $this->addRowError( ValidatorInterface::ERROR_DUPLICATE_URL_KEY, $rowNum, - $rowData[self::COL_NAME], + $urlKey, $message, $errorLevel ) @@ -3259,4 +3309,30 @@ private function composeLinkKey(int $productId, int $linkedId, int $linkTypeId) { return "{$productId}-{$linkedId}-{$linkTypeId}"; } + + /** + * Get row store ID + * + * @param array $rowData + * @return int + */ + private function getRowStoreId(array $rowData): int + { + return !empty($rowData[self::COL_STORE]) + ? (int) $this->getStoreIdByCode($rowData[self::COL_STORE]) + : Store::DEFAULT_STORE_ID; + } + + /** + * Get row stock item model + * + * @param array $rowData + * @return StockItemInterface + */ + private function getRowExistingStockItem(array $rowData): StockItemInterface + { + $productId = $this->skuProcessor->getNewSku($rowData[self::COL_SKU])['entity_id']; + $websiteId = $this->stockConfiguration->getDefaultScopeId(); + return $this->stockRegistry->getStockItem($productId, $websiteId); + } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index 4c716421b7ae6..e12fc726f1056 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -6,14 +6,14 @@ namespace Magento\CatalogImportExport\Model\Import\Product; -use Magento\CatalogImportExport\Model\Import\Product; -use Magento\Framework\App\ResourceConnection; -use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection as ProductOptionValueCollection; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\CollectionFactory as ProductOptionValueCollectionFactory; -use Magento\Store\Model\Store; +use Magento\CatalogImportExport\Model\Import\Product; +use Magento\Framework\App\ResourceConnection; use Magento\ImportExport\Model\Import; +use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; +use Magento\Store\Model\Store; /** * Entity class which provide possibility to import product custom options @@ -110,6 +110,13 @@ class Option extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity 'file' => ['sku', 'file_extension', 'image_size_x', 'image_size_y'], ]; + /** + * Invalid rows list + * + * @var array + */ + private $_invalidRows; + /** * Keep product id value for every row which will be imported * @@ -433,7 +440,7 @@ protected function _initMessageTemplates() self::ERROR_INVALID_TYPE, __( 'Value for \'type\' sub attribute in \'custom_options\' attribute contains incorrect value, acceptable values are: %1', - '\''.implode('\', \'', array_keys($this->_specificTypes)).'\'' + '\'' . implode('\', \'', array_keys($this->_specificTypes)) . '\'' ) ); $this->_productEntity->addMessageTemplate(self::ERROR_EMPTY_TITLE, __('Please enter a value for title.')); @@ -1251,7 +1258,9 @@ protected function _importData() $childCount = []; $optionsToRemove = []; foreach ($bunch as $rowNumber => $rowData) { - if (isset($optionId, $valueId) && empty($rowData[PRODUCT::COL_STORE_VIEW_CODE])) { + if (isset($optionId, $valueId) && + (empty($rowData[PRODUCT::COL_STORE_VIEW_CODE]) || empty($rowData['custom_options'])) + ) { $nextOptionId = $optionId; $nextValueId = $valueId; } @@ -1548,8 +1557,8 @@ protected function _collectOptionTitle(array $rowData, $prevOptionId, array &$ti if (!empty($rowData[self::COLUMN_TITLE])) { if (!isset($titles[$prevOptionId][$defaultStoreId])) { if (isset($this->lastOptionTitle[$prevOptionId])) { - $titles[$prevOptionId] = $this->lastOptionTitle[$prevOptionId]; - unset($this->lastOptionTitle); + $titles[$prevOptionId] = $this->lastOptionTitle[$prevOptionId]; + unset($this->lastOptionTitle); } else { $titles[$prevOptionId][$defaultStoreId] = $rowData[self::COLUMN_TITLE]; } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/StatusProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/StatusProcessor.php new file mode 100644 index 0000000000000..1c6d679848216 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/StatusProcessor.php @@ -0,0 +1,153 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Import\Product; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * Imported product status state manager + */ +class StatusProcessor +{ + private const ATTRIBUTE_CODE = 'status'; + /** + * @var array + */ + private $oldData; + /** + * @var array + */ + private $newData; + /** + * @var ResourceModelFactory + */ + private $resourceFactory; + /** + * @var ResourceConnection + */ + private $resourceConnection; + /** + * @var MetadataPool + */ + private $metadataPool; + /** + * @var string + */ + private $productEntityLinkField; + /** + * @var AbstractAttribute + */ + private $attribute; + + /** + * Initializes dependencies. + * + * @param MetadataPool $metadataPool + * @param ResourceModelFactory $resourceFactory + * @param ResourceConnection $resourceConnection + */ + public function __construct( + MetadataPool $metadataPool, + ResourceModelFactory $resourceFactory, + ResourceConnection $resourceConnection + ) { + $this->oldData = []; + $this->newData = []; + $this->resourceFactory = $resourceFactory; + $this->resourceConnection = $resourceConnection; + $this->metadataPool = $metadataPool; + } + + /** + * Check if status has changed for given (sku, storeId) + * + * @param string $sku + * @param int $storeId + * @return bool + */ + public function isStatusChanged(string $sku, int $storeId): bool + { + $sku = strtolower($sku); + if (!isset($this->newData[$sku][$storeId])) { + $changed = false; + } elseif (!isset($this->oldData[$sku][$storeId])) { + $changed = true; + } else { + $oldStatus = (int) $this->oldData[$sku][$storeId]; + $newStatus = (int) $this->newData[$sku][$storeId]; + $changed = $oldStatus !== $newStatus; + } + return $changed; + } + + /** + * Load old status data + * + * @param array $linkIdBySku + */ + public function loadOldStatus(array $linkIdBySku): void + { + $connection = $this->resourceConnection->getConnection(); + $linkId = $this->getProductEntityLinkField(); + $select = $connection->select() + ->from($this->getAttribute()->getBackend()->getTable()) + ->columns([$linkId, 'store_id', 'value']) + ->where(sprintf('%s IN (?)', $linkId), array_values($linkIdBySku)); + $skuByLinkId = array_flip($linkIdBySku); + + foreach ($connection->fetchAll($select) as $item) { + if (isset($skuByLinkId[$item[$linkId]])) { + $this->oldData[$skuByLinkId[$item[$linkId]]][$item['store_id']] = $item['value']; + } + } + } + + /** + * Set SKU status for given storeId + * + * @param string $sku + * @param string $storeId + * @param int $value + */ + public function setStatus(string $sku, string $storeId, int $value): void + { + $sku = strtolower($sku); + $this->newData[$sku][$storeId] = $value; + } + + /** + * Get product entity link field. + * + * @return string + */ + private function getProductEntityLinkField() + { + if (!$this->productEntityLinkField) { + $this->productEntityLinkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + } + + return $this->productEntityLinkField; + } + + /** + * Get Attribute model + * + * @return AbstractAttribute + */ + private function getAttribute(): AbstractAttribute + { + if ($this->attribute === null) { + $this->attribute = $this->resourceFactory->create()->getAttribute(self::ATTRIBUTE_CODE); + } + return $this->attribute; + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/StockProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/StockProcessor.php new file mode 100644 index 0000000000000..76508d1ebec89 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/StockProcessor.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogImportExport\Model\Import\Product; + +use Magento\Framework\Indexer\IndexerRegistry; + +/** + * Imported product stock manager + */ +class StockProcessor +{ + /** + * @var IndexerRegistry + */ + private $indexerRegistry; + /** + * @var array + */ + private $indexers; + + /** + * Initializes dependencies. + * + * @param IndexerRegistry $indexerRegistry + * @param array $indexers + */ + public function __construct( + IndexerRegistry $indexerRegistry, + array $indexers = [] + ) { + $this->indexerRegistry = $indexerRegistry; + $this->indexers = array_filter($indexers); + } + + /** + * Reindex products by ids + * + * @param array $ids + * @return void + */ + public function reindexList(array $ids = []): void + { + if ($ids) { + foreach ($this->indexers as $indexerName) { + $indexer = $this->indexerRegistry->get($indexerName); + if (!$indexer->isScheduled()) { + $indexer->reindexList($ids); + } + } + } + } +} diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php index 40041fe90db96..ff724ddc746aa 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php @@ -13,8 +13,8 @@ use PHPUnit\Framework\MockObject\MockObject; /** - * Class ProductTest - * @package Magento\CatalogImportExport\Test\Unit\Model\Import + * Test import entity product model + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -547,6 +547,17 @@ public function testSaveProductAttributes() $this->_connection->expects($this->once()) ->method('insertOnDuplicate') ->with($testTable, $tableData, ['value']); + $attribute = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMockForAbstractClass(); + $attribute->expects($this->once())->method('getId')->willReturn(1); + $resource = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModel::class) + ->disableOriginalConstructor() + ->setMethods(['getAttribute']) + ->getMock(); + $resource->expects($this->once())->method('getAttribute')->willReturn($attribute); + $this->_resourceFactory->expects($this->once())->method('create')->willReturn($resource); $this->setPropertyValue($this->importProduct, '_oldSku', [$testSku => ['entity_id' => self::ENTITY_ID]]); $object = $this->invokeMethod($this->importProduct, '_saveProductAttributes', [$attributesData]); $this->assertEquals($this->importProduct, $object); diff --git a/app/code/Magento/CatalogImportExport/etc/di.xml b/app/code/Magento/CatalogImportExport/etc/di.xml index 4e2fe390e0b17..71e5c5e50e608 100644 --- a/app/code/Magento/CatalogImportExport/etc/di.xml +++ b/app/code/Magento/CatalogImportExport/etc/di.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\CatalogImportExport\Model\Export\RowCustomizerInterface" type="Magento\CatalogImportExport\Model\Export\RowCustomizer\Composite" /> <preference for="Magento\CatalogImportExport\Model\StockItemImporterInterface" type="Magento\CatalogImportExport\Model\StockItemImporter" /> + <preference for="Magento\CatalogImportExport\Model\Export\ProductFilterInterface" type="Magento\CatalogImportExport\Model\Export\ProductFilters" /> <type name="Magento\ImportExport\Model\Import"> <plugin name="catalogProductFlatIndexerImport" type="Magento\CatalogImportExport\Model\Indexer\Product\Flat\Plugin\Import" /> <plugin name="invalidatePriceIndexerOnImport" type="Magento\CatalogImportExport\Model\Indexer\Product\Price\Plugin\Import" /> @@ -16,6 +17,13 @@ <plugin name="invalidateProductCategoryIndexerOnImport" type="Magento\CatalogImportExport\Model\Indexer\Product\Category\Plugin\Import" /> <plugin name="invalidateCategoryProductIndexerOnImport" type="Magento\CatalogImportExport\Model\Indexer\Category\Product\Plugin\Import" /> </type> + <type name="Magento\CatalogImportExport\Model\Import\Product\StockProcessor"> + <arguments> + <argument name="indexers" xsi:type="array"> + <item name="cataloginventory_stock" xsi:type="const">Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID</item> + </argument> + </arguments> + </type> <type name="Magento\CatalogImportExport\Model\Import\Product\Validator"> <arguments> <argument name="validators" xsi:type="array"> @@ -35,4 +43,12 @@ <argument name="validationState" xsi:type="object">Magento\Framework\Config\ValidationState\Required</argument> </arguments> </type> + <type name="Magento\CatalogImportExport\Model\Export\ProductFilters"> + <arguments> + <argument name="filters" xsi:type="array"> + <item name="category_ids" xsi:type="object">Magento\CatalogImportExport\Model\Export\Product\CategoryFilter</item> + <item name="quantity_and_stock_status" xsi:type="object">Magento\CatalogImportExport\Model\Export\Product\StockStatusFilter</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php index 502d9532e8a05..e67568b80898e 100644 --- a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php +++ b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php @@ -222,9 +222,11 @@ private function checkOptionsQtyIncrements(Item $quoteItem, array $options): voi { $removeErrors = true; foreach ($options as $option) { + $optionValue = $option->getValue(); + $optionQty = $quoteItem->getData('qty') * $optionValue; $result = $this->stockState->checkQtyIncrements( $option->getProduct()->getId(), - $quoteItem->getData('qty'), + $optionQty, $option->getProduct()->getStore()->getWebsiteId() ); if ($result->getHasError()) { diff --git a/app/code/Magento/CatalogInventory/Model/Source/Stock.php b/app/code/Magento/CatalogInventory/Model/Source/Stock.php index 7d44ab782de61..9661fc83ce275 100644 --- a/app/code/Magento/CatalogInventory/Model/Source/Stock.php +++ b/app/code/Magento/CatalogInventory/Model/Source/Stock.php @@ -42,7 +42,7 @@ public function getAllOptions() public function addValueSortToCollection($collection, $dir = \Magento\Framework\Data\Collection::SORT_ORDER_DESC) { $collection->getSelect()->joinLeft( - ['stock_item_table' => 'cataloginventory_stock_item'], + ['stock_item_table' => $collection->getTable('cataloginventory_stock_item')], "e.entity_id=stock_item_table.product_id", [] ); diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemChecker.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemChecker.php new file mode 100644 index 0000000000000..e3a9c48b27155 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemChecker.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Model\Stock; + +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; +use Magento\Framework\Stdlib\ArrayUtils; +use Magento\CatalogInventory\Model\Stock\Item as StockItem; + +/** + * Verifies Stock item model changes. + */ +class StockItemChecker +{ + /** + * @var StockItemRepositoryInterface + */ + private $stockItemRepository; + + /** + * @var ArrayUtils + */ + private $arrayUtils; + + /** + * @var string[] + */ + private $skippedAttributes; + + /** + * @param StockItemRepositoryInterface $stockItemRepository + * @param ArrayUtils $arrayUtils + * @param string[] $skippedAttributes + */ + public function __construct( + StockItemRepositoryInterface $stockItemRepository, + ArrayUtils $arrayUtils, + array $skippedAttributes = [] + ) { + $this->stockItemRepository = $stockItemRepository; + $this->arrayUtils = $arrayUtils; + $this->skippedAttributes = $skippedAttributes; + } + + /** + * Check if stock item is modified. + * + * @param StockItem $model + * @return bool + */ + public function isModified($model): bool + { + if (!$model->getId()) { + return true; + } + $stockItem = $this->stockItemRepository->get($model->getId()); + $stockItemData = $stockItem->getData(); + $modelData = $model->getData(); + foreach ($this->skippedAttributes as $attribute) { + unset($stockItemData[$attribute], $modelData[$attribute]); + } + $diff = $this->arrayUtils->recursiveDiff($stockItemData, $modelData); + + return !empty($diff); + } +} diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryChangeManageStockActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryChangeManageStockActionGroup.xml new file mode 100644 index 0000000000000..2c38f14f53379 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryChangeManageStockActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCatalogInventoryChangeManageStockActionGroup"> + <annotations> + <description>Opens advanced inventory modal if it has not opened yet. Sets Manage stock value.</description> + </annotations> + <arguments> + <argument name="manageStock" type="string" defaultValue="Yes"/> + </arguments> + <conditionalClick selector="{{AdminProductFormSection.advancedInventoryLink}}" dependentSelector="{{AdminProductFormAdvancedInventorySection.advancedInventoryModal}}" visible="false" stepKey="openAdvancedInventoryWindow"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.useConfigSettings}}" stepKey="waitForAdvancedInventoryModalWindowOpen"/> + <uncheckOption selector="{{AdminProductFormAdvancedInventorySection.useConfigSettings}}" stepKey="uncheckManageStockConfigSetting"/> + <selectOption selector="{{AdminProductFormAdvancedInventorySection.manageStock}}" userInput="{{manageStock}}" stepKey="changeManageStock"/> + <click selector="{{AdminProductFormAdvancedInventorySection.doneButton}}" stepKey="clickDoneButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Source/StockTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Source/StockTest.php index 11f41fcaf6d01..1c81e17358e71 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Source/StockTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Source/StockTest.php @@ -25,6 +25,7 @@ public function testAddValueSortToCollection() $selectMock = $this->createMock(\Magento\Framework\DB\Select::class); $collectionMock = $this->createMock(\Magento\Eav\Model\Entity\Collection\AbstractCollection::class); $collectionMock->expects($this->atLeastOnce())->method('getSelect')->willReturn($selectMock); + $collectionMock->expects($this->atLeastOnce())->method('getTable')->willReturn('cataloginventory_stock_item'); $selectMock->expects($this->once()) ->method('joinLeft') diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemCheckerTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemCheckerTest.php new file mode 100644 index 0000000000000..0f2568a2e213d --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemCheckerTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Test\Unit\Model\Stock; + +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Model\Stock\StockItemChecker; +use Magento\CatalogInventory\Model\Stock\Item as StockItem; +use Magento\CatalogInventory\Model\Stock\StockItemRepository; +use Magento\Framework\Stdlib\ArrayUtils; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Unit test for StockItemChecker. + * @see StockItemChecker + */ +class StockItemCheckerTest extends TestCase +{ + /** + * @var StockItemChecker + */ + private $model; + + /** + * @var StockItemRepository|MockObject + */ + private $stockItemRepository; + + /** + * @var StockItem|MockObject + */ + private $stockItemModel; + + /** + * @var ArrayUtils + */ + private $arrayUtils; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->stockItemRepository = $this->createPartialMock(StockItemRepository::class, ['get']); + $this->arrayUtils = $objectManager->getObject(ArrayUtils::class); + $this->stockItemModel = $this->createPartialMock(StockItem::class, ['getId', 'getData']); + + $this->model = $objectManager->getObject( + StockItemChecker::class, + [ + 'stockItemRepository' => $this->stockItemRepository, + 'arrayUtils' => $this->arrayUtils, + 'skippedAttributes' => [StockItemInterface::LOW_STOCK_DATE], + ] + ); + } + + /** + * Test for isModified method when model is new. + * + * @return void + */ + public function testIsModifiedWhenModelIsNew(): void + { + $this->stockItemModel->expects($this->once())->method('getId')->willReturn(null); + $this->stockItemRepository->expects($this->never())->method('get'); + + $this->assertTrue($this->model->isModified($this->stockItemModel)); + } + + /** + * Test for isModified method when found difference between data. + * + * @param array $itemFromRepository + * @param array $model + * @param bool $expectedResult + * @return void + * @dataProvider stockItemModelDataProvider + */ + public function testIsModified( + array $itemFromRepository, + array $model, + bool $expectedResult + ): void { + $this->stockItemModel->expects($this->exactly(2))->method('getId')->willReturn($model['id']); + $this->stockItemRepository->expects($this->once())->method('get')->willReturn($this->stockItemModel); + $this->stockItemModel->expects($this->exactly(2)) + ->method('getData') + ->willReturnOnConsecutiveCalls($itemFromRepository, $model); + + $this->assertEquals($expectedResult, $this->model->isModified($this->stockItemModel)); + } + + /** + * Data provider for testIsModified. + * + * @return array + */ + public function stockItemModelDataProvider(): array + { + return [ + 'Model is modified' => [ + 'stockItemFromRepository' => [ + 'id' => 1, + 'low_stock_date' => '01.01.2020', + 'qty' => 100, + ], + 'model' => [ + 'id' => 1, + 'low_stock_date' => '01.01.2021', + 'qty' => 99, + ], + 'expectedResult' => true, + ], + 'Model is not modified' => [ + 'stockItemFromRepository' => [ + 'id' => 1, + 'low_stock_date' => '01.01.2020', + 'qty' => 100, + ], + 'model' => [ + 'id' => 1, + 'low_stock_date' => '01.01.2021', + 'qty' => 100, + ], + 'expectedResult' => false, + ], + ]; + } +} 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 aafde14a28584..38a33b75f552a 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 @@ -242,6 +242,7 @@ private function prepareMeta() 'handleChanges' => '${$.provider}:data.product.stock_data.is_qty_decimal', ], 'sortOrder' => 10, + 'disabled' => $this->locator->getProduct()->isLockedAttribute($fieldCode), ]; $advancedInventoryButton['arguments']['data']['config'] = [ 'displayAsLink' => true, diff --git a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventoryModal.php b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventoryModal.php new file mode 100644 index 0000000000000..76194b0710441 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventoryModal.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Ui\DataProvider\Product\Form\Modifier; + +use Magento\Catalog\Model\Locator\LocatorInterface; +use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; + +/** + * Data provider for advanced inventory modal form. + */ +class AdvancedInventoryModal extends AbstractModifier +{ + /** + * @var LocatorInterface + */ + private $locator; + + /** + * @var array + */ + private $meta; + + /** + * @param LocatorInterface $locator + */ + public function __construct(LocatorInterface $locator) + { + $this->locator = $locator; + } + + /** + * @inheritdoc + */ + public function modifyMeta(array $meta) + { + $this->meta = $meta; + $this->prepareMeta(); + + return $this->meta; + } + + /** + * @inheritdoc + */ + public function modifyData(array $data) + { + return $data; + } + + /** + * Modify Advanced Inventory Modal meta. + * + * @return array + */ + private function prepareMeta(): array + { + $product = $this->locator->getProduct(); + $readOnly = (bool)$product->getInventoryReadonly(); + + $this->meta['advanced_inventory_modal']['children']['stock_data']['arguments'] + ['data']['config']['disabled'] = $readOnly; + + return $this->meta; + } +} diff --git a/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml b/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml index 28035de29bc2e..4d90b2159d852 100644 --- a/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml +++ b/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml @@ -37,10 +37,21 @@ <item name="class" xsi:type="string">Magento\CatalogInventory\Ui\DataProvider\Product\Form\Modifier\AdvancedInventory</item> <item name="sortOrder" xsi:type="number">20</item> </item> + <item name="advancedInventoryModal" xsi:type="array"> + <item name="class" xsi:type="string">Magento\CatalogInventory\Ui\DataProvider\Product\Form\Modifier\AdvancedInventoryModal</item> + <item name="sortOrder" xsi:type="number">900</item> + </item> </argument> </arguments> </virtualType> <type name="Magento\Catalog\Model\ProductLink\Search"> <plugin name="processOutOfStockProducts" type="Magento\CatalogInventory\Model\Plugin\ProductSearch"/> </type> + <type name="Magento\CatalogInventory\Model\Stock\StockItemChecker"> + <arguments> + <argument name="skippedAttributes" xsi:type="array"> + <item name="low_stock_date" xsi:type="const">Magento\CatalogInventory\Api\Data\StockItemInterface::LOW_STOCK_DATE</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml index 546f838b9b428..a5a8476b20a02 100644 --- a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml @@ -13,21 +13,21 @@ <resource>Magento_CatalogInventory::cataloginventory</resource> <group id="options" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Stock Options</label> - <field id="can_subtract" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="can_subtract" translate="label" type="select" sortOrder="2" showInDefault="1" canRestore="1"> <label>Decrease Stock When Order is Placed</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="can_back_in_stock" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="can_back_in_stock" translate="label" type="select" sortOrder="2" showInDefault="1" canRestore="1"> <label>Set Items' Status to be In Stock When Order is Cancelled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="show_out_of_stock" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="show_out_of_stock" translate="label comment" type="select" sortOrder="3" showInDefault="1" canRestore="1"> <label>Display Out of Stock Products</label> <comment>Products will still be shown by direct product URLs.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\CatalogInventory\Model\Config\Backend\ShowOutOfStock</backend_model> </field> - <field id="stock_threshold_qty" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="stock_threshold_qty" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Only X left Threshold</label> <validate>validate-number</validate> </field> @@ -41,45 +41,45 @@ <![CDATA[Please note that these settings apply to individual items in the cart, not to the entire cart.]]> </comment> <label>Product Stock Options</label> - <field id="manage_stock" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="manage_stock" translate="label comment" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Manage Stock</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\CatalogInventory\Model\Config\Backend\Managestock</backend_model> <comment>Changing can take some time due to processing whole catalog.</comment> </field> - <field id="backorders" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="backorders" translate="label comment" type="select" sortOrder="3" showInDefault="1" canRestore="1"> <label>Backorders</label> <source_model>Magento\CatalogInventory\Model\Source\Backorders</source_model> <backend_model>Magento\CatalogInventory\Model\Config\Backend\Backorders</backend_model> <comment>Changing can take some time due to processing whole catalog.</comment> </field> - <field id="max_sale_qty" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="max_sale_qty" translate="label" type="text" sortOrder="4" showInDefault="1" canRestore="1"> <label>Maximum Qty Allowed in Shopping Cart</label> <validate>validate-number validate-greater-than-zero</validate> </field> - <field id="min_qty" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="min_qty" translate="label" type="text" sortOrder="5" showInDefault="1" canRestore="1"> <label>Out-of-Stock Threshold</label> <validate>validate-number</validate> <backend_model>Magento\CatalogInventory\Model\System\Config\Backend\Minqty</backend_model> </field> - <field id="min_sale_qty" translate="label" sortOrder="6" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="min_sale_qty" translate="label" sortOrder="6" showInDefault="1" canRestore="1"> <label>Minimum Qty Allowed in Shopping Cart</label> <frontend_model>Magento\CatalogInventory\Block\Adminhtml\Form\Field\Minsaleqty</frontend_model> <backend_model>Magento\CatalogInventory\Model\System\Config\Backend\Minsaleqty</backend_model> </field> - <field id="notify_stock_qty" translate="label" type="text" sortOrder="7" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="notify_stock_qty" translate="label" type="text" sortOrder="7" showInDefault="1" canRestore="1"> <label>Notify for Quantity Below</label> <validate>validate-number</validate> </field> - <field id="auto_return" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="auto_return" translate="label" type="select" sortOrder="10" showInDefault="1" canRestore="1"> <label>Automatically Return Credit Memo Item to Stock</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="enable_qty_increments" translate="label" type="select" sortOrder="8" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="enable_qty_increments" translate="label" type="select" sortOrder="8" showInDefault="1" canRestore="1"> <label>Enable Qty Increments</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="qty_increments" translate="label" type="text" sortOrder="9" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="qty_increments" translate="label" type="text" sortOrder="9" showInDefault="1" canRestore="1"> <label>Qty Increments</label> <validate>validate-number validate-greater-than-zero</validate> <backend_model>Magento\CatalogInventory\Model\System\Config\Backend\Qtyincrements</backend_model> diff --git a/app/code/Magento/CatalogInventory/etc/config.xml b/app/code/Magento/CatalogInventory/etc/config.xml index 976b3f4cad510..21641b3a4cfcd 100644 --- a/app/code/Magento/CatalogInventory/etc/config.xml +++ b/app/code/Magento/CatalogInventory/etc/config.xml @@ -22,6 +22,7 @@ <min_sale_qty>1</min_sale_qty> <min_qty>0</min_qty> <notify_stock_qty>1</notify_stock_qty> + <auto_return>0</auto_return> <enable_qty_increments>0</enable_qty_increments> <qty_increments>1</qty_increments> </item_options> diff --git a/app/code/Magento/CatalogInventory/etc/mview.xml b/app/code/Magento/CatalogInventory/etc/mview.xml index 72dda16e8b5bb..338f1fe0610a1 100644 --- a/app/code/Magento/CatalogInventory/etc/mview.xml +++ b/app/code/Magento/CatalogInventory/etc/mview.xml @@ -9,6 +9,8 @@ <view id="cataloginventory_stock" class="Magento\CatalogInventory\Model\Indexer\Stock" group="indexer"> <subscriptions> <table name="cataloginventory_stock_item" entity_column="product_id" /> + <!--Track product status to trigger stock indexer--> + <table name="catalog_product_entity_int" entity_column="entity_id" /> </subscriptions> </view> <view id="catalog_product_price" class="Magento\Catalog\Model\Indexer\Product\Price" group="indexer"> diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml index c77c77a5183d0..27ce26cabc627 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml +++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml @@ -49,6 +49,9 @@ <scopeLabel>[GLOBAL]</scopeLabel> <label translate="true">Manage Stock</label> <dataScope>manage_stock</dataScope> + <imports> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> + </imports> </settings> <formElements> <select> @@ -71,9 +74,9 @@ <links> <link name="linkedValue">${$.provider}:data.product.stock_data.manage_stock</link> </links> - <exports> - <link name="checked">${$.parentName}.manage_stock:disabled</link> - </exports> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -129,6 +132,9 @@ </validation> <label translate="true">Out-of-Stock Threshold</label> <dataScope>min_qty</dataScope> + <imports> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> + </imports> </settings> </field> <field name="use_config_min_qty" component="Magento_CatalogInventory/js/components/use-config-settings" formElement="checkbox"> @@ -144,9 +150,9 @@ <links> <link name="linkedValue">${$.provider}:data.product.stock_data.min_qty</link> </links> - <exports> - <link name="checked">${$.parentName}.min_qty:disabled</link> - </exports> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -187,6 +193,7 @@ <dataScope>min_sale_qty</dataScope> <imports> <link name="handleChanges">${$.provider}:data.product.stock_data.is_qty_decimal</link> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> </imports> </settings> </field> @@ -207,6 +214,9 @@ <class name="admin__field-no-label">true</class> </additionalClasses> <dataScope>use_config_min_sale_qty</dataScope> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -287,6 +297,9 @@ </validation> <label translate="true">Maximum Qty Allowed in Shopping Cart</label> <dataScope>max_sale_qty</dataScope> + <imports> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> + </imports> </settings> </field> <field name="use_config_max_sale_qty" component="Magento_CatalogInventory/js/components/use-config-settings" formElement="checkbox"> @@ -302,9 +315,9 @@ <links> <link name="linkedValue">${$.provider}:data.product.stock_data.max_sale_qty</link> </links> - <exports> - <link name="checked">${$.parentName}.max_sale_qty:disabled</link> - </exports> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -388,6 +401,9 @@ <scopeLabel>[GLOBAL]</scopeLabel> <label translate="true">Backorders</label> <dataScope>backorders</dataScope> + <imports> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> + </imports> </settings> <formElements> <select> @@ -410,9 +426,9 @@ <links> <link name="linkedValue">${$.provider}:data.product.stock_data.backorders</link> </links> - <exports> - <link name="checked">${$.parentName}.backorders:disabled</link> - </exports> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -450,6 +466,9 @@ </validation> <label translate="true">Notify for Quantity Below</label> <dataScope>notify_stock_qty</dataScope> + <imports> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> + </imports> </settings> </field> <field name="use_config_notify_stock_qty" component="Magento_CatalogInventory/js/components/use-config-settings" formElement="checkbox"> @@ -465,9 +484,9 @@ <links> <link name="linkedValue">${$.provider}:data.product.stock_data.notify_stock_qty</link> </links> - <exports> - <link name="checked">${$.parentName}.notify_stock_qty:disabled</link> - </exports> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -500,6 +519,9 @@ <scopeLabel>[GLOBAL]</scopeLabel> <label translate="true">Enable Qty Increments</label> <dataScope>enable_qty_increments</dataScope> + <imports> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> + </imports> </settings> <formElements> <select> @@ -522,9 +544,9 @@ <links> <link name="linkedValue">${$.provider}:data.product.stock_data.enable_qty_increments</link> </links> - <exports> - <link name="checked">${$.parentName}.enable_qty_increments:disabled</link> - </exports> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -565,6 +587,7 @@ <dataScope>qty_increments</dataScope> <imports> <link name="handleChanges">${$.provider}:data.product.stock_data.is_qty_decimal</link> + <link name="disabled">${$.parentName}.use_config_${$.index}:disableParent</link> </imports> </settings> </field> @@ -581,9 +604,9 @@ <links> <link name="linkedValue">${$.provider}:data.product.stock_data.qty_increments</link> </links> - <exports> - <link name="checked">${$.parentName}.qty_increments:disabled</link> - </exports> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <checkbox class="Magento\CatalogInventory\Ui\Component\Product\Form\Element\UseConfigSettings"> @@ -618,6 +641,9 @@ <scopeLabel>[GLOBAL]</scopeLabel> <label translate="true">Stock Status</label> <dataScope>is_in_stock</dataScope> + <imports> + <link name="disabled">ns = ${ $.ns }, index = stock_data:disabled</link> + </imports> </settings> <formElements> <select> diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-min-sale-qty.js b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-min-sale-qty.js index 4ac1d5ed2d294..c7ca38f05f707 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-min-sale-qty.js +++ b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-min-sale-qty.js @@ -37,7 +37,7 @@ define([ /** * @inheritdoc */ - 'onCheckedChanged': function (newChecked) { + onCheckedChanged: function (newChecked) { var valueFromConfig = this.valueFromConfig(); if (newChecked && (_.isArray(valueFromConfig) && valueFromConfig.length === 0 || valueFromConfig === 1)) { @@ -49,7 +49,7 @@ define([ this.changeVisibleDisabled(this.inputField, true, true, null); this.changeVisibleDisabled(this.dynamicRowsField, false, true, null); } else { - this.changeVisibleDisabled(this.inputField, true, false, null); + this.changeVisibleDisabled(this.inputField, true, this.disabled() || false, null); this.changeVisibleDisabled(this.dynamicRowsField, false, true, null); } diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-settings.js b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-settings.js index f91797448fa55..e121743b0f69f 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-settings.js +++ b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/use-config-settings.js @@ -11,7 +11,15 @@ define([ return checkbox.extend({ defaults: { valueFromConfig: '', - linkedValue: '' + linkedValue: '', + disableParent: false, + listens: { + disabled: 'processState', + checked: 'processState onCheckedChanged' + }, + imports: { + readOnly: 'ns = ${ $.ns }, index = stock_data:disabled' + } }, /** @@ -20,13 +28,24 @@ define([ initObservable: function () { return this ._super() - .observe(['valueFromConfig', 'linkedValue']); + .observe(['valueFromConfig', 'linkedValue', 'disableParent']); + }, + + /** + * Handle checked and disabled changes to calculate disableParent value + */ + processState: function () { + this.disableParent(this.checked() || this.readOnly); + + if (this.readOnly) { + this.disable(); + } }, /** * @inheritdoc */ - 'onCheckedChanged': function (newChecked) { + onCheckedChanged: function (newChecked) { if (newChecked) { this.linkedValue(this.valueFromConfig()); } diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php index 48c463fc18b80..aaafc4ecd4c5d 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php @@ -86,9 +86,9 @@ public function __construct( /** * @inheritdoc */ - public function build($productId) + public function build(int $productId, int $storeId) : array { - $timestamp = $this->localeDate->scopeTimeStamp($this->storeManager->getStore()); + $timestamp = $this->localeDate->scopeTimeStamp($this->storeManager->getStore($storeId)); $currentDate = $this->dateTime->formatDate($timestamp, false); $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); @@ -108,7 +108,7 @@ public function build($productId) sprintf('t.product_id = %s.%s', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField), [] )->where('parent.entity_id = ?', $productId) - ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) + ->where('t.website_id = ?', $this->storeManager->getStore($storeId)->getWebsiteId()) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.rule_date = ?', $currentDate) ->order('t.rule_price ' . Select::SQL_ASC) 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..5860137c1ab8d --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="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"/> + <executeInSelenium + function=" + function ($webdriver) use ($I) { + $rows = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('table.data-grid tbody tr[data-role=row]:not(.data-grid-tr-no-data):nth-of-type(1)')); + while(!empty($rows)) { + $rows[0]->click(); + $I->waitForPageLoad(30); + $I->click('#delete'); + $I->waitForPageLoad(30); + $I->waitForElementVisible('aside.confirm .modal-footer button.action-accept', 10); + $I->waitForPageLoad(60); + $I->click('aside.confirm .modal-footer button.action-accept'); + $I->waitForPageLoad(60); + $I->waitForLoadingMaskToDisappear(); + $I->waitForElementVisible('#messages div.message-success', 10); + $I->see('You deleted the rule.', '#messages div.message-success'); + $rows = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('table.data-grid tbody tr[data-role=row]:not(.data-grid-tr-no-data):nth-of-type(1)')); + } + }" + stepKey="deleteAllCatalogPriceRulesOneByOne"/> + <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/ActionGroup/AdminCatalogPriceRuleFillActionsActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleFillActionsActionGroup.xml new file mode 100644 index 0000000000000..da8571769ef31 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleFillActionsActionGroup.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="AdminCatalogPriceRuleFillActionsActionGroup"> + <annotations> + <description>Fill Catalog Price Rule actions fields: Apply, Discount Amount, Discard subsequent rules.</description> + </annotations> + <arguments> + <argument name="apply" type="string" defaultValue="{{_defaultCatalogRule.simple_action}}"/> + <argument name="discountAmount" type="string" defaultValue="{{_defaultCatalogRule.discount_amount}}"/> + <argument name="discardSubsequentRules" type="string" defaultValue="Yes"/> + </arguments> + + <conditionalClick selector="{{AdminNewCatalogPriceRule.actionsTabTitle}}" dependentSelector="{{AdminNewCatalogPriceRule.actionsTabBody}}" visible="false" stepKey="openActionSectionIfNeeded"/> + <scrollTo selector="{{AdminNewCatalogPriceRule.actionsTabTitle}}" stepKey="scrollToActionsFieldset"/> + <waitForElementVisible selector="{{AdminNewCatalogPriceRuleActions.apply}}" stepKey="waitActionsFieldsetFullyOpened"/> + <selectOption selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{apply}}" stepKey="fillDiscountType"/> + <fillField selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{discountAmount}}" stepKey="fillDiscountAmount"/> + <selectOption selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="{{discardSubsequentRules}}" stepKey="fillDiscardSubsequentRules"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleFillMainInfoActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleFillMainInfoActionGroup.xml new file mode 100644 index 0000000000000..e609550d19461 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleFillMainInfoActionGroup.xml @@ -0,0 +1,34 @@ +<?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="AdminCatalogPriceRuleFillMainInfoActionGroup"> + <annotations> + <description>Fill Catalog Price Rule main info fields: Name, Description, Active (1/0), Priority.</description> + </annotations> + <arguments> + <argument name="name" type="string" defaultValue="{{_defaultCatalogRule.name}}"/> + <argument name="description" type="string" defaultValue="{{_defaultCatalogRule.description}}"/> + <argument name="active" type="string" defaultValue="1"/> + <argument name="websites" type="string" defaultValue="'Main Website'"/> + <argument name="groups" type="string" defaultValue="'NOT LOGGED IN','General','Wholesale','Retailer'"/> + <argument name="fromDate" type="string" defaultValue=""/> + <argument name="toDate" type="string" defaultValue=""/> + <argument name="priority" type="string" defaultValue=""/> + </arguments> + + <fillField selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{name}}" stepKey="fillName"/> + <fillField selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{description}}" stepKey="fillDescription"/> + <conditionalClick selector="{{AdminNewCatalogPriceRule.isActive}}" dependentSelector="{{AdminNewCatalogPriceRule.activeByStatus(active)}}" visible="false" stepKey="fillActive"/> + <selectOption selector="{{AdminNewCatalogPriceRule.websites}}" parameterArray="[{{websites}}]" stepKey="selectSpecifiedWebsites"/> + <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" parameterArray="[{{groups}}]" stepKey="selectSpecifiedCustomerGroups"/> + <fillField selector="{{AdminNewCatalogPriceRule.fromDate}}" userInput="{{fromDate}}" stepKey="fillFromDate"/> + <fillField selector="{{AdminNewCatalogPriceRule.toDate}}" userInput="{{toDate}}" stepKey="fillToDate"/> + <fillField selector="{{AdminNewCatalogPriceRule.priority}}" userInput="{{priority}}" stepKey="fillPriority"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSaveAndApplyActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSaveAndApplyActionGroup.xml new file mode 100644 index 0000000000000..84cc7b862ef7c --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSaveAndApplyActionGroup.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="AdminCatalogPriceRuleSaveAndApplyActionGroup"> + <annotations> + <description>Clicks Save and Apply on a Admin Catalog Price Rule creation/edit page. Validates that the Success Message is present. Validates that applied rules success message is present.</description> + </annotations> + + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplyRule"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessageAppears"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the rule." stepKey="checkSuccessSaveMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="Updated rules applied." stepKey="checkSuccessAppliedMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSelectCustomerGroupsActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSelectCustomerGroupsActionGroup.xml new file mode 100644 index 0000000000000..8c37325aff722 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSelectCustomerGroupsActionGroup.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="AdminCatalogPriceRuleSelectCustomerGroupsActionGroup"> + <annotations> + <description>Fill Catalog Price Rule customer groups multiselect on new/edit page.</description> + </annotations> + <arguments> + <argument name="groups" type="string" defaultValue="'NOT LOGGED IN','General','Wholesale','Retailer'"/> + </arguments> + + <selectOption selector="{{AdminNewCatalogPriceRule.customerGroups}}" parameterArray="[{{groups}}]" stepKey="selectSpecifiedCustomerGroups"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml index 82e7a6979e34b..9ad27d22caba6 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminSaveAndApplyRulesActionGroup.xml @@ -10,9 +10,9 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminSaveAndApplyRulesActionGroup"> <annotations> - <description>Clicks Save on a Admin Catalog Price Rule creation/edit page. Validates that the Success Message is present. Clicks Apply Rules. Validates that the Success Message is present.</description> + <description>DEPRECATED. Please use AdminCatalogPriceRuleSaveAndApplyActionGroup instead. Clicks Save on a Admin Catalog Price Rule creation/edit page. Validates that the Success Message is present. Clicks Apply Rules. Validates that the Success Message is present.</description> </annotations> - + <waitForPageLoad stepKey="waitForPageToLoad"/> <scrollToTopOfPage stepKey="scrollToTop"/> <click selector="{{AdminNewCatalogPriceRule.save}}" stepKey="saveTheCatalogRule"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml index cd2f7a207a3e2..b6fc92e1a1df6 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogSelectCustomerGroupActionGroup.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CatalogSelectCustomerGroupActionGroup"> <annotations> - <description>Selects the provided Customer Group Name on the Admin Catalog Price Rule creation/edit page.</description> + <description>DEPRECATED. Please use AdminCatalogPriceRuleSelectCustomerGroupsActionGroup instead. Selects the provided Customer Group Name on the Admin Catalog Price Rule creation/edit page.</description> </annotations> <arguments> <argument name="customerGroupName" defaultValue="NOT LOGGED IN" type="string"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml index bdc09c56353df..732aee0ad63d7 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup.xml @@ -18,8 +18,8 @@ <argument name="indexA" type="string"/> <argument name="indexB" type="string"/> </arguments> - - <click selector="{{AdminNewCatalogPriceRule.conditionsTab}}" stepKey="openConditionsTab"/> + + <conditionalClick selector="{{AdminNewCatalogPriceRule.conditionsTabTitle}}" dependentSelector="{{AdminNewCatalogPriceRule.conditionsTabBody}}" visible="false" stepKey="openConditionsTab"/> <waitForPageLoad stepKey="waitForConditionTabOpened"/> <click selector="{{AdminNewCatalogPriceRuleConditions.newCondition}}" stepKey="addNewCondition"/> <selectOption selector="{{AdminNewCatalogPriceRuleConditions.conditionSelect(indexA)}}" userInput="{{attributeName}}" stepKey="selectTypeCondition"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml index 7d375da6dfb65..be0fdb2e0b419 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml @@ -20,7 +20,8 @@ <element name="ruleNameNew" type="input" selector="[name='staging[name]']"/> <element name="description" type="textarea" selector="[name='description']"/> <element name="status" type="select" selector="[name='is_active']"/> - <element name="isActive" type="select" selector="input[name='is_active']+label"/> + <element name="isActive" type="text" selector="input[name='is_active']+label"/> + <element name="activeByStatus" type="text" selector="div.admin__actions-switch input[name='is_active'][value='{{value}}']+label" parameterized="true"/> <element name="websites" type="select" selector="[name='website_ids']"/> <element name="active" type="checkbox" selector="//div[contains(@class, 'admin__actions-switch')]/input[@name='is_active']/../label"/> @@ -34,10 +35,15 @@ <element name="startDateButton" type="button" selector="[name='staging[start_time]'] + button" timeout="15"/> <element name="toDateButton" type="button" selector="[name='to_date'] + button" timeout="15"/> <element name="todayDate" type="button" selector="#ui-datepicker-div [data-handler='today']"/> + <element name="fromDate" type="input" selector="[name='from_date']"/> + <element name="toDate" type="input" selector="[name='to_date']"/> <element name="priority" type="input" selector="[name='sort_order']"/> <element name="conditionsTab" type="block" selector="[data-index='block_promo_catalog_edit_tab_conditions']"/> + <element name="conditionsTabTitle" type="block" selector="[data-index='block_promo_catalog_edit_tab_conditions'] .fieldset-wrapper-title"/> + <element name="conditionsTabBody" type="block" selector="[data-index='block_promo_catalog_edit_tab_conditions'] .admin__fieldset-wrapper-content"/> <element name="actionsTab" type="block" selector="[data-index='actions']"/> - + <element name="actionsTabTitle" type="block" selector="[data-index='actions'] .fieldset-wrapper-title"/> + <element name="actionsTabBody" type="block" selector="[data-index='actions'] .admin__fieldset-wrapper-content"/> <element name="fieldError" type="text" selector="//input[@name='{{fieldName}}']/following-sibling::label[@class='admin__field-error']" parameterized="true"/> </section> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml index 514366fa45c52..02d6d90ae5d0e 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml @@ -114,6 +114,11 @@ <!-- Navigate to category on store front --> <amOnPage url="{{StorefrontProductPage.url($createCategory.name$)}}" stepKey="goToCategoryPage"/> + <!-- Sort products By Price --> + <actionGroup ref="StorefrontCategoryPageSortProductActionGroup" stepKey="sortProductByPrice"/> + <!-- Set Ascending Direction --> + <actionGroup ref="StorefrontCategoryPageSortAscendingActionGroup" stepKey="setAscendingDirection"/> + <!-- Check simple product name on store front category page --> <actionGroup ref="AssertProductDetailsOnStorefrontActionGroup" stepKey="storefrontProduct1Name"> <argument name="productInfo" value="$createSimpleProduct.name$"/> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml new file mode 100644 index 0000000000000..8b72b7616b6ff --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml @@ -0,0 +1,289 @@ +<?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="AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test"> + <annotations> + <features value="CatalogRuleConfigurable"/> + <stories value="Apply catalog price rule"/> + <title value="Apply catalog rule for configurable product with assigned simple products"/> + <description value="Admin should be able to apply catalog rule for configurable product with assigned simple products"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-27708"/> + <group value="catalog"/> + <group value="configurable_product"/> + <group value="catalog_rule_configurable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category for first configurable product --> + <createData entity="SimpleSubCategory" stepKey="firstSimpleCategory"/> + + <!-- Create first configurable product with two options --> + <createData entity="ApiConfigurableProduct" stepKey="createFirstConfigProduct"> + <requiredEntity createDataKey="firstSimpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createFirstConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createFirstConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createFirstConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addFirstProductToAttributeSet"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getFirstConfigAttributeFirstOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getFirstConfigAttributeSecondOption"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + </getData> + + <!-- Create two child products for first configurable product --> + <createData entity="ApiSimpleOne" stepKey="createFirstConfigFirstChildProduct"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createFirstConfigSecondChildProduct"> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createFirstConfigProductOption"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigProductAttribute"/> + <requiredEntity createDataKey="getFirstConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getFirstConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createFirstConfigProductAddFirstChild"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createFirstConfigProductAddSecondChild"> + <requiredEntity createDataKey="createFirstConfigProduct"/> + <requiredEntity createDataKey="createFirstConfigSecondChildProduct"/> + </createData> + + <!-- Add customizable options to first product --> + <updateData createDataKey="createFirstConfigProduct" entity="productWithOptionRadiobutton" stepKey="updateFirstProductWithOption"/> + + <!-- Create category for second configurable product --> + <createData entity="SimpleSubCategory" stepKey="secondSimpleCategory"/> + + <!-- Create second configurable product with two options --> + <createData entity="ApiConfigurableProduct" stepKey="createSecondConfigProduct"> + <requiredEntity createDataKey="secondSimpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createSecondConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createSecondConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createSecondConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addSecondProductToAttributeSet"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getSecondConfigAttributeFirstOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getSecondConfigAttributeSecondOption"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + </getData> + + <!-- Create two child products for second configurable product --> + <createData entity="ApiSimpleOne" stepKey="createSecondConfigFirstChildProduct"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createSecondConfigSecondChildProduct"> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createSecondConfigProductOption"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigProductAttribute"/> + <requiredEntity createDataKey="getSecondConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getSecondConfigAttributeSecondOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createSecondConfigProductAddFirstChild"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createSecondConfigProductAddSecondChild"> + <requiredEntity createDataKey="createSecondConfigProduct"/> + <requiredEntity createDataKey="createSecondConfigSecondChildProduct"/> + </createData> + + <!-- Add customizable options to second product --> + <updateData createDataKey="createSecondConfigProduct" entity="productWithOptionRadiobutton" stepKey="updateSecondProductWithOption"/> + + <!--Create customer group --> + <createData entity="CustomCustomerGroup" stepKey="customerGroup"/> + + <!-- Create Customer --> + <createData entity="SimpleUsCustomerWithNewCustomerGroup" stepKey="createCustomer"> + <requiredEntity createDataKey="customerGroup" /> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + </before> + <after> + <!-- Delete created data --> + <deleteData createDataKey="createFirstConfigProduct" stepKey="deleteFirstConfigProduct"/> + <deleteData createDataKey="createFirstConfigFirstChildProduct" stepKey="deleteFirstConfigFirstChildProduct"/> + <deleteData createDataKey="createFirstConfigSecondChildProduct" stepKey="deleteFirstConfigSecondChildProduct"/> + <deleteData createDataKey="createFirstConfigProductAttribute" stepKey="deleteFirstConfigProductAttribute"/> + <deleteData createDataKey="firstSimpleCategory" stepKey="deleteFirstSimpleCategory"/> + + <deleteData createDataKey="createSecondConfigProduct" stepKey="deleteSecondConfigProduct"/> + <deleteData createDataKey="createSecondConfigFirstChildProduct" stepKey="deleteSecondConfigFirstChildProduct"/> + <deleteData createDataKey="createSecondConfigSecondChildProduct" stepKey="deleteSecondConfigSecondChildProduct"/> + <deleteData createDataKey="createSecondConfigProductAttribute" stepKey="deleteSecondConfigProductAttribute"/> + <deleteData createDataKey="secondSimpleCategory" stepKey="deleteSimpleCategory"/> + + <!-- Customer log out --> + <!-- Must logout before delete customer otherwise magento fails during logout --> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutFromStorefront"/> + + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="customerGroup" stepKey="deleteCustomerGroup"/> + + <!-- Delete created price rules --> + <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!-- Create catalog price rule --> + <executeJS function="return '$$customerGroup.code$$'" stepKey="customerGroupName"/> + <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingCatalogPriceRule"/> + <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForCatalogPriceRule"> + <argument name="groups" value=""{$customerGroupName}""/> + </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleFillActionsActionGroup" stepKey="fillActionsForCatalogPriceRule"> + <argument name="apply" value="{{CatalogRuleToFixed.simple_action}}"/> + <argument name="discountAmount" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleSaveAndApplyActionGroup" stepKey="saveAndApplyCatalogPriceRule"/> + + <actionGroup ref="AdminReindexAndFlushCache" stepKey="reindexAndFlushCache"/> + + <!-- Login to storefront from customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomerOnStorefront"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Assert first product in category --> + <amOnPage url="{{StorefrontCategoryPage.url($$firstSimpleCategory.custom_attributes[url_key]$$)}}" stepKey="goToFirstCategoryPageStorefront"/> + <waitForPageLoad stepKey="waitForFirstCategoryPageLoad"/> + <actionGroup ref="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup" stepKey="checkFirstProductPriceInCategory"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert second product in category --> + <amOnPage url="{{StorefrontCategoryPage.url($$secondSimpleCategory.custom_attributes[url_key]$$)}}" stepKey="goToSecondCategoryPageStorefront"/> + <waitForPageLoad stepKey="waitForSecondCategoryPageLoad"/> + <actionGroup ref="StorefrontCheckCategoryConfigurableProductWithUpdatedPriceActionGroup" stepKey="checkSecondProductPriceInCategory"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Assert first product in storefront product page --> + <amOnPage url="{{StorefrontProductPage.url($$createFirstConfigProduct.custom_attributes[url_key]$$)}}" stepKey="amOnFirstProductPage"/> + <waitForPageLoad stepKey="waitForFirstProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="checkFirstProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Add first product with selected options to the cart --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="firstConfigProductSelectFirstOptionValue"> + <argument name="attributeLabel" value="$$createFirstConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$createFirstConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + </actionGroup> + <actionGroup ref="StorefrontProductPageSelectRadioButtonOptionValueActionGroup" stepKey="firstConfigProductSelectSecondOptionValue"> + <argument name="attributeLabel" value="{{ProductOptionRadiobuttonWithTwoFixedOptions.title}}"/> + <argument name="optionLabel" value="{{ProductOptionValueRadioButtons1.title}}"/> + </actionGroup> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addFirstConfigProductToCart"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + </actionGroup> + + <!-- Add first product with another selected options to the cart --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="firstConfigProductSelectFirstOptionAnotherValue"> + <argument name="attributeLabel" value="$$createFirstConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$createFirstConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + </actionGroup> + <actionGroup ref="StorefrontProductPageSelectRadioButtonOptionValueActionGroup" stepKey="firstConfigProductSelectSecondOptionAnotherValue"> + <argument name="attributeLabel" value="{{ProductOptionRadiobuttonWithTwoFixedOptions.title}}"/> + <argument name="optionLabel" value="{{ProductOptionValueRadioButtons3.title}}"/> + </actionGroup> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addFirstConfigProductWithOtherOptionsToCart"> + <argument name="productName" value="$$createFirstConfigProduct.name$$"/> + </actionGroup> + + <!-- Assert second product in storefront product page --> + <amOnPage url="{{StorefrontProductPage.url($$createSecondConfigProduct.custom_attributes[url_key]$$)}}" stepKey="amOnSecondProductPage"/> + <waitForPageLoad stepKey="waitForSecondProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="checkSecondProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + <argument name="expectedPrice" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <!-- Add second product with selected options to the cart --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="secondConfigProductSelectFirstOptionValue"> + <argument name="attributeLabel" value="$$createSecondConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$createSecondConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + </actionGroup> + <actionGroup ref="StorefrontProductPageSelectRadioButtonOptionValueActionGroup" stepKey="secondConfigProductSelectSecondOptionValue"> + <argument name="attributeLabel" value="{{ProductOptionRadiobuttonWithTwoFixedOptions.title}}"/> + <argument name="optionLabel" value="{{ProductOptionValueRadioButtons1.title}}"/> + </actionGroup> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addSecondConfigProductToCart"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + </actionGroup> + + <!-- Add second product with another selected options to the cart --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="secondConfigProductSelectFirstOptionAnotherValue"> + <argument name="attributeLabel" value="$$createSecondConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$createSecondConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + </actionGroup> + <actionGroup ref="StorefrontProductPageSelectRadioButtonOptionValueActionGroup" stepKey="secondConfigProductSelectSecondOptionAnotherValue"> + <argument name="attributeLabel" value="{{ProductOptionRadiobuttonWithTwoFixedOptions.title}}"/> + <argument name="optionLabel" value="{{ProductOptionValueRadioButtons3.title}}"/> + </actionGroup> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addSecondConfigProductWithOtherOptionsToCart"> + <argument name="productName" value="$$createSecondConfigProduct.name$$"/> + </actionGroup> + + <!--Assert products prices in the cart --> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="amOnShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <waitForElementVisible selector="{{CheckoutCartProductSection.ProductPriceByOption($$createFirstConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="waitForCartFullyLoaded"/> + <see userInput="$210.69" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createFirstConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertFirstProductPriceForFirstProductOption"/> + <see userInput="$120.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createFirstConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertFirstProductPriceForSecondProductOption"/> + <see userInput="$210.69" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createSecondConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertSecondProductPriceForFirstProductOption"/> + <see userInput="$120.70" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createSecondConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertSecondProductPriceForSecondProductOption"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml index 1bc794ae80cd7..c110daee35428 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml @@ -11,14 +11,14 @@ <annotations> <features value="CatalogRuleConfigurable"/> <stories value="Apply catalog price rule"/> - <title value="Apply catalog rule for configurable product with assigned simple products"/> - <description value="Admin should be able to apply catalog rule for configurable product with assigned simple products"/> + <title value="DEPRECATED. Apply catalog rule for configurable product with assigned simple products"/> + <description value="DEPRECATED. Admin should be able to apply catalog rule for configurable product with assigned simple products"/> <severity value="CRITICAL"/> <testCaseId value="MC-14063"/> <group value="catalogRuleConfigurable"/> <group value="mtf_migrated"/> <skip> - <issueId value="MC-17140"/> + <issueId value="DEPRECATED">Use AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test instead</issueId> </skip> </annotations> <before> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml new file mode 100644 index 0000000000000..bc6c89f2f1155 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml @@ -0,0 +1,220 @@ +<?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="AdminApplyCatalogRuleForConfigurableProductWithOptions2Test"> + <annotations> + <features value="CatalogRuleConfigurable"/> + <stories value="Apply catalog price rule"/> + <title value="Apply catalog price rule for configurable product with options"/> + <description value="Admin should be able to apply the catalog rule for configurable product with options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-27707"/> + <group value="catalog"/> + <group value="configurable_product"/> + <group value="catalog_rule_configurable"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="simpleCategory"/> + + <!-- Create configurable product with three options --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="simpleCategory"/> + </createData> + + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption3" stepKey="createConfigProductAttributeThirdOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="3" stepKey="getConfigAttributeThirdOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create three child products --> + <createData entity="ApiSimpleOne" stepKey="createConfigFirstChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createConfigSecondChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + </createData> + + <createData entity="ApiSimpleOne" stepKey="createConfigThirdChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeThirdOption"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + <requiredEntity createDataKey="getConfigAttributeThirdOption"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddFirstChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddSecondChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigSecondChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddThirdChild"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigThirdChildProduct"/> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + </before> + <after> + <!-- Delete created data --> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createConfigSecondChildProduct" stepKey="deleteSecondSimpleProduct"/> + <deleteData createDataKey="createConfigThirdChildProduct" stepKey="deleteThirdSimpleProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="simpleCategory" stepKey="deleteCategory"/> + + <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!-- Create price rule for first configurable product option --> + <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingFirstPriceRule"/> + <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForFirstPriceRule"> + <argument name="groups" value="'NOT LOGGED IN'"/> + </actionGroup> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="fillConditionsForFirstPriceRule"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleFillActionsActionGroup" stepKey="fillActionsForFirstPriceRule"> + <argument name="apply" value="{{CatalogRuleToFixed.simple_action}}"/> + <argument name="discountAmount" value="{{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleSaveAndApplyActionGroup" stepKey="saveAndApplyFirstPriceRule"/> + + <!-- Create price rule for second configurable product option --> + <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingThirdPriceRule"/> + <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForThirdPriceRule"> + <argument name="groups" value="'NOT LOGGED IN'"/> + </actionGroup> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="fillConditionsForThirdPriceRule"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleFillActionsActionGroup" stepKey="fillActionsForThirdPriceRule"/> + <actionGroup ref="AdminCatalogPriceRuleSaveAndApplyActionGroup" stepKey="saveAndApplyThirdPriceRule"/> + + <!-- Create price rule for third configurable product option --> + <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingSecondPriceRule"/> + <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForSecondPriceRule"> + <argument name="groups" value="'NOT LOGGED IN'"/> + </actionGroup> + <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="fillConditionsForSecondPriceRule"> + <argument name="attributeName" value="$$createConfigProductAttribute.attribute[frontend_labels][0][label]$$"/> + <argument name="targetSelectValue" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + <argument name="indexA" value="1"/> + <argument name="indexB" value="1"/> + </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleFillActionsActionGroup" stepKey="fillActionsForSecondPriceRule"> + <argument name="apply" value="{{CatalogRuleWithoutDiscount.simple_action}}"/> + <argument name="discountAmount" value="{{CatalogRuleWithoutDiscount.discount_amount}}"/> + </actionGroup> + <actionGroup ref="AdminCatalogPriceRuleSaveAndApplyActionGroup" stepKey="saveAndApplySecondPriceRule"/> + + <actionGroup ref="AdminReindexAndFlushCache" stepKey="reindexAndFlushCache"/> + + <!-- Assert product in storefront product page --> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.custom_attributes[url_key]$$)}}" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="StorefrontAssertUpdatedProductPriceInStorefrontProductPageActionGroup" stepKey="assertUpdatedProductPriceInStorefrontProductPage"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + <argument name="expectedPrice" value="As low as ${{CatalogRuleToFixed.discount_amount}}"/> + </actionGroup> + + <executeJS function="return '$' + ({{CatalogRuleToFixed.discount_amount}}).toFixed(2);" stepKey="firstOptionPrice"/> + <executeJS function="return '$' + ({{ApiConfigurableProduct.price}} * (100 - {{_defaultCatalogRule.discount_amount}})/100).toFixed(2);" stepKey="secondOptionPrice"/> + + <!-- Assert product options price in storefront product page --> + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToFirstProductOption"> + <argument name="option" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="{$firstOptionPrice} Regular Price ${{ApiConfigurableProduct.price}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToSecondProductOption"> + <argument name="option" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="{$secondOptionPrice} Regular Price ${{ApiConfigurableProduct.price}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertCatalogPriceRuleAppliedToProductOptionActionGroup" stepKey="assertCatalogPriceRuleAppliedToThirdProductOption"> + <argument name="option" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + <argument name="expectedPrice" value="{{ApiConfigurableProduct.price}}"/> + </actionGroup> + + <!-- Add product with selected option to the cart --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectFirstOptionValue"> + <argument name="attributeLabel" value="$$createConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$"/> + </actionGroup> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addFirstOptionToCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + </actionGroup> + + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectSecondOptionValue"> + <argument name="attributeLabel" value="$$createConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$"/> + </actionGroup> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addSecondOptionToCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + </actionGroup> + + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectThirdOptionValue"> + <argument name="attributeLabel" value="$$createConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$"/> + </actionGroup> + <actionGroup ref="AddToCartFromStorefrontProductPageActionGroup" stepKey="addThirdOptionToCart"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + </actionGroup> + + <!--Assert product price in the cart --> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCartPage"/> + <waitForElementVisible selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="waitForPriceAppears"/> + <see userInput="{$firstOptionPrice}" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeFirstOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForFirstProductOption"/> + <see userInput="{$secondOptionPrice}" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeSecondOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForSecondProductOption"/> + <see userInput="{{ApiConfigurableProduct.price}}" selector="{{CheckoutCartProductSection.ProductPriceByOption($$createConfigProductAttributeThirdOption.option[store_labels][1][label]$$)}}" stepKey="assertProductPriceForThirdProductOption"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml index fcf5e2c038047..05f30fd6fcbde 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml @@ -11,14 +11,14 @@ <annotations> <features value="CatalogRuleConfigurable"/> <stories value="Apply catalog price rule"/> - <title value="Apply catalog price rule for configurable product with options"/> - <description value="Admin should be able to apply the catalog rule for configurable product with options"/> + <title value="DEPRECATED. Apply catalog price rule for configurable product with options"/> + <description value="DEPRECATED. Admin should be able to apply the catalog rule for configurable product with options"/> <severity value="CRITICAL"/> <testCaseId value="MC-14062"/> <group value="catalogRuleConfigurable"/> <group value="mtf_migrated"/> <skip> - <issueId value="MC-17140"/> + <issueId value="DEPRECATED">Use AdminApplyCatalogRuleForConfigurableProductWithOptions2Test instead</issueId> </skip> </annotations> <before> diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 3506437ea038d..30a7c723940e2 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -385,6 +385,8 @@ public function addFieldToFilter($field, $condition = null) public function clear() { $this->searchResult = null; + $this->setFlag('has_category_filter', false); + return parent::clear(); } @@ -394,6 +396,8 @@ public function clear() protected function _reset() { $this->searchResult = null; + $this->setFlag('has_category_filter', false); + return parent::_reset(); } @@ -423,7 +427,11 @@ public function _loadEntities($printQuery = false, $logQuery = false) throw $e; } + $position = 0; foreach ($rows as $value) { + if ($this->getFlag('has_category_filter')) { + $value['cat_index_position'] = $position++; + } $object = $this->getNewEmptyItem()->setData($value); $this->addItem($object); if (isset($this->_itemsById[$object->getId()])) { @@ -432,6 +440,9 @@ public function _loadEntities($printQuery = false, $logQuery = false) $this->_itemsById[$object->getId()] = [$object]; } } + if ($this->getFlag('has_category_filter')) { + $this->setFlag('has_category_filter', false); + } return $this; } @@ -669,6 +680,7 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category) if ($this->defaultFilterStrategyApplyChecker->isApplicable()) { parent::addCategoryFilter($category); } else { + $this->setFlag('has_category_filter', true); $this->_productLimitationPrice(); } diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontQuickSearchWithPaginationActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontQuickSearchWithPaginationActionGroup.xml new file mode 100644 index 0000000000000..95ebfa40adb26 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontQuickSearchWithPaginationActionGroup.xml @@ -0,0 +1,22 @@ +<?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="StorefrontQuickSearchWithPaginationActionGroup"> + <annotations> + <description>Navigate to catalog search page with prepared GET params to get search results with particular page number.</description> + </annotations> + <arguments> + <argument name="phrase" type="string" defaultValue="{{_defaultProduct.name}}"/> + <argument name="pageNumber" type="string" defaultValue="1"/> + </arguments> + <amOnPage url="{{StorefrontCatalogSearchPage.url}}?q={{phrase}}&p={{pageNumber}}" stepKey="navigateToCatalogSearchPageWithPreparedRequest"/> + <waitForPageLoad stepKey="waitForCatalogSearchPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml index 6c475ddc60a95..fab28dc357016 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml @@ -25,14 +25,23 @@ <requiredEntity createDataKey="createCategory1"/> </createData> + <!-- Perform reindex and flush cache --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage1"/> <waitForPageLoad stepKey="waitForPageLoad1"/> </before> <after> - <actionGroup ref="logout" stepKey="logoutOfAdmin1"/> - <deleteData createDataKey="createProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="createCategory1" stepKey="deleteCategory1"/> + + <!-- Delete all search terms --> + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <comment userInput="Delete all search terms" stepKey="deleteAllSearchTermsComment"/> + <actionGroup ref="AdminDeleteAllSearchTermsActionGroup" stepKey="deleteAllSearchTerms"/> + + <actionGroup ref="logout" stepKey="logoutOfAdmin1"/> </after> <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName1"> diff --git a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml index c358062b88a41..e875a48aa29dc 100644 --- a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml @@ -38,7 +38,7 @@ <label>Autocomplete Limit</label> <validate>validate-digits</validate> </field> - <field id="enable_eav_indexer" translate="label" type="select" sortOrder="18" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="enable_eav_indexer" translate="label" type="select" sortOrder="18" showInDefault="1" canRestore="1"> <label>Enable EAV Indexer</label> <comment>Enable/Disable Product EAV indexer to improve indexation speed. Make sure that indexer is not used by 3rd party extensions.</comment> <depends> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryAccessibleWhenSuffixIsNullTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryAccessibleWhenSuffixIsNullTest.xml index ef8f2b6b1a3e2..6674c55064169 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryAccessibleWhenSuffixIsNullTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryAccessibleWhenSuffixIsNullTest.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCategoryAccessibleWhenSuffixIsNullTest"> <annotations> + <stories value="Url rewrites"/> <title value="Storefront category is accessible when url suffix is set to null test"/> <description value="Check no crash occurs on Category page when catalog/seo/category_url_suffix is set to null"/> <features value="CatalogUrlRewrite"/> diff --git a/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml b/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml index ad0ff68192af4..75d395473f969 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogUrlRewrite/etc/adminhtml/system.xml @@ -28,7 +28,7 @@ <label>Create Permanent Redirect for URLs if URL Key Changed</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="generate_category_product_rewrites" translate="label comment" type="select" sortOrder="6" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="generate_category_product_rewrites" translate="label comment" type="select" sortOrder="6" showInDefault="1" canRestore="1"> <label>Generate "category/product" URL Rewrites</label> <backend_model>Magento\CatalogUrlRewrite\Model\TableCleaner</backend_model> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index 9e47830debfc4..4a75c806aa37b 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -6,15 +6,30 @@ namespace Magento\CatalogWidget\Block\Product; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Block\Product\AbstractProduct; +use Magento\Catalog\Block\Product\Widget\Html\Pager; use Magento\Catalog\Model\Product; -use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Catalog\Pricing\Price\FinalPrice; +use Magento\CatalogWidget\Model\Rule; use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\Http\Context; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject\IdentityInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Url\EncoderInterface; use Magento\Framework\View\LayoutFactory; +use Magento\Framework\View\LayoutInterface; +use Magento\Rule\Model\Condition\Combine; +use Magento\Rule\Model\Condition\Sql\Builder; use Magento\Widget\Block\BlockInterface; -use Magento\Framework\Url\EncoderInterface; +use Magento\Widget\Helper\Conditions; /** * Catalog Products List widget block @@ -22,7 +37,7 @@ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ -class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implements BlockInterface, IdentityInterface +class ProductsList extends AbstractProduct implements BlockInterface, IdentityInterface { /** * Default value for products count that will be shown @@ -32,7 +47,7 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem /** * Name of request parameter for page number value * - * @deprecated + * @deprecated @see $this->getData('page_var_name') */ const PAGE_VAR_NAME = 'np'; @@ -49,41 +64,41 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem /** * Instance of pager block * - * @var \Magento\Catalog\Block\Product\Widget\Html\Pager + * @var Pager */ protected $pager; /** - * @var \Magento\Framework\App\Http\Context + * @var Context */ protected $httpContext; /** * Catalog product visibility * - * @var \Magento\Catalog\Model\Product\Visibility + * @var Visibility */ protected $catalogProductVisibility; /** * Product collection factory * - * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory + * @var CollectionFactory */ protected $productCollectionFactory; /** - * @var \Magento\Rule\Model\Condition\Sql\Builder + * @var Builder */ protected $sqlBuilder; /** - * @var \Magento\CatalogWidget\Model\Rule + * @var Rule */ protected $rule; /** - * @var \Magento\Widget\Helper\Conditions + * @var Conditions */ protected $conditionsHelper; @@ -105,7 +120,7 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem private $layoutFactory; /** - * @var \Magento\Framework\Url\EncoderInterface|null + * @var EncoderInterface|null */ private $urlEncoder; @@ -114,29 +129,36 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem */ private $rendererListBlock; + /** + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + /** * @param \Magento\Catalog\Block\Product\Context $context - * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory - * @param \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility - * @param \Magento\Framework\App\Http\Context $httpContext - * @param \Magento\Rule\Model\Condition\Sql\Builder $sqlBuilder - * @param \Magento\CatalogWidget\Model\Rule $rule - * @param \Magento\Widget\Helper\Conditions $conditionsHelper + * @param CollectionFactory $productCollectionFactory + * @param Visibility $catalogProductVisibility + * @param Context $httpContext + * @param Builder $sqlBuilder + * @param Rule $rule + * @param Conditions $conditionsHelper + * @param CategoryRepositoryInterface $categoryRepository * @param array $data * @param Json|null $json * @param LayoutFactory|null $layoutFactory - * @param \Magento\Framework\Url\EncoderInterface|null $urlEncoder + * @param EncoderInterface|null $urlEncoder * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Catalog\Block\Product\Context $context, - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, - \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility, - \Magento\Framework\App\Http\Context $httpContext, - \Magento\Rule\Model\Condition\Sql\Builder $sqlBuilder, - \Magento\CatalogWidget\Model\Rule $rule, - \Magento\Widget\Helper\Conditions $conditionsHelper, + CollectionFactory $productCollectionFactory, + Visibility $catalogProductVisibility, + Context $httpContext, + Builder $sqlBuilder, + Rule $rule, + Conditions $conditionsHelper, + CategoryRepositoryInterface $categoryRepository, array $data = [], Json $json = null, LayoutFactory $layoutFactory = null, @@ -151,6 +173,7 @@ public function __construct( $this->json = $json ?: ObjectManager::getInstance()->get(Json::class); $this->layoutFactory = $layoutFactory ?: ObjectManager::getInstance()->get(LayoutFactory::class); $this->urlEncoder = $urlEncoder ?: ObjectManager::getInstance()->get(EncoderInterface::class); + $this->categoryRepository = $categoryRepository; parent::__construct( $context, $data @@ -171,10 +194,14 @@ protected function _construct() ->addColumnCountLayoutDepend('2columns-right', 4) ->addColumnCountLayoutDepend('3columns', 3); - $this->addData([ - 'cache_lifetime' => 86400, - 'cache_tags' => [\Magento\Catalog\Model\Product::CACHE_TAG, - ], ]); + $this->addData( + [ + 'cache_lifetime' => 86400, + 'cache_tags' => [ + Product::CACHE_TAG, + ], + ] + ); } /** @@ -182,6 +209,7 @@ protected function _construct() * * @return array * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) + * @throws NoSuchEntityException */ public function getCacheKeyInfo() { @@ -195,7 +223,7 @@ public function getCacheKeyInfo() $this->_storeManager->getStore()->getId(), $this->_design->getDesignTheme()->getId(), $this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_GROUP), - (int) $this->getRequest()->getParam($this->getData('page_var_name'), 1), + (int)$this->getRequest()->getParam($this->getData('page_var_name'), 1), $this->getProductsPerPage(), $this->getProductsCount(), $conditions, @@ -210,7 +238,7 @@ public function getCacheKeyInfo() * @SuppressWarnings(PHPMD.NPathComplexity) */ public function getProductPriceHtml( - \Magento\Catalog\Model\Product $product, + Product $product, $priceType = null, $renderZone = \Magento\Framework\Pricing\Render::ZONE_ITEM_LIST, array $arguments = [] @@ -239,7 +267,7 @@ public function getProductPriceHtml( } $price = $priceRender->render( - \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE, + FinalPrice::PRICE_CODE, $product, $arguments ); @@ -253,7 +281,7 @@ public function getProductPriceHtml( protected function getDetailsRendererList() { if (empty($this->rendererListBlock)) { - /** @var $layout \Magento\Framework\View\LayoutInterface */ + /** @var $layout LayoutInterface */ $layout = $this->layoutFactory->create(['cacheable' => false]); $layout->getUpdate()->addHandle('catalog_widget_product_list')->load(); $layout->generateXml(); @@ -294,12 +322,13 @@ protected function _beforeToHtml() /** * Prepare and return product collection * - * @return \Magento\Catalog\Model\ResourceModel\Product\Collection + * @return Collection * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) + * @throws LocalizedException */ public function createCollection() { - /** @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */ + /** @var $collection Collection */ $collection = $this->productCollectionFactory->create(); if ($this->getData('store_id') !== null) { @@ -327,10 +356,38 @@ public function createCollection() return $collection; } + /** + * Update conditions if the category is an anchor category + * + * @param array $condition + * @return array + */ + private function updateAnchorCategoryConditions(array $condition): array + { + if (array_key_exists('value', $condition)) { + $categoryId = $condition['value']; + + try { + $category = $this->categoryRepository->get($categoryId, $this->_storeManager->getStore()->getId()); + } catch (NoSuchEntityException $e) { + return $condition; + } + + $children = $category->getIsAnchor() ? $category->getChildren(true) : []; + if ($children) { + $children = explode(',', $children); + $condition['operator'] = "()"; + $condition['value'] = array_merge([$categoryId], $children); + } + } + + return $condition; + } + /** * Get conditions * - * @return \Magento\Rule\Model\Condition\Combine + * @return Combine */ protected function getConditions() { @@ -343,10 +400,14 @@ protected function getConditions() } foreach ($conditions as $key => $condition) { - if (!empty($condition['attribute']) - && in_array($condition['attribute'], ['special_from_date', 'special_to_date']) - ) { - $conditions[$key]['value'] = date('Y-m-d H:i:s', strtotime($condition['value'])); + if (!empty($condition['attribute'])) { + if (in_array($condition['attribute'], ['special_from_date', 'special_to_date'])) { + $conditions[$key]['value'] = date('Y-m-d H:i:s', strtotime($condition['value'])); + } + + if ($condition['attribute'] == 'category_ids') { + $conditions[$key] = $this->updateAnchorCategoryConditions($condition); + } } } @@ -412,13 +473,14 @@ protected function getPageSize() * Render pagination HTML * * @return string + * @throws LocalizedException */ public function getPagerHtml() { if ($this->showPager() && $this->getProductCollection()->getSize() > $this->getProductsPerPage()) { if (!$this->pager) { $this->pager = $this->getLayout()->createBlock( - \Magento\Catalog\Block\Product\Widget\Html\Pager::class, + Pager::class, $this->getWidgetPagerBlockName() ); @@ -448,12 +510,12 @@ public function getIdentities() if ($this->getProductCollection()) { foreach ($this->getProductCollection() as $product) { if ($product instanceof IdentityInterface) { - $identities = array_merge($identities, $product->getIdentities()); + $identities += $product->getIdentities(); } } } - return $identities ?: [\Magento\Catalog\Model\Product::CACHE_TAG]; + return $identities ?: [Product::CACHE_TAG]; } /** @@ -475,7 +537,7 @@ public function getTitle() private function getPriceCurrency() { if ($this->priceCurrency === null) { - $this->priceCurrency = \Magento\Framework\App\ObjectManager::getInstance() + $this->priceCurrency = ObjectManager::getInstance() ->get(PriceCurrencyInterface::class); } return $this->priceCurrency; diff --git a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php index a712ae91cbfa9..eca994de0892f 100644 --- a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php +++ b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php @@ -14,7 +14,8 @@ use Magento\Store\Model\Store; /** - * Class Product + * Rule product condition data model + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Product extends \Magento\Rule\Model\Condition\Product\AbstractProduct @@ -250,7 +251,7 @@ protected function addNotGlobalAttribute( public function getMappedSqlField() { $result = ''; - if (in_array($this->getAttribute(), ['category_ids', 'sku'])) { + if (in_array($this->getAttribute(), ['category_ids', 'sku', 'attribute_set_id'])) { $result = parent::getMappedSqlField(); } elseif (isset($this->joinedAttributes[$this->getAttribute()])) { $result = $this->joinedAttributes[$this->getAttribute()]; diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php index 219cae6829299..7dceb41d263ec 100644 --- a/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php +++ b/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php @@ -5,53 +5,65 @@ */ namespace Magento\CatalogWidget\Test\Unit\Model\Rule\Condition; +use Magento\Catalog\Model\ProductCategoryList; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Catalog\Model\ResourceModel\Product; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\CatalogWidget\Model\Rule\Condition\Product as ProductWidget; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\AbstractEntity; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\SalesRule\Model\Rule; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class ProductTest extends \PHPUnit\Framework\TestCase +class ProductTest extends TestCase { /** - * @var \Magento\CatalogWidget\Model\Rule\Condition\Product + * @var ProductWidget */ private $model; /** - * @var \Magento\Catalog\Model\ResourceModel\Product|\PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ - private $productResource; + private $attributeMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Product|MockObject */ - private $attributeMock; + private $productResource; /** * @inheritdoc - * - * @return void */ protected function setUp() { $objectManagerHelper = new ObjectManager($this); - $eavConfig = $this->createMock(\Magento\Eav\Model\Config::class); - $this->attributeMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class); + $eavConfig = $this->createMock(Config::class); + $this->attributeMock = $this->createMock(Attribute::class); $eavConfig->expects($this->any())->method('getAttribute')->willReturn($this->attributeMock); - $ruleMock = $this->createMock(\Magento\SalesRule\Model\Rule::class); - $storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $ruleMock = $this->createMock(Rule::class); + $storeManager = $this->createMock(StoreManagerInterface::class); + $storeMock = $this->createMock(StoreInterface::class); $storeManager->expects($this->any())->method('getStore')->willReturn($storeMock); - $this->productResource = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class); + $this->productResource = $this->createMock(Product::class); $this->productResource->expects($this->once())->method('loadAllAttributes')->willReturnSelf(); $this->productResource->expects($this->once())->method('getAttributesByCode')->willReturn([]); - $productCategoryList = $this->getMockBuilder(\Magento\Catalog\Model\ProductCategoryList::class) + $productCategoryList = $this->getMockBuilder(ProductCategoryList::class) ->disableOriginalConstructor() ->getMock(); $this->model = $objectManagerHelper->getObject( - \Magento\CatalogWidget\Model\Rule\Condition\Product::class, + ProductWidget::class, [ 'config' => $eavConfig, 'storeManager' => $storeManager, @@ -72,8 +84,8 @@ protected function setUp() */ public function testAddToCollection() { - $collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); - $selectMock = $this->createMock(\Magento\Framework\DB\Select::class); + $collectionMock = $this->createMock(Collection::class); + $selectMock = $this->createMock(Select::class); $collectionMock->expects($this->once())->method('getSelect')->willReturn($selectMock); $selectMock->expects($this->any())->method('join')->willReturnSelf(); $this->attributeMock->expects($this->any())->method('getAttributeCode')->willReturn('code'); @@ -83,10 +95,10 @@ public function testAddToCollection() $this->attributeMock->expects($this->once())->method('isScopeGlobal')->willReturn(true); $this->attributeMock->expects($this->once())->method('getBackendType')->willReturn('multiselect'); - $entityMock = $this->createMock(\Magento\Eav\Model\Entity\AbstractEntity::class); + $entityMock = $this->createMock(AbstractEntity::class); $entityMock->expects($this->once())->method('getLinkField')->willReturn('entitiy_id'); $this->attributeMock->expects($this->once())->method('getEntity')->willReturn($entityMock); - $connection = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $connection = $this->createMock(AdapterInterface::class); $this->productResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($connection); @@ -102,5 +114,7 @@ public function testGetMappedSqlFieldSku() { $this->model->setAttribute('sku'); $this->assertEquals('e.sku', $this->model->getMappedSqlField()); + $this->model->setAttribute('attribute_set_id'); + $this->assertEquals('e.attribute_set_id', $this->model->getMappedSqlField()); } } diff --git a/app/code/Magento/Checkout/Block/Cart/Sidebar.php b/app/code/Magento/Checkout/Block/Cart/Sidebar.php index c5e309df3cad6..147782e501ae4 100644 --- a/app/code/Magento/Checkout/Block/Cart/Sidebar.php +++ b/app/code/Magento/Checkout/Block/Cart/Sidebar.php @@ -83,7 +83,8 @@ public function getConfig() 'minicartMaxItemsVisible' => $this->getMiniCartMaxItemsCount(), 'websiteId' => $this->_storeManager->getStore()->getWebsiteId(), 'maxItemsToDisplay' => $this->getMaxItemsToDisplay(), - 'storeId' => $this->_storeManager->getStore()->getId() + 'storeId' => $this->_storeManager->getStore()->getId(), + 'storeGroupId' => $this->_storeManager->getStore()->getStoreGroupId() ]; } diff --git a/app/code/Magento/Checkout/CustomerData/DefaultItem.php b/app/code/Magento/Checkout/CustomerData/DefaultItem.php index 21580d1275d0c..23d5827dc1916 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 item in checkout customer data */ class DefaultItem extends AbstractItem { @@ -78,7 +78,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function doGetItemData() { @@ -121,6 +121,8 @@ protected function getOptionList() } /** + * Get product for thumbnail + * * @return \Magento\Catalog\Model\Product * @codeCoverageIgnore */ @@ -130,6 +132,8 @@ protected function getProductForThumbnail() } /** + * Get product + * * @return \Magento\Catalog\Model\Product * @codeCoverageIgnore */ diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index fdf49d6765a29..87585e4bf327f 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -31,7 +31,7 @@ use Magento\Ui\Component\Form\Element\Multiline; /** - * Default Config Provider + * Default Config Provider for checkout * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml index 0e36e0ecbe71f..fa169373c1096 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionDropDownActionGroup.xml @@ -10,13 +10,13 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontSelectOptionDropDownActionGroup"> <annotations> - <description>Selects the provided Product Option Value under the provided Product Option Title on a Storefront Product page.</description> + <description>DEPRECATED. Please use StorefrontProductPageSelectDropDownOptionValueActionGroup instead. Selects the provided Product Option Value under the provided Product Option Title on a Storefront Product page.</description> </annotations> <arguments> <argument name="optionTitle" defaultValue="ProductOptionDropDown"/> <argument name="option" defaultValue="ProductOptionValueDropdown2.title"/> </arguments> - + <selectOption selector="{{StorefrontProductInfoMainSection.productOptionSelect(optionTitle.title)}}" userInput="{{option}}" stepKey="fillOptionDropDown"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml index c0de6f8f8466f..ba75a03d6ff04 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontSelectOptionRadioButtonActionGroup.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontSelectOptionRadioButtonActionGroup"> <annotations> - <description>Checks the provided Product Option radio button for the provided Product Option Price on a Storefront Product page.</description> + <description>DEPRECATED. Please use StorefrontProductPageSelectRadioButtonOptionValueActionGroup instead. Checks the provided Product Option radio button for the provided Product Option Price on a Storefront Product page.</description> </annotations> <arguments> <argument name="optionTitle" defaultValue="ProductOptionRadiobutton"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml index 07d29aa0aac4a..92eae461019a2 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithDifferentShippingAndBillingAddressAndProductWithTierPricesTest.xml @@ -77,6 +77,7 @@ <checkOption selector="{{CheckoutPaymentSection.bankTransfer}}" stepKey="selectBankTransfer"/> <waitForElementVisible selector="{{CheckoutPaymentSection.billingAddressNotSameBankTransferCheckbox}}" stepKey="waitForElementToBeVisible"/> <uncheckOption selector="{{CheckoutPaymentSection.billingAddressNotSameBankTransferCheckbox}}" stepKey="uncheckSameBillingAndShippingAddress"/> + <waitForElementVisible selector="{{CheckoutShippingSection.editActiveAddressButton}}" stepKey="waitForEditButtonToBeVisible"/> <conditionalClick selector="{{CheckoutShippingSection.editActiveAddressButton}}" dependentSelector="{{CheckoutShippingSection.editActiveAddressButton}}" visible="true" stepKey="clickEditButton"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml index 1a427bbe77166..f11d25a30f073 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontNotApplicableShippingMethodInReviewAndPaymentStepTest.xml @@ -27,12 +27,6 @@ <magentoCLI command="config:set {{AdminFreeshippingActiveConfigData.path}} {{AdminFreeshippingActiveConfigData.enabled}}" stepKey="enableFreeShippingMethod" /> <magentoCLI command="config:set {{AdminFreeshippingMinimumOrderAmountConfigData.path}} {{AdminFreeshippingMinimumOrderAmountConfigData.hundred}}" stepKey="setFreeShippingMethodMinimumOrderAmountToBe100" /> - <!--Set Fedex configs data--> - <magentoCLI command="config:set {{AdminFedexEnableForCheckoutConfigData.path}} {{AdminFedexEnableForCheckoutConfigData.value}}" stepKey="enableCheckout"/> - <magentoCLI command="config:set {{AdminFedexEnableSandboxModeConfigData.path}} {{AdminFedexEnableSandboxModeConfigData.value}}" stepKey="enableSandbox"/> - <magentoCLI command="config:set {{AdminFedexEnableDebugConfigData.path}} {{AdminFedexEnableDebugConfigData.value}}" stepKey="enableDebug"/> - <magentoCLI command="config:set {{AdminFedexEnableShowMethodConfigData.path}} {{AdminFedexEnableShowMethodConfigData.value}}" stepKey="enableShowMethod"/> - <!--Set StoreInformation configs data--> <magentoCLI command="config:set {{AdminGeneralSetStoreNameConfigData.path}} '{{AdminGeneralSetStoreNameConfigData.value}}'" stepKey="setStoreInformationName"/> <magentoCLI command="config:set {{AdminGeneralSetStorePhoneConfigData.path}} {{DE_Address_Berlin_Not_Default_Address.telephone}}" stepKey="setStoreInformationPhone"/> @@ -75,10 +69,6 @@ <magentoCLI command="config:set {{AdminFreeshippingMinimumOrderAmountConfigData.path}} {{AdminFreeshippingMinimumOrderAmountConfigData.default}}" stepKey="setFreeShippingMethodMinimumOrderAmountAsDefault" /> <magentoCLI command="config:set {{AdminFreeshippingActiveConfigData.path}} {{AdminFreeshippingActiveConfigData.disabled}}" stepKey="disableFreeShippingMethod" /> <!--Reset configs--> - <magentoCLI command="config:set {{AdminFedexDisableForCheckoutConfigData.path}} {{AdminFedexDisableForCheckoutConfigData.value}}" stepKey="disableCheckout"/> - <magentoCLI command="config:set {{AdminFedexDisableSandboxModeConfigData.path}} {{AdminFedexDisableSandboxModeConfigData.value}}" stepKey="disableSandbox"/> - <magentoCLI command="config:set {{AdminFedexDisableDebugConfigData.path}} {{AdminFedexDisableDebugConfigData.value}}" stepKey="disableDebug"/> - <magentoCLI command="config:set {{AdminFedexDisableShowMethodConfigData.path}} {{AdminFedexDisableShowMethodConfigData.value}}" stepKey="disableShowMethod"/> <magentoCLI command="config:set {{AdminGeneralSetStoreNameConfigData.path}} ''" stepKey="setStoreInformationName"/> <magentoCLI command="config:set {{AdminGeneralSetStorePhoneConfigData.path}} ''" stepKey="setStoreInformationPhone"/> <magentoCLI command="config:set {{AdminGeneralSetCityConfigData.path}} ''" stepKey="setStoreInformationCity"/> @@ -187,7 +177,7 @@ <!-- Assert Shipping total is not yet calculated --> <actionGroup ref="AssertStorefrontNotCalculatedValueInShippingTotalInOrderSummaryActionGroup" stepKey="assertNotYetCalculated2"/> - <!-- Assert order cannot be placed and error message will shown. --> + <!-- Assert order cannot be placed and error message will shown. --> <actionGroup ref="AssertStorefrontOrderCannotBePlacedActionGroup" stepKey="assertOrderCannotBePlaced2"> <argument name="error" value="The shipping method is missing. Select the shipping method and try again."/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php index f69ced3b094c7..fdf63b4ebe1ed 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php @@ -145,7 +145,8 @@ public function testGetConfig() 'minicartMaxItemsVisible' => 3, 'websiteId' => 100, 'maxItemsToDisplay' => 8, - 'storeId' => null + 'storeId' => null, + 'storeGroupId' => null ]; $valueMap = [ diff --git a/app/code/Magento/Checkout/etc/adminhtml/system.xml b/app/code/Magento/Checkout/etc/adminhtml/system.xml index 399474a36bfc7..7454c2b6524f3 100644 --- a/app/code/Magento/Checkout/etc/adminhtml/system.xml +++ b/app/code/Magento/Checkout/etc/adminhtml/system.xml @@ -21,7 +21,7 @@ <label>Allow Guest Checkout</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="display_billing_address_on" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="display_billing_address_on" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Display Billing Address On</label> <source_model>\Magento\Checkout\Model\Adminhtml\BillingAddressDisplayOptions</source_model> </field> @@ -32,7 +32,7 @@ </group> <group id="cart" translate="label" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Shopping Cart</label> - <field id="delete_quote_after" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="delete_quote_after" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Quote Lifetime (days)</label> <validate>validate-zero-or-greater validate-digits</validate> </field> @@ -49,9 +49,9 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> - <group id="cart_link" translate="label" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="cart_link" translate="label" sortOrder="3" showInDefault="1" showInWebsite="1"> <label>My Cart Link</label> - <field id="use_qty" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="use_qty" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Display Cart Summary</label> <source_model>Magento\Checkout\Model\Config\Source\Cart\Summary</source_model> </field> diff --git a/app/code/Magento/Checkout/etc/config.xml b/app/code/Magento/Checkout/etc/config.xml index f8c2e7ebcb503..c8408f6d902fa 100644 --- a/app/code/Magento/Checkout/etc/config.xml +++ b/app/code/Magento/Checkout/etc/config.xml @@ -11,6 +11,7 @@ <options> <onepage_checkout_enabled>1</onepage_checkout_enabled> <guest_checkout>1</guest_checkout> + <display_billing_address_on>0</display_billing_address_on> <max_items_display_count>10</max_items_display_count> </options> <cart> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js b/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js index 34f1700749794..5adbd9356a8d8 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/select-payment-method.js @@ -7,7 +7,7 @@ * @api */ define([ - '../model/quote' + 'Magento_Checkout/js/model/quote' ], function (quote) { 'use strict'; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js index e728a5c0fcdd5..f850386890470 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -255,9 +255,11 @@ function ( return attribute.label; } - resultAttribute = _.findWhere(this.source.get('customAttributes')[attribute['attribute_code']], { - value: attribute.value - }); + if (typeof this.source.get('customAttributes') !== 'undefined') { + resultAttribute = _.findWhere(this.source.get('customAttributes')[attribute['attribute_code']], { + value: attribute.value + }); + } return resultAttribute && resultAttribute.label || attribute.value; } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js index 4adc1cd88c0ae..c77e72e38107a 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js @@ -162,10 +162,11 @@ define([ /** * Get cart param by name. + * * @param {String} name * @returns {*} */ - getCartParam: function (name) { + getCartParamUnsanitizedHtml: function (name) { if (!_.isUndefined(name)) { if (!this.cart.hasOwnProperty(name)) { this.cart[name] = ko.observable(); @@ -175,12 +176,21 @@ define([ return this.cart[name](); }, + /** + * @deprecated please use getCartParamUnsanitizedHtml. + * @param {String} name + * @returns {*} + */ + getCartParam: function (name) { + return this.getCartParamUnsanitizedHtml(name); + }, + /** * Returns array of cart items, limited by 'maxItemsToDisplay' setting * @returns [] */ getCartItems: function () { - var items = this.getCartParam('items') || []; + var items = this.getCartParamUnsanitizedHtml('items') || []; items = items.slice(parseInt(-this.maxItemsToDisplay, 10)); @@ -192,7 +202,7 @@ define([ * @returns {Number} */ getCartLineItemsCount: function () { - var items = this.getCartParam('items') || []; + var items = this.getCartParamUnsanitizedHtml('items') || []; return parseInt(items.length, 10); } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js index 15ae5d68534b8..17ccf3863a875 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js @@ -203,7 +203,7 @@ define([ */ isPaymentMethodsAvailable: function () { return _.some(this.paymentGroupsList(), function (group) { - return this.getRegion(group.displayArea)().length; + return this.regionHasElements(group.displayArea); }, this); }, diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-address/address-renderer/default.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-address/address-renderer/default.js index 939a2af1a25aa..1f8cc90fe1622 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-address/address-renderer/default.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-address/address-renderer/default.js @@ -65,9 +65,11 @@ define([ return attribute.label; } - resultAttribute = _.findWhere(this.source.get('customAttributes')[attribute['attribute_code']], { - value: attribute.value - }); + if (typeof this.source.get('customAttributes') !== 'undefined') { + resultAttribute = _.findWhere(this.source.get('customAttributes')[attribute['attribute_code']], { + value: attribute.value + }); + } return resultAttribute && resultAttribute.label || attribute.value; }, diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information/address-renderer/default.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information/address-renderer/default.js index 009178cbb19b9..6ec9fde554dc2 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information/address-renderer/default.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information/address-renderer/default.js @@ -42,9 +42,11 @@ define([ return attribute.label; } - resultAttribute = _.findWhere(this.source.get('customAttributes')[attribute['attribute_code']], { - value: attribute.value - }); + if (typeof this.source.get('customAttributes') !== 'undefined') { + resultAttribute = _.findWhere(this.source.get('customAttributes')[attribute['attribute_code']], { + value: attribute.value + }); + } return resultAttribute && resultAttribute.label || attribute.value; } diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html index 0719a7d01ec70..c5fd6d545702b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/content.html @@ -56,7 +56,7 @@ " translate="'Proceed to Checkout'" /> - <div data-bind="html: getCartParam('extra_actions')"></div> + <div data-bind="html: getCartParamUnsanitizedHtml('extra_actions')"></div> </div> </div> </if> @@ -97,7 +97,7 @@ </div> </div> - <div id="minicart-widgets" class="minicart-widgets" if="getRegion('promotion').length"> + <div id="minicart-widgets" class="minicart-widgets" if="regionHasElements('promotion')"> <each args="getRegion('promotion')" render=""/> </div> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html b/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html index b7be5efee383e..77b801ec0e826 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html @@ -8,7 +8,7 @@ class="items payment-methods"> <div repeat="foreach: paymentGroupsList, item: '$group'" class="payment-group"> - <div if="getRegion($group().displayArea)().length" + <div if="regionHasElements($group().displayArea)" translate="getGroupTitle($group)" class="step-title" data-role="title"> diff --git a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php index fbceca0906702..95330c9d01381 100644 --- a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php +++ b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/GuestValidation.php @@ -11,7 +11,7 @@ use Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter; /** - * Class GuestValidation + * Guest checkout agreements validation. * * Plugin that checks if checkout agreement enabled and validates all agreements. * Current plugin is duplicate from Magento\CheckoutAgreements\Model\Checkout\Plugin\Validation due to different @@ -58,6 +58,8 @@ public function __construct( } /** + * Validates agreements before save payment information and order placing. + * * @param \Magento\Checkout\Api\GuestPaymentInformationManagementInterface $subject * @param string $cartId * @param string $email @@ -80,28 +82,8 @@ public function beforeSavePaymentInformationAndPlaceOrder( } /** - * @param \Magento\Checkout\Api\GuestPaymentInformationManagementInterface $subject - * @param string $cartId - * @param string $email - * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod - * @param \Magento\Quote\Api\Data\AddressInterface|null $billingAddress - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSavePaymentInformation( - \Magento\Checkout\Api\GuestPaymentInformationManagementInterface $subject, - $cartId, - $email, - \Magento\Quote\Api\Data\PaymentInterface $paymentMethod, - \Magento\Quote\Api\Data\AddressInterface $billingAddress = null - ) { - if ($this->isAgreementEnabled()) { - $this->validateAgreements($paymentMethod); - } - } - - /** + * Validates agreements. + * * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod * @throws \Magento\Framework\Exception\CouldNotSaveException * @return void @@ -123,7 +105,8 @@ private function validateAgreements(\Magento\Quote\Api\Data\PaymentInterface $pa } /** - * Verify if agreement validation needed + * Verify if agreement validation needed. + * * @return bool */ private function isAgreementEnabled() diff --git a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php index 67e2a6c9ec334..5338a16004a00 100644 --- a/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php +++ b/app/code/Magento/CheckoutAgreements/Model/Checkout/Plugin/Validation.php @@ -7,23 +7,24 @@ namespace Magento\CheckoutAgreements\Model\Checkout\Plugin; use Magento\CheckoutAgreements\Model\AgreementsProvider; -use Magento\Store\Model\ScopeInterface; use Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Store\Model\ScopeInterface; /** - * Class Validation + * Class Validation validates the agreement based on the payment method */ class Validation { /** * @var \Magento\Framework\App\Config\ScopeConfigInterface */ - protected $scopeConfiguration; + private $scopeConfiguration; /** * @var \Magento\Checkout\Api\AgreementsValidatorInterface */ - protected $agreementsValidator; + private $agreementsValidator; /** * @var \Magento\CheckoutAgreements\Api\CheckoutAgreementsListInterface @@ -35,25 +36,37 @@ class Validation */ private $activeStoreAgreementsFilter; + /** + * Quote repository. + * + * @var \Magento\Quote\Api\CartRepositoryInterface + */ + private $quoteRepository; + /** * @param \Magento\Checkout\Api\AgreementsValidatorInterface $agreementsValidator * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfiguration * @param \Magento\CheckoutAgreements\Api\CheckoutAgreementsListInterface $checkoutAgreementsList * @param ActiveStoreAgreementsFilter $activeStoreAgreementsFilter + * @param CartRepositoryInterface $quoteRepository */ public function __construct( \Magento\Checkout\Api\AgreementsValidatorInterface $agreementsValidator, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfiguration, \Magento\CheckoutAgreements\Api\CheckoutAgreementsListInterface $checkoutAgreementsList, - \Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter $activeStoreAgreementsFilter + \Magento\CheckoutAgreements\Model\Api\SearchCriteria\ActiveStoreAgreementsFilter $activeStoreAgreementsFilter, + CartRepositoryInterface $quoteRepository ) { $this->agreementsValidator = $agreementsValidator; $this->scopeConfiguration = $scopeConfiguration; $this->checkoutAgreementsList = $checkoutAgreementsList; $this->activeStoreAgreementsFilter = $activeStoreAgreementsFilter; + $this->quoteRepository = $quoteRepository; } /** + * Validates agreements before save payment information and order placing. + * * @param \Magento\Checkout\Api\PaymentInformationManagementInterface $subject * @param int $cartId * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod @@ -74,13 +87,16 @@ public function beforeSavePaymentInformationAndPlaceOrder( } /** + * Check validation before saving the payment information + * * @param \Magento\Checkout\Api\PaymentInformationManagementInterface $subject * @param int $cartId * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod * @param \Magento\Quote\Api\Data\AddressInterface|null $billingAddress - * @throws \Magento\Framework\Exception\CouldNotSaveException * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\CouldNotSaveException */ public function beforeSavePaymentInformation( \Magento\Checkout\Api\PaymentInformationManagementInterface $subject, @@ -88,12 +104,15 @@ public function beforeSavePaymentInformation( \Magento\Quote\Api\Data\PaymentInterface $paymentMethod, \Magento\Quote\Api\Data\AddressInterface $billingAddress = null ) { - if ($this->isAgreementEnabled()) { + $quote = $this->quoteRepository->getActive($cartId); + if ($this->isAgreementEnabled() && !$quote->getIsMultiShipping()) { $this->validateAgreements($paymentMethod); } } /** + * Validate agreements base on the payment method + * * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod * @throws \Magento\Framework\Exception\CouldNotSaveException * @return void @@ -115,10 +134,11 @@ protected function validateAgreements(\Magento\Quote\Api\Data\PaymentInterface $ } /** - * Verify if agreement validation needed + * Verify if agreement validation needed. + * * @return bool */ - protected function isAgreementEnabled() + private function isAgreementEnabled() { $isAgreementsEnabled = $this->scopeConfiguration->isSetFlag( AgreementsProvider::PATH_ENABLED, diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/DeleteTermActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/DeleteTermActionGroup.xml index b88101ce88ef6..13163e90efdbc 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/DeleteTermActionGroup.xml +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/DeleteTermActionGroup.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="DeleteTermActionGroup"> <arguments> - <argument name="term" /> + <argument name="term"/> </arguments> <amOnPage url="{{AdminTermsPage.url}}" stepKey="onTermGridPage"/> <waitForPageLoad stepKey="waitForAdminTermsGridPageLoad"/> @@ -19,8 +19,8 @@ <click selector="{{AdminTermGridSection.firstRowConditionId}}" stepKey="clickFirstRow"/> <waitForPageLoad stepKey="waitForEditTermPageLoad"/> <click selector="{{AdminEditTermFormSection.delete}}" stepKey="clickDeleteButton"/> + <waitForElementVisible selector="{{AdminEditTermFormSection.acceptPopupButton}}" stepKey="waitForElement"/> <click selector="{{AdminEditTermFormSection.acceptPopupButton}}" stepKey="clickDeleteOkButton"/> - <waitForPageLoad stepKey="waitForAdminTermsGridPageLoad2"/> - <see selector="{{AdminTermFormMessagesSection.successMessage}}" userInput="You deleted the condition." stepKey="seeSuccessMessage"/> + <waitForText selector="{{AdminTermFormMessagesSection.successMessage}}" userInput="You deleted the condition." stepKey="seeSuccessMessage"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php index 3d7b910c7abc5..b685d3edff275 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/GuestValidationTest.php @@ -10,7 +10,6 @@ use Magento\Store\Model\ScopeInterface; /** - * Class GuestValidationTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class GuestValidationTest extends \PHPUnit\Framework\TestCase @@ -109,7 +108,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrder() $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation( + $this->model->beforeSavePaymentInformationAndPlaceOrder( $this->subjectMock, $cartId, $email, @@ -144,7 +143,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation( + $this->model->beforeSavePaymentInformationAndPlaceOrder( $this->subjectMock, $cartId, $email, @@ -156,36 +155,4 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali "The order wasn't placed. First, agree to the terms and conditions, then try placing your order again." ); } - - public function testBeforeSavePaymentInformation() - { - $cartId = 100; - $email = 'email@example.com'; - $agreements = [1, 2, 3]; - $this->scopeConfigMock - ->expects($this->once()) - ->method('isSetFlag') - ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) - ->willReturn(true); - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); - $this->agreementsFilterMock->expects($this->once()) - ->method('buildSearchCriteria') - ->willReturn($searchCriteriaMock); - $this->checkoutAgreementsListMock->expects($this->once()) - ->method('getList') - ->with($searchCriteriaMock) - ->willReturn([1]); - $this->extensionAttributesMock->expects($this->once())->method('getAgreementIds')->willReturn($agreements); - $this->agreementsValidatorMock->expects($this->once())->method('isValid')->with($agreements)->willReturn(true); - $this->paymentMock->expects(static::atLeastOnce()) - ->method('getExtensionAttributes') - ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation( - $this->subjectMock, - $cartId, - $email, - $this->paymentMock, - $this->addressMock - ); - } } diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php index 7f11fad202401..64c91edb4e27d 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/Checkout/Plugin/ValidationTest.php @@ -10,7 +10,7 @@ use Magento\Store\Model\ScopeInterface; /** - * Class ValidationTest + * Class ValidationTest validates the agreement based on the payment method * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ValidationTest extends \PHPUnit\Framework\TestCase @@ -60,12 +60,24 @@ class ValidationTest extends \PHPUnit\Framework\TestCase */ private $agreementsFilterMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $quoteMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $quoteRepositoryMock; + protected function setUp() { $this->agreementsValidatorMock = $this->createMock(\Magento\Checkout\Api\AgreementsValidatorInterface::class); $this->subjectMock = $this->createMock(\Magento\Checkout\Api\PaymentInformationManagementInterface::class); $this->paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); $this->addressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); + $this->quoteMock = $this->createPartialMock(\Magento\Quote\Model\Quote::class, ['getIsMultiShipping']); + $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class); $this->extensionAttributesMock = $this->createPartialMock( \Magento\Quote\Api\Data\PaymentExtension::class, ['getAgreementIds'] @@ -82,7 +94,8 @@ protected function setUp() $this->agreementsValidatorMock, $this->scopeConfigMock, $this->checkoutAgreementsListMock, - $this->agreementsFilterMock + $this->agreementsFilterMock, + $this->quoteRepositoryMock ); } @@ -96,6 +109,15 @@ public function testBeforeSavePaymentInformationAndPlaceOrder() ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) ->willReturn(true); $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); + $this->quoteMock + ->expects($this->once()) + ->method('getIsMultiShipping') + ->willReturn(false); + $this->quoteRepositoryMock + ->expects($this->once()) + ->method('getActive') + ->with($cartId) + ->willReturn($this->quoteMock); $this->agreementsFilterMock->expects($this->once()) ->method('buildSearchCriteria') ->willReturn($searchCriteriaMock); @@ -108,7 +130,12 @@ public function testBeforeSavePaymentInformationAndPlaceOrder() $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation($this->subjectMock, $cartId, $this->paymentMock, $this->addressMock); + $this->model->beforeSavePaymentInformation( + $this->subjectMock, + $cartId, + $this->paymentMock, + $this->addressMock + ); } /** @@ -124,6 +151,15 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) ->willReturn(true); $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); + $this->quoteMock + ->expects($this->once()) + ->method('getIsMultiShipping') + ->willReturn(false); + $this->quoteRepositoryMock + ->expects($this->once()) + ->method('getActive') + ->with($cartId) + ->willReturn($this->quoteMock); $this->agreementsFilterMock->expects($this->once()) ->method('buildSearchCriteria') ->willReturn($searchCriteriaMock); @@ -136,7 +172,12 @@ public function testBeforeSavePaymentInformationAndPlaceOrderIfAgreementsNotVali $this->paymentMock->expects(static::atLeastOnce()) ->method('getExtensionAttributes') ->willReturn($this->extensionAttributesMock); - $this->model->beforeSavePaymentInformation($this->subjectMock, $cartId, $this->paymentMock, $this->addressMock); + $this->model->beforeSavePaymentInformation( + $this->subjectMock, + $cartId, + $this->paymentMock, + $this->addressMock + ); $this->expectExceptionMessage( "The order wasn't placed. First, agree to the terms and conditions, then try placing your order again." @@ -152,6 +193,15 @@ public function testBeforeSavePaymentInformation() ->method('isSetFlag') ->with(AgreementsProvider::PATH_ENABLED, ScopeInterface::SCOPE_STORE) ->willReturn(true); + $this->quoteMock + ->expects($this->once()) + ->method('getIsMultiShipping') + ->willReturn(false); + $this->quoteRepositoryMock + ->expects($this->once()) + ->method('getActive') + ->with($cartId) + ->willReturn($this->quoteMock); $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); $this->agreementsFilterMock->expects($this->once()) ->method('buildSearchCriteria') diff --git a/app/code/Magento/CheckoutAgreements/etc/adminhtml/system.xml b/app/code/Magento/CheckoutAgreements/etc/adminhtml/system.xml index 64ffb2b5dc21a..496f3fec70e5e 100644 --- a/app/code/Magento/CheckoutAgreements/etc/adminhtml/system.xml +++ b/app/code/Magento/CheckoutAgreements/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <system> <section id="checkout"> <group id="options"> - <field id="enable_agreements" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="enable_agreements" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enable Terms and Conditions</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/CheckoutAgreements/etc/config.xml b/app/code/Magento/CheckoutAgreements/etc/config.xml new file mode 100644 index 0000000000000..e2932af0c25cb --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/etc/config.xml @@ -0,0 +1,16 @@ +<?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> + <checkout> + <options> + <enable_agreements>0</enable_agreements> + </options> + </checkout> + </default> +</config> diff --git a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content/Uploader.php b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content/Uploader.php index cf0fc34b217e4..07b66fc53fa84 100644 --- a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content/Uploader.php +++ b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content/Uploader.php @@ -35,6 +35,8 @@ public function __construct( } /** + * Constructor + * * @return void */ protected function _construct() @@ -49,7 +51,7 @@ protected function _construct() $files[] = '*.' . $ext; } $this->getConfig()->setUrl( - $this->_urlBuilder->addSessionParam()->getUrl('cms/*/upload', ['type' => $type]) + $this->_urlBuilder->getUrl('cms/*/upload', ['type' => $type]) )->setFileField( 'image' )->setFilters( diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFiles.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFiles.php index 6f57efad41e75..fa873930aaade 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFiles.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFiles.php @@ -5,8 +5,8 @@ */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; -use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\Filesystem\DirectoryList; /** * Delete image files. @@ -60,10 +60,15 @@ public function __construct( */ public function execute() { + $resultJson = $this->resultJsonFactory->create(); + + if (!$this->getRequest()->isPost()) { + $result = ['error' => true, 'message' => __('Wrong request.')]; + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + return $resultJson->setData($result); + } + try { - if (!$this->getRequest()->isPost()) { - throw new \Exception('Wrong request.'); - } $files = $this->getRequest()->getParam('files'); /** @var $helper \Magento\Cms\Helper\Wysiwyg\Images */ @@ -79,17 +84,16 @@ public function execute() /** @var \Magento\Framework\Filesystem $filesystem */ $filesystem = $this->_objectManager->get(\Magento\Framework\Filesystem::class); $dir = $filesystem->getDirectoryRead(DirectoryList::MEDIA); - $filePath = $path . '/' . \Magento\Framework\File\Uploader::getCorrectFileName($file); + $filePath = $path . '/' . $file; if ($dir->isFile($dir->getRelativePath($filePath)) && !preg_match('#.htaccess#', $file)) { $this->getStorage()->deleteFile($filePath); } } - + return $this->resultRawFactory->create(); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { $result = ['error' => true, 'message' => $e->getMessage()]; - /** @var \Magento\Framework\Controller\Result\Json $resultJson */ - $resultJson = $this->resultJsonFactory->create(); return $resultJson->setData($result); } diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index 21f3b232e783c..f0a232bdccccc 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -570,7 +570,11 @@ public function getThumbnailPath($filePath, $checkFile = false) $mediaRootDir = $this->_cmsWysiwygImages->getStorageRoot(); if (strpos($filePath, (string) $mediaRootDir) === 0) { - $thumbPath = $this->getThumbnailRoot() . substr($filePath, strlen($mediaRootDir)); + $relativeFilePath = substr($filePath, strlen($mediaRootDir)); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $thumbPath = $relativeFilePath === basename($filePath) + ? $this->getThumbnailRoot() . DIRECTORY_SEPARATOR . $relativeFilePath + : $this->getThumbnailRoot() . $relativeFilePath; if (!$checkFile || $this->_directory->isExist($this->_directory->getRelativePath($thumbPath))) { return $thumbPath; @@ -589,21 +593,12 @@ public function getThumbnailPath($filePath, $checkFile = false) */ public function getThumbnailUrl($filePath, $checkFile = false) { - $mediaRootDir = $this->_cmsWysiwygImages->getStorageRoot(); - - if (strpos($filePath, (string) $mediaRootDir) === 0) { - $thumbSuffix = self::THUMBS_DIRECTORY_NAME . substr($filePath, strlen($mediaRootDir)); - if (!$checkFile || $this->_directory->isExist( - $this->_directory->getRelativePath($mediaRootDir . '/' . $thumbSuffix) - ) - ) { - $thumbSuffix = substr( - $mediaRootDir, - strlen($this->_directory->getAbsolutePath()) - ) . '/' . $thumbSuffix; - $randomIndex = '?rand=' . time(); - return str_replace('\\', '/', $this->_cmsWysiwygImages->getBaseUrl() . $thumbSuffix) . $randomIndex; - } + $thumbPath = $this->getThumbnailPath($filePath, $checkFile); + if ($thumbPath) { + $thumbRelativePath = ltrim($this->_directory->getRelativePath($thumbPath), '/\\'); + $baseUrl = rtrim($this->_cmsWysiwygImages->getBaseUrl(), '/'); + $randomIndex = '?rand=' . time(); + return str_replace('\\', '/', $baseUrl . '/' . $thumbRelativePath) . $randomIndex; } return false; @@ -666,11 +661,13 @@ public function resizeOnTheFly($filename) */ public function getThumbsPath($filePath = false) { - $mediaRootDir = $this->_cmsWysiwygImages->getStorageRoot(); $thumbnailDir = $this->getThumbnailRoot(); - if ($filePath && strpos($filePath, (string) $mediaRootDir) === 0) { - $thumbnailDir .= $this->file->getParentDirectory(substr($filePath, strlen($mediaRootDir))); + if ($filePath) { + $thumbPath = $this->getThumbnailPath($filePath, false); + if ($thumbPath) { + $thumbnailDir = $this->file->getParentDirectory($thumbPath); + } } return $thumbnailDir; diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreateDisabledPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreateDisabledPageTest.xml index 5b83807eca244..a5f43b090e9d6 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreateDisabledPageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreateDisabledPageTest.xml @@ -10,6 +10,7 @@ <test name="AdminCMSPageCreateDisabledPageTest"> <annotations> <features value="Cms"/> + <stories value="Create a CMS Page via the Admin"/> <title value="Create disabled CMS Page via the Admin"/> <description value="Admin should be able to create a CMS Page"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageForDefaultStoreTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageForDefaultStoreTest.xml index a9f5fcd8b17e0..d63952f7eb6c8 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageForDefaultStoreTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageForDefaultStoreTest.xml @@ -10,6 +10,7 @@ <test name="AdminCMSPageCreatePageForDefaultStoreTest"> <annotations> <features value="Cms"/> + <stories value="Create a CMS Page via the Admin"/> <title value="Create CMS Page via the Admin for default store"/> <description value="Admin should be able to create a CMS Page"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageInSingleStoreModeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageInSingleStoreModeTest.xml index 1ec85f90f46ef..d2124b5d83be6 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageInSingleStoreModeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageInSingleStoreModeTest.xml @@ -10,6 +10,7 @@ <test name="AdminCMSPageCreatePageInSingleStoreModeTest"> <annotations> <features value="Cms"/> + <stories value="Create a CMS Page via the Admin"/> <title value="Create CMS Page via the Admin in single store mode"/> <description value="Admin should be able to create a CMS Page"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageTest.xml index 947fa92f2c8ff..d33d7484f7770 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageTest.xml @@ -6,10 +6,11 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCMSPageCreatePageTest"> <annotations> <features value="Cms"/> + <stories value="Create a CMS Page via the Admin"/> <title value="Create CMS Page via the Admin"/> <description value="Admin should be able to create a CMS Page"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageWithBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageWithBlockTest.xml index a6c67dc61dd97..1600a0d9d8d0f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageWithBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCMSPageCreatePageWithBlockTest.xml @@ -10,6 +10,7 @@ <test name="AdminCMSPageCreatePageWithBlockTest"> <annotations> <features value="Cms"/> + <stories value="Create a CMS Page via the Admin"/> <title value="Create CMS Page that contains block content via the Admin"/> <description value="Admin should be able to create a CMS Page"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php index 9a9c63195051c..f87eee62e1095 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php @@ -131,6 +131,7 @@ class StorageTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { + $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->filesystemMock = $this->createMock(\Magento\Framework\Filesystem::class); $this->driverMock = $this->getMockBuilder(\Magento\Framework\Filesystem\DriverInterface::class) ->setMethods(['getRealPathSafety']) @@ -159,10 +160,7 @@ protected function setUp() $this->returnValue($this->directoryMock) ); - $this->fileMock = $this->createPartialMock( - \Magento\Framework\Filesystem\Driver\File::class, - ['getParentDirectory'] - ); + $this->fileMock = $this->objectManagerHelper->getObject(\Magento\Framework\Filesystem\Driver\File::class); $this->ioFileMock = $this->createPartialMock(\Magento\Framework\Filesystem\Io\File::class, ['getPathInfo']); $this->ioFileMock->expects( $this->any() @@ -233,8 +231,6 @@ function ($path) { 'image_allowed' => $this->allowedImageExtensions, ]; - $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->imagesStorage = $this->objectManagerHelper->getObject( \Magento\Cms\Model\Wysiwyg\Images\Storage::class, [ @@ -525,8 +521,6 @@ public function testUploadFile() ] ); - $this->fileMock->expects($this->any())->method('getParentDirectory')->willReturn($path); - $image = $this->getMockBuilder(\Magento\Catalog\Model\Product\Image::class) ->disableOriginalConstructor() ->setMethods(['open', 'keepAspectRatio', 'resize', 'save']) diff --git a/app/code/Magento/Cms/etc/adminhtml/system.xml b/app/code/Magento/Cms/etc/adminhtml/system.xml index 20d543440565b..785d0e3710f4f 100644 --- a/app/code/Magento/Cms/etc/adminhtml/system.xml +++ b/app/code/Magento/Cms/etc/adminhtml/system.xml @@ -58,9 +58,12 @@ <label>Enable WYSIWYG Editor</label> <source_model>Magento\Cms\Model\Config\Source\Wysiwyg\Enabled</source_model> </field> - <field id="editor" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="editor" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>WYSIWYG Editor</label> <source_model>Magento\Cms\Model\Config\Source\Wysiwyg\Editor</source_model> + <depends> + <field id="enabled" negative="1">disabled</field> + </depends> </field> </group> </section> diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 35fb7d82651f0..869bd22e20548 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -116,14 +116,6 @@ <argument name="resourceModel" xsi:type="string">Magento\Cms\Model\ResourceModel\Block</argument> </arguments> </type> - <virtualType name="CmsGirdFilterPool" type="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool"> - <arguments> - <argument name="appliers" xsi:type="array"> - <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item> - <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item> - </argument> - </arguments> - </virtualType> <type name="Magento\Framework\Model\Entity\RepositoryFactory"> <arguments> <argument name="entities" xsi:type="array"> diff --git a/app/code/Magento/Config/Model/Config/Importer/SaveProcessor.php b/app/code/Magento/Config/Model/Config/Importer/SaveProcessor.php index 2ea9df52c0a03..225729f69a74e 100644 --- a/app/code/Magento/Config/Model/Config/Importer/SaveProcessor.php +++ b/app/code/Magento/Config/Model/Config/Importer/SaveProcessor.php @@ -91,6 +91,7 @@ public function process(array $data) * @param string $scope The configuration scope (default, website, or store) * @param string $scopeCode The scope code * @return void + * @throws \Magento\Framework\Exception\RuntimeException */ private function invokeSave(array $scopeData, $scope, $scopeCode = null) { @@ -98,11 +99,13 @@ private function invokeSave(array $scopeData, $scope, $scopeCode = null) foreach ($scopeData as $path) { $value = $this->scopeConfig->getValue($path, $scope, $scopeCode); - $backendModel = $this->valueFactory->create($path, $value, $scope, $scopeCode); + if ($value !== null) { + $backendModel = $this->valueFactory->create($path, $value, $scope, $scopeCode); - if ($backendModel instanceof Value) { - $backendModel->beforeSave(); - $backendModel->afterSave(); + if ($backendModel instanceof Value) { + $backendModel->beforeSave(); + $backendModel->afterSave(); + } } } } diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminCheckUseSystemValueActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminCheckUseSystemValueActionGroup.xml new file mode 100644 index 0000000000000..10c36ced4cee3 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminCheckUseSystemValueActionGroup.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="AdminCheckUseSystemValueActionGroup"> + <arguments> + <argument name="rowId" type="string"/> + </arguments> + + <checkOption selector="{{AdminConfigSection.useSystemValue(rowId)}}" stepKey="checkUseSystemValue"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminToggleEnabledActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminToggleEnabledActionGroup.xml new file mode 100644 index 0000000000000..1fe36cf9ae390 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminToggleEnabledActionGroup.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="AdminToggleEnabledActionGroup"> + <arguments> + <argument name="element" type="string"/> + <argument name="state" type="string" defaultValue="Yes"/> + </arguments> + <selectOption selector="{{element}}" userInput="{{state}}" stepKey="switchActiveState"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminUncheckUseSystemValueActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminUncheckUseSystemValueActionGroup.xml new file mode 100644 index 0000000000000..25ecd6fe09a27 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminUncheckUseSystemValueActionGroup.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="AdminUncheckUseSystemValueActionGroup"> + <arguments> + <argument name="rowId" type="string"/> + </arguments> + + <uncheckOption selector="{{AdminConfigSection.useSystemValue(rowId)}}" stepKey="uncheckUseSystemValue"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AssertAdminValidationErrorActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AssertAdminValidationErrorActionGroup.xml new file mode 100644 index 0000000000000..768eefd26a4f9 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AssertAdminValidationErrorActionGroup.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="AssertAdminValidationErrorActionGroup"> + <arguments> + <argument name="inputId" type="string"/> + <argument name="errorMessage" type="string" defaultValue="This is a required field."/> + </arguments> + + <see selector="{{AdminConfigSection.errorElement(inputId)}}" userInput="{{errorMessage}}" + stepKey="seeElementValidationError"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml index a4fb3c7e32975..de01de3c7f4a8 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml @@ -22,5 +22,6 @@ <element name="useSystemValue" type="checkbox" selector="#{{configRowId}} > .use-default > input" parameterized="true"/> <element name="collapsibleSectionByTitle" type="text" selector="//form[@id='config-edit-form']//div[@class='section-config'][contains(.,'{{sectionTitle}}')]" parameterized="true" /> <element name="expandedSectionByTitle" type="text" selector="//form[@id='config-edit-form']//div[@class='section-config active'][contains(.,'{{sectionTitle}}')]" parameterized="true" /> + <element name="errorElement" type="text" selector="#{{inputId}}-error" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Importer/SaveProcessorTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Importer/SaveProcessorTest.php index aec3a6f64fec0..39a0e14f3e91c 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Importer/SaveProcessorTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Importer/SaveProcessorTest.php @@ -136,4 +136,37 @@ public function testProcess() $this->assertSame(null, $this->model->process($data)); } + + public function testProcessWithNullValues() + { + $data = [ + 'default' => [ + 'advanced' => ['modules_disable_output' => ['Test_Module' => '1']] + ], + 'websites' => ['test_website' => ['general' => ['locale' => ['timezone' => 'America/Rio_Branco']]]], + ]; + $this->arrayUtilsMock->expects($this->exactly(2)) + ->method('flatten') + ->willReturnMap([ + [ + [ + 'advanced' => ['modules_disable_output' => ['Test_Module' => '1']] + ], + '', + '/', + ['advanced/modules_disable_output/Test_Module' => '1'] + ], + [ + ['general' => ['locale' => ['timezone' => 'America/Rio_Branco']]], + '', + '/', + ['general/locale/timezone' => 'America/Rio_Branco'] + ] + ]); + $this->scopeConfigMock->expects($this->exactly(2)) + ->method('getValue') + ->willReturn(null); + + $this->assertSame(null, $this->model->process($data)); + } } diff --git a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php index 2767e725cc9b0..88007d0b1a880 100644 --- a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php @@ -573,7 +573,7 @@ protected function _parseVariations($rowData) $fieldAndValuePairs = []; foreach ($fieldAndValuePairsText as $nameAndValue) { - $nameAndValue = explode(ImportProduct::PAIR_NAME_VALUE_SEPARATOR, $nameAndValue); + $nameAndValue = explode(ImportProduct::PAIR_NAME_VALUE_SEPARATOR, $nameAndValue, 2); if (!empty($nameAndValue)) { $value = isset($nameAndValue[1]) ? trim($nameAndValue[1]) : ''; // Ignoring field names' case. diff --git a/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php index 8d75fd902e911..85b8dc5ec1d04 100644 --- a/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Import/Product/Type/ConfigurableTest.php @@ -9,8 +9,8 @@ use Magento\ConfigurableImportExport; /** - * Class ConfigurableTest - * @package Magento\ConfigurableImportExport\Test\Unit\Model\Import\Product\Type + * Configurable import export tests + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ConfigurableTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractImportTestCase @@ -78,6 +78,8 @@ class ConfigurableTest extends \Magento\ImportExport\Test\Unit\Model\Import\Abst protected $productEntityLinkField = 'entity_id'; /** + * @inheritdoc + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function setUp() @@ -270,10 +272,12 @@ protected function setUp() } /** + * Bunches data provider + * * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function _getBunch() + protected function _getBunch(): array { return [[ 'sku' => 'configurableskuI22', @@ -393,9 +397,11 @@ protected function _getBunch() } /** + * Super attributes data provider + * * @return array */ - protected function _getSuperAttributes() + protected function _getSuperAttributes(): array { return [ 'testattr2' => [ @@ -404,7 +410,6 @@ protected function _getSuperAttributes() 'attribute_code' => 'testattr2', 'is_global' => '1', 'is_visible' => '1', - 'is_static' => '0', 'is_required' => '0', 'is_unique' => '0', 'frontend_label' => 'testattr2', @@ -426,7 +431,6 @@ protected function _getSuperAttributes() 'attribute_code' => 'testattr3', 'is_global' => '1', 'is_visible' => '1', - 'is_static' => '0', 'is_required' => '0', 'is_unique' => '0', 'frontend_label' => 'testattr3', @@ -445,6 +449,8 @@ protected function _getSuperAttributes() } /** + * Verify save mtethod + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testSaveData() @@ -527,6 +533,8 @@ public function testSaveData() } /** + * Callback for is row allowed to import + * * @param $rowData * @param $rowNum * @return bool @@ -540,75 +548,31 @@ public function isRowAllowedToImport($rowData, $rowNum) return true; } - public function testIsRowValid() + /** + * Verify is row valid method + * + * @dataProvider getProductDataIsValidRow + * @param array $productData + * @return void + */ + public function testIsRowValid(array $productData): void { $bunch = $this->_getBunch(); - $badProduct = [ - 'sku' => 'configurableskuI22BadPrice', - 'store_view_code' => null, - 'attribute_set_code' => 'Default', - 'product_type' => 'configurable', - 'name' => 'Configurable Product 21 BadPrice', - 'product_websites' => 'website_1', - 'configurable_variation_labels' => 'testattr2=Select Color, testattr3=Select Size', - 'configurable_variations' => 'sku=testconf2-attr2val1-testattr3v1,' - . 'testattr2=attr2val1_DOESNT_EXIST,' - . 'testattr3=testattr3v1,' - . 'display=1|sku=testconf2-attr2val1-testattr3v2,' - . 'testattr2=attr2val1,' - . 'testattr3=testattr3v2,' - . 'display=0', - '_store' => null, - '_attribute_set' => 'Default', - '_type' => 'configurable', - '_product_websites' => 'website_1', - ]; // Checking that variations' field names are case-insensitive with this // product. $caseInsensitiveSKU = 'configurableskuI22CaseInsensitive'; - $caseInsensitiveProduct = [ - 'sku' => $caseInsensitiveSKU, - 'store_view_code' => null, - 'attribute_set_code' => 'Default', - 'product_type' => 'configurable', - 'name' => 'Configurable Product 21', - 'product_websites' => 'website_1', - 'configurable_variation_labels' => 'testattr2=Select Color, testattr3=Select Size', - 'configurable_variations' => 'SKU=testconf2-attr2val1-testattr3v1,' - . 'testattr2=attr2val1,' - . 'testattr3=testattr3v1,' - . 'display=1|sku=testconf2-attr2val1-testattr3v2,' - . 'testattr2=attr2val1,' - . 'testattr3=testattr3v2,' - . 'display=0', - '_store' => null, - '_attribute_set' => 'Default', - '_type' => 'configurable', - '_product_websites' => 'website_1', - ]; - $bunch[] = $badProduct; - $bunch[] = $caseInsensitiveProduct; + $productData['caseInsencitiveProduct']['sku'] = $caseInsensitiveSKU; + $bunch[] = $productData['bad_product']; + $bunch[] = $productData['caseInsencitiveProduct']; // Set _attributes to avoid error in Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType. $this->setPropertyValue($this->configurable, '_attributes', [ - $badProduct[\Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET] => [], + $productData['bad_product'][\Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET] => [], ]); // Avoiding errors about attributes not being super - $this->setPropertyValue( - $this->configurable, - '_superAttributes', - [ - 'testattr2' => ['options' => ['attr2val1' => 1]], - 'testattr3' => [ - 'options' => [ - 'testattr3v2' => 1, - 'testattr3v1' => 1, - ], - ], - ] - ); + $this->setPropertyValue($this->configurable, '_superAttributes', $productData['super_attributes']); foreach ($bunch as $rowData) { - $result = $this->configurable->isRowValid($rowData, 0, !isset($this->_oldSku[$rowData['sku']])); + $result = $this->configurable->isRowValid($rowData, 0, false); $this->assertNotNull($result); if ($rowData['sku'] === $caseInsensitiveSKU) { $this->assertTrue($result); @@ -616,7 +580,78 @@ public function testIsRowValid() } } - public function testRowValidationForNumericalSkus() + /** + * + * Data provider for isValidRows test. + * + * @return array + */ + public function getProductDataIsValidRow(): array + { + return [ + [ + [ + 'bad_product' => [ + 'sku' => 'configurableskuI22BadPrice', + 'store_view_code' => null, + 'attribute_set_code' => 'Default', + 'product_type' => 'configurable', + 'name' => 'Configurable Product 21 BadPrice', + 'product_websites' => 'website_1', + 'configurable_variation_labels' => 'testattr2=Select Color, testattr3=Select Size', + 'configurable_variations' => 'sku=testconf2-attr2val1-testattr3v1,' + . 'testattr2=attr2val1_DOESNT_EXIST,' + . 'testattr3=testattr3v1,' + . 'display=1|sku=testconf2-attr2val1-testattr3v2,' + . 'testattr2=attr2val1,' + . 'testattr3=testattr3v2,' + . 'display=0', + '_store' => null, + '_attribute_set' => 'Default', + '_type' => 'configurable', + '_product_websites' => 'website_1', + ], + 'caseInsencitiveProduct' => [ + 'sku' => '', + 'store_view_code' => null, + 'attribute_set_code' => 'Default', + 'product_type' => 'configurable', + 'name' => 'Configurable Product 21', + 'product_websites' => 'website_1', + 'configurable_variation_labels' => 'testattr2=Select Color, testattr3=Select Size', + 'configurable_variations' => 'SKU=testconf2-attr2val1-testattr3v1,' + . 'testattr2=attr2val1,' + . 'testattr3=testattr3v1=sx=sl,' + . 'display=1|sku=testconf2-attr2val1-testattr3v2,' + . 'testattr2=attr2val1,' + . 'testattr3=testattr3v2,' + . 'display=0', + '_store' => null, + '_attribute_set' => 'Default', + '_type' => 'configurable', + '_product_websites' => 'website_1', + ], + 'super_attributes' => + [ + 'testattr2' => ['options' => ['attr2val1' => 1]], + 'testattr3' => [ + 'options' => [ + 'testattr3v2' => 1, + 'testattr3v1=sx=sl' => 1, + ], + ], + ] + ] + ] + ]; + } + + /** + * Verify row validation with numeric skus + * + * @return void + */ + public function testRowValidationForNumericalSkus(): void { // Set _attributes to avoid error in Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType. $this->setPropertyValue($this->configurable, '_attributes', [ @@ -649,9 +684,11 @@ public function testRowValidationForNumericalSkus() } /** + * Row validation Data Provider + * * @return array */ - public function rowValidationDataProvider() + public function rowValidationDataProvider(): array { return [ 'duplicateProduct' => [ diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php index 3f1c22548c8d8..fc5e149c0726e 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/LinkedProductSelectBuilder.php @@ -39,11 +39,11 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ - public function build($productId) + public function build(int $productId, int $storeId) : array { - $selects = $this->linkedProductSelectBuilder->build($productId); + $selects = $this->linkedProductSelectBuilder->build($productId, $storeId); foreach ($selects as $select) { $this->baseSelectProcessor->process($select); diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php index d3ce508b31e0d..29717a057cbdb 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php @@ -62,14 +62,16 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getProducts(ProductInterface $product) { - $key = $this->storeManager->getStore()->getId() . '-' . $product->getId(); + $productId = $product->getId(); + $storeId = $product->getStoreId() ?: $this->storeManager->getStore()->getId(); + $key = $storeId . '-' . $productId; if (!isset($this->linkedProductMap[$key])) { $productIds = $this->resource->getConnection()->fetchCol( - '(' . implode(') UNION (', $this->linkedProductSelectBuilder->build($product->getId())) . ')' + '(' . implode(') UNION (', $this->linkedProductSelectBuilder->build($productId, $storeId)) . ')' ); $this->linkedProductMap[$key] = $this->collectionFactory->create() diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml index a5ad45048ce96..fa515c89fa460 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml @@ -148,6 +148,7 @@ <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetSearch"/> <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> <deleteData stepKey="deleteAttribute" createDataKey="createConfigProductAttribute"/> + <magentoCLI command="cron:run --group=index" stepKey="runCron"/> <actionGroup ref="logout" stepKey="logout"/> </after> <!-- Create configurable product from downloadable product page--> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml index 9b4cea72882eb..1b7fc2c153208 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml @@ -72,6 +72,8 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <magentoCLI command="cron:run --group=index" stepKey="runCron"/> + <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml index 0b078bc7d2a98..124f0eea2e77a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml @@ -140,7 +140,7 @@ <waitForElementVisible selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="waitForProductEnableSlider"/> <click selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="enableProductForSecondStoreView"/> <seeCheckboxIsChecked selector="{{AdminProductFormSection.productStatus}}" stepKey="seeThatProductIsEnabled"/> - <actionGroup ref="AdminFormSaveAndClose" stepKey="enabledConfigProductSecondStore"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="enabledConfigProductSecondStore"/> <!--go to admin and open product edit page to enable child product for second store view --> <amOnPage url="{{AdminProductEditPage.url($$createConfigProduct.id$$)}}" stepKey="goToProductEditPage2"/> @@ -154,7 +154,7 @@ <click selector="{{AdminProductFormConfigurationsSection.enableProductBtn}}" stepKey="clickEnableChildProduct1"/> <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('2')}}" stepKey="clickToExpandActionsForSecondVariation2"/> <click selector="{{AdminProductFormConfigurationsSection.enableProductBtn}}" stepKey="clickEnableChildProduct2"/> - <actionGroup ref="AdminFormSaveAndClose" stepKey="saveAll"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="saveAll"/> <!-- assert second store view storefront category list page --> <amOnPage url="/second_store_view/" stepKey="amOnsecondStoreFront1"/> @@ -168,7 +168,7 @@ <waitForPageLoad stepKey="waitChild1EditPageToLoad"/> <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="openProduct1InWebsitesSection"/> <click selector="{{ProductInWebsitesSection.website('Second Website')}}" stepKey="selectSecondWebsite1"/> - <actionGroup ref="AdminFormSaveAndClose" stepKey="saveUpdatedChild1Again"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="saveUpdatedChild1Again"/> <!--go to admin again and open child product1 and enable for second store view--> <amOnPage url="{{AdminProductEditPage.url($$createConfigChildProduct1.id$$)}}" stepKey="goToProduct1EditPage"/> @@ -180,14 +180,14 @@ <see userInput="Second Store View" selector="{{AdminMainActionsSection.storeSwitcher}}" stepKey="seeNewStoreViewNameP1"/> <waitForElementVisible selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="waitForProductEnableSliderP1"/> <seeCheckboxIsChecked selector="{{AdminProductFormSection.productStatus}}" stepKey="seeThatProduct1IsEnabled"/> - <actionGroup ref="AdminFormSaveAndClose" stepKey="save2UpdatedChild1"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="save2UpdatedChild1"/> <!--go to admin and open child product2 edit page and assign it to the second website --> <amOnPage url="{{AdminProductEditPage.url($$createConfigChildProduct2.id$$)}}" stepKey="goToProduct2EditPage"/> <waitForPageLoad stepKey="waitChild2EditPageToLoad"/> <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="openProduct2InWebsitesSection"/> <click selector="{{ProductInWebsitesSection.website('Second Website')}}" stepKey="selectSecondWebsite2"/> - <actionGroup ref="AdminFormSaveAndClose" stepKey="saveUpdatedChild2"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="saveUpdatedChild2"/> <!--go to admin again and open child product2 and enable for second store view--> <amOnPage url="{{AdminProductEditPage.url($$createConfigChildProduct2.id$$)}}" stepKey="goToProduct2EditPage2"/> @@ -199,7 +199,7 @@ <see userInput="Second Store View" selector="{{AdminMainActionsSection.storeSwitcher}}" stepKey="seeNewStoreViewNameP2"/> <waitForElementVisible selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="waitForProductEnableSliderP2"/> <seeCheckboxIsChecked selector="{{AdminProductFormSection.productStatus}}" stepKey="seeThatProduct2IsEnabled"/> - <actionGroup ref="AdminFormSaveAndClose" stepKey="save2UpdatedChild2"/> + <actionGroup ref="AdminFormSaveAndCloseActionGroup" stepKey="save2UpdatedChild2"/> <!-- assert storefront category list page --> <amOnPage url="/second_store_view/" stepKey="amOnsecondStoreFront"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml index 42bad3e4bb8bf..914550fabf39b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoErrorForMiniCartItemEditTest.xml @@ -11,6 +11,7 @@ <test name="NoErrorForMiniCartItemEditTest"> <annotations> <features value="ConfigurableProduct"/> + <stories value="Storefront Minicart Update"/> <title value="No error for minicart item edit test"/> <description value="Already selected configurable option should be selected when configurable product is edited from minicart"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml index 2cdad86aa0b2c..b8b0007c63d5f 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVisibilityOfDuplicateProductTest.xml @@ -114,7 +114,7 @@ <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> <!--Duplicate the product--> <comment userInput="Duplicate the product" stepKey="commentDuplicateProduct"/> - <actionGroup ref="AdminFormSaveAndDuplicate" stepKey="saveAndDuplicateProductForm"/> + <actionGroup ref="AdminFormSaveAndDuplicateActionGroup" stepKey="saveAndDuplicateProductForm"/> <click selector="{{AdminProductFormSection.enableProductLabel}}" stepKey="clickEnableProduct"/> <fillField selector="{{AdminProductFormSection.productName}}" userInput="$$createConfigProduct.name$$-Updated" stepKey="fillProductName"/> <selectOption selector="{{AdminProductFormSection.productStockStatus}}" userInput="1" stepKey="selectInStock"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php index 3ef03f32ae05d..0794ba2cb42bf 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Product/LinkedProductSelectBuilderTest.php @@ -50,6 +50,7 @@ protected function setUp() public function testBuild() { $productId = 42; + $storeId = 1; /** @var Select|\PHPUnit_Framework_MockObject_MockObject $selectMock */ $selectMock = $this->getMockBuilder(Select::class) @@ -67,6 +68,6 @@ public function testBuild() ->method('process') ->with($selectMock); - $this->assertEquals($expectedResult, $this->subject->build($productId)); + $this->assertEquals($expectedResult, $this->subject->build($productId, $storeId)); } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/LowestPriceOptionsProviderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/LowestPriceOptionsProviderTest.php index 7c83645a9fda3..29f48dbe7a460 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/LowestPriceOptionsProviderTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/LowestPriceOptionsProviderTest.php @@ -6,8 +6,8 @@ namespace Magento\ConfigurableProduct\Test\Unit\Pricing\Price; +use Magento\Catalog\Model\Product; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Api\Data\StoreInterface; @@ -102,9 +102,11 @@ protected function setUp() public function testGetProducts() { $productId = 1; + $storeId = 1; $linkedProducts = ['some', 'linked', 'products', 'dataobjects']; - $product = $this->getMockBuilder(ProductInterface::class)->disableOriginalConstructor()->getMock(); + $product = $this->createMock(Product::class); $product->expects($this->any())->method('getId')->willReturn($productId); + $product->expects($this->any())->method('getStoreId')->willReturn($storeId); $this->linkedProductSelectBuilder->expects($this->any())->method('build')->with($productId)->willReturn([]); $this->productCollection ->expects($this->once()) 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 e0cc83922e03e..16119dcb5c866 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 @@ -447,7 +447,7 @@ protected function getRows() 'smallImage' => '${$.provider}:${$.parentScope}.small_image', ], 'uploaderConfig' => [ - 'url' => $this->urlBuilder->addSessionParam()->getUrl( + 'url' => $this->urlBuilder->getUrl( 'catalog/product_gallery/upload' ), ], diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableQty.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableQty.php index ade56edeb3dfc..6eb65efa2ecae 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableQty.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurableQty.php @@ -41,10 +41,8 @@ public function modifyMeta(array $meta) 'arguments' => [ 'data' => [ 'config' => [ - 'imports' => [ - 'disabled' => '!ns = ${ $.ns }, index = ' - . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', - ], + 'component' => 'Magento_ConfigurableProduct/js/' . + 'components/qty-configurable' ], ], ], 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 58b58b11c1b1c..db51e7eebefc4 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 @@ -28,7 +28,7 @@ public function __construct(LocatorInterface $locator) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -36,7 +36,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -72,15 +72,7 @@ public function modifyMeta(array $meta) ], ]; - $meta['advanced_inventory_modal'] = [ - 'children' => [ - 'stock_data' => [ - 'children' => [ - 'qty' => $config, - ], - ], - ], - ]; + $meta['advanced_inventory_modal']['children']['stock_data']['children']['qty'] = $config; } return $meta; diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/qty-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/qty-configurable.js new file mode 100644 index 0000000000000..6b3840bdec450 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/qty-configurable.js @@ -0,0 +1,45 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Ui/js/form/element/abstract' +], function (Abstract) { + 'use strict'; + + return Abstract.extend({ + defaults: { + imports: { + isConfigurable: '!ns = ${ $.ns }, index = configurable-matrix:isEmpty' + } + }, + + /** @inheritdoc */ + initialize: function () { + this._super(); + // resolve initial disable state + this.handleQtyValue(this.isConfigurable); + + /** important to set this listener in initialize because of a different order of processing. + * Do not move to defaults->listens section */ + this.setListeners({ + isConfigurable: 'handleQtyValue' + }); + + return this; + }, + + /** + * Disable and clear Qty if product type changed to configurable + * + * @param {String} isConfigurable + */ + handleQtyValue: function (isConfigurable) { + this.disabled(!!this.isUseDefault() || isConfigurable); + + if (isConfigurable) { + this.clear(); + } + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index faacbd03f20b0..6f3af43bf5c7a 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -139,7 +139,12 @@ define([ }); $.each(queryParams, $.proxy(function (key, value) { - this.options.values[key] = value; + if (this.options.spConfig.attributes[key] !== undefined && + _.find(this.options.spConfig.attributes[key].options, function (element) { + return element.id === value; + })) { + this.options.values[key] = value; + } }, this)); }, @@ -155,7 +160,13 @@ define([ if (element.value) { attributeId = element.id.replace(/[a-z]*/, ''); - this.options.values[attributeId] = element.value; + + if (this.options.spConfig.attributes[attributeId] !== undefined && + _.find(this.options.spConfig.attributes[attributeId].options, function (optionElement) { + return optionElement.id === element.value; + })) { + this.options.values[attributeId] = element.value; + } } }, this)); }, diff --git a/app/code/Magento/Contact/etc/adminhtml/system.xml b/app/code/Magento/Contact/etc/adminhtml/system.xml index b5974e4d42e84..c1437c7778b79 100644 --- a/app/code/Magento/Contact/etc/adminhtml/system.xml +++ b/app/code/Magento/Contact/etc/adminhtml/system.xml @@ -24,15 +24,24 @@ <field id="recipient_email" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Emails To</label> <validate>validate-email</validate> + <depends> + <field id="*/contact/enabled">1</field> + </depends> </field> <field id="sender_email_identity" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="*/contact/enabled">1</field> + </depends> </field> <field id="email_template" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="*/contact/enabled">1</field> + </depends> </field> </group> </section> diff --git a/app/code/Magento/Cookie/etc/adminhtml/system.xml b/app/code/Magento/Cookie/etc/adminhtml/system.xml index 9790410969055..d1dcbf45ae5be 100644 --- a/app/code/Magento/Cookie/etc/adminhtml/system.xml +++ b/app/code/Magento/Cookie/etc/adminhtml/system.xml @@ -29,7 +29,7 @@ </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" showInStore="0" canRestore="1"> + <field id="cookie_restriction" translate="label" 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> diff --git a/app/code/Magento/Cron/etc/adminhtml/system.xml b/app/code/Magento/Cron/etc/adminhtml/system.xml index cef45ba386be2..2bd6611e57473 100644 --- a/app/code/Magento/Cron/etc/adminhtml/system.xml +++ b/app/code/Magento/Cron/etc/adminhtml/system.xml @@ -8,36 +8,36 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="system"> - <group id="cron" translate="label comment" type="text" sortOrder="15" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="cron" translate="label comment" type="text" sortOrder="15" showInDefault="1"> <label>Cron (Scheduled Tasks)</label> <comment>For correct URLs generated during cron runs please make sure that Web > Secure and Unsecure Base URLs are explicitly set. All the times are in minutes.</comment> - <group id="template" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="template" translate="label" type="text" sortOrder="10" showInDefault="1"> <label>Cron configuration options for group:</label> - <field id="schedule_generate_every" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="schedule_generate_every" translate="label" type="text" sortOrder="10" showInDefault="1" canRestore="1"> <label>Generate Schedules Every</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="schedule_ahead_for" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="schedule_ahead_for" translate="label" type="text" sortOrder="20" showInDefault="1" canRestore="1"> <label>Schedule Ahead for</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="schedule_lifetime" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="schedule_lifetime" translate="label" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>Missed if Not Run Within</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="history_cleanup_every" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="history_cleanup_every" translate="label" type="text" sortOrder="40" showInDefault="1" canRestore="1"> <label>History Cleanup Every</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="history_success_lifetime" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="history_success_lifetime" translate="label" type="text" sortOrder="50" showInDefault="1" canRestore="1"> <label>Success History Lifetime</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="history_failure_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="history_failure_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1" canRestore="1"> <label>Failure History Lifetime</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="use_separate_process" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_separate_process" translate="label" type="select" sortOrder="70" showInDefault="1" canRestore="1"> <label>Use Separate Process</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php index 07c7c553ac792..f4d69096475d5 100644 --- a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php +++ b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php @@ -67,6 +67,6 @@ public function execute() $this->messageManager->addErrorMessage($e->getMessage()); } - return $resultRedirect->setPath('*'); + return $resultRedirect->setPath('adminhtml/*/'); } } diff --git a/app/code/Magento/CurrencySymbol/Test/Unit/Controller/Adminhtml/System/Currencysymbol/SaveTest.php b/app/code/Magento/CurrencySymbol/Test/Unit/Controller/Adminhtml/System/Currencysymbol/SaveTest.php index b3c69c352ac7d..e4dfc7a3dc765 100644 --- a/app/code/Magento/CurrencySymbol/Test/Unit/Controller/Adminhtml/System/Currencysymbol/SaveTest.php +++ b/app/code/Magento/CurrencySymbol/Test/Unit/Controller/Adminhtml/System/Currencysymbol/SaveTest.php @@ -132,7 +132,7 @@ public function testExecute() ->with(__('You applied the custom currency symbols.')); $redirect = $this->createMock(Redirect::class); - $redirect->expects($this->once())->method('setPath')->with('*')->willReturnSelf(); + $redirect->expects($this->once())->method('setPath')->with('adminhtml/*/')->willReturnSelf(); $this->resultRedirectFactory->method('create')->willReturn($redirect); $this->assertEquals($redirect, $this->action->execute()); diff --git a/app/code/Magento/Customer/Block/Account/Dashboard.php b/app/code/Magento/Customer/Block/Account/Dashboard.php index 547074d0bcd81..92537009175fd 100644 --- a/app/code/Magento/Customer/Block/Account/Dashboard.php +++ b/app/code/Magento/Customer/Block/Account/Dashboard.php @@ -129,16 +129,6 @@ public function getOrdersUrl() return ''; } - /** - * Retrieve the Url for customer reviews. - * - * @return string - */ - public function getReviewsUrl() - { - return $this->_urlBuilder->getUrl('review/customer/index', ['_secure' => true]); - } - /** * Retrieve the Url for managing customer wishlist. * diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/ProductReviews.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/ProductReviews.php deleted file mode 100644 index dca7820c3dbc4..0000000000000 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/ProductReviews.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Controller\Adminhtml\Index; - -class ProductReviews extends \Magento\Customer\Controller\Adminhtml\Index -{ - /** - * Get customer's product reviews list - * - * @return \Magento\Framework\View\Result\Layout - */ - public function execute() - { - $customerId = $this->initCurrentCustomer(); - $resultLayout = $this->resultLayoutFactory->create(); - $block = $resultLayout->getLayout()->getBlock('admin.customer.reviews'); - $block->setCustomerId($customerId)->setUseAjax(true); - return $resultLayout; - } -} diff --git a/app/code/Magento/Customer/Controller/Review.php b/app/code/Magento/Customer/Controller/Review.php deleted file mode 100644 index 3a26f58c9f7d7..0000000000000 --- a/app/code/Magento/Customer/Controller/Review.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Controller; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\View\Result\PageFactory; - -class Review extends \Magento\Framework\App\Action\Action -{ - /** - * @var PageFactory - */ - protected $resultPageFactory; - - /** - * @param Context $context - * @param PageFactory $resultPageFactory - */ - public function __construct( - Context $context, - PageFactory $resultPageFactory - ) { - parent::__construct($context); - $this->resultPageFactory = $resultPageFactory; - } - - /** - * @return \Magento\Framework\View\Result\Page - */ - public function execute() - { - return $this->resultPageFactory->create(); - } -} diff --git a/app/code/Magento/Customer/Controller/Review/Index.php b/app/code/Magento/Customer/Controller/Review/Index.php deleted file mode 100644 index e11dea17d4df7..0000000000000 --- a/app/code/Magento/Customer/Controller/Review/Index.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php -/** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Controller\Review; - -class Index extends \Magento\Customer\Controller\Review -{ -} diff --git a/app/code/Magento/Customer/Controller/Review/View.php b/app/code/Magento/Customer/Controller/Review/View.php deleted file mode 100644 index 679452de680f2..0000000000000 --- a/app/code/Magento/Customer/Controller/Review/View.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php -/** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Controller\Review; - -class View extends \Magento\Customer\Controller\Review -{ -} diff --git a/app/code/Magento/Customer/Model/Session.php b/app/code/Magento/Customer/Model/Session.php index e9dc7700ec090..e1a8f53c208ff 100644 --- a/app/code/Magento/Customer/Model/Session.php +++ b/app/code/Magento/Customer/Model/Session.php @@ -19,6 +19,7 @@ * @method string getNoReferer() * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + * @SuppressWarnings(PHPMD.TooManyFields) * @since 100.0.2 */ class Session extends \Magento\Framework\Session\SessionManager @@ -108,6 +109,11 @@ class Session extends \Magento\Framework\Session\SessionManager */ protected $response; + /** + * @var AccountConfirmation + */ + private $accountConfirmation; + /** * Session constructor. * @@ -511,13 +517,6 @@ public function authenticate($loginUrl = null) $this->response->setRedirect($loginUrl); } else { $arguments = $this->_customerUrl->getLoginUrlParams(); - if ($this->_createUrl()->getUseSession()) { - $arguments += [ - '_query' => [ - $this->sidResolver->getSessionIdQueryParam($this->_session) => $this->_session->getSessionId(), - ] - ]; - } $this->response->setRedirect( $this->_createUrl()->getUrl(\Magento\Customer\Model\Url::ROUTE_ACCOUNT_LOGIN, $arguments) ); @@ -535,8 +534,6 @@ public function authenticate($loginUrl = null) */ protected function _setAuthUrl($key, $url) { - $url = $this->_coreUrl->removeRequestParam($url, $this->sidResolver->getSessionIdQueryParam($this)); - // Add correct session ID to URL if needed $url = $this->_createUrl()->getRebuiltUrl($url); return $this->storage->setData($key, $url); } diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml index 58777ec0d3515..81b8cabaa51ef 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml @@ -15,7 +15,7 @@ <arguments> <argument name="lastName" defaultValue=""/> </arguments> - + <!--Clear filter if exist--> <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingCustomerFilters"/> @@ -28,8 +28,9 @@ <waitForPageLoad stepKey="waitForDeleteItemPopup" time="10"/> <click stepKey="clickOnOk" selector="{{CustomersPageSection.ok}}"/> <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{CustomersPageSection.deletedSuccessMessage}}" time="10"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear"/> </actionGroup> - + <actionGroup name="DeleteCustomerByEmailActionGroup"> <annotations> <description>Goes to the Admin Customers grid page. Deletes a Customer based on the provided Email Address.</description> @@ -37,7 +38,7 @@ <arguments> <argument name="email" type="string"/> </arguments> - + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> <waitForPageLoad stepKey="waitForAdminCustomerPageLoad"/> <click selector="{{AdminCustomerFiltersSection.filtersButton}}" stepKey="clickFilterButton"/> @@ -51,6 +52,7 @@ <click selector="{{CustomersPageSection.delete}}" stepKey="clickDelete"/> <waitForElementVisible selector="{{CustomersPageSection.ok}}" stepKey="waitForOkToVisible"/> <click selector="{{CustomersPageSection.ok}}" stepKey="clickOkConfirmationButton"/> - <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{CustomersPageSection.deletedSuccessMessage}}" time="30"/> + <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{CustomersPageSection.deletedSuccessMessage}}" time="10"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml index ee32214435428..16125c6ddf250 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml @@ -46,8 +46,8 @@ <actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startCreateInvoice"/> <actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/> - <actionGroup ref="goToShipmentIntoOrder" stepKey="goToShipment"/> - <actionGroup ref="submitShipmentIntoOrder" stepKey="submitShipment"/> + <actionGroup ref="GoToShipmentIntoOrderActionGroup" stepKey="goToShipment"/> + <actionGroup ref="SubmitShipmentIntoOrderActionGroup" stepKey="submitShipment"/> <!--Create Credit Memo--> <actionGroup ref="StartToCreateCreditMemoActionGroup" stepKey="startToCreateCreditMemo"> diff --git a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php index 8565790990df1..a1733b233ea66 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php @@ -147,10 +147,10 @@ public function testAuthenticate() $urlMock->expects($this->once()) ->method('getRebuiltUrl') ->willReturn(''); - $this->urlFactoryMock->expects($this->exactly(4)) + $this->urlFactoryMock->expects($this->exactly(3)) ->method('create') ->willReturn($urlMock); - $urlMock->expects($this->once()) + $urlMock->expects($this->never()) ->method('getUseSession') ->willReturn(false); diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json index 3e98818a67b74..a9bc41c36e703 100644 --- a/app/code/Magento/Customer/composer.json +++ b/app/code/Magento/Customer/composer.json @@ -19,7 +19,6 @@ "magento/module-newsletter": "*", "magento/module-page-cache": "*", "magento/module-quote": "*", - "magento/module-review": "*", "magento/module-sales": "*", "magento/module-store": "*", "magento/module-tax": "*", diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml index 2bd1041214801..fca625d847a1d 100644 --- a/app/code/Magento/Customer/etc/adminhtml/system.xml +++ b/app/code/Magento/Customer/etc/adminhtml/system.xml @@ -15,10 +15,10 @@ <label>Customer Configuration</label> <tab>customer</tab> <resource>Magento_Customer::config_customer</resource> - <group id="account_share" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="account_share" translate="label" type="text" sortOrder="10" showInDefault="1"> <label>Account Sharing Options</label> <hide_in_single_store_mode>1</hide_in_single_store_mode> - <field id="scope" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="scope" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Share Customer Accounts</label> <backend_model>Magento\Customer\Model\Config\Share</backend_model> <source_model>Magento\Customer\Model\Config\Share</source_model> @@ -26,7 +26,7 @@ </group> <group id="create_account" translate="label" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Create New Account Options</label> - <field id="auto_group_assign" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="auto_group_assign" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enable Automatic Assignment to Customer Group</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -76,12 +76,12 @@ <field id="auto_group_assign">1</field> </depends> </field> - <field id="viv_disable_auto_group_assign_default" translate="label" type="select" sortOrder="57" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="viv_disable_auto_group_assign_default" translate="label" type="select" sortOrder="57" showInDefault="1" canRestore="1"> <label>Default Value for Disable Automatic Group Changes Based on VAT ID</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\Customer\Model\Config\Backend\CreateAccount\DisableAutoGroupAssignDefault</backend_model> </field> - <field id="vat_frontend_visibility" translate="label comment" type="select" sortOrder="58" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="vat_frontend_visibility" translate="label comment" type="select" sortOrder="58" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show VAT Number on Storefront</label> <comment>To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -106,7 +106,7 @@ <label>Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> </field> - <field id="confirm" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="confirm" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Require Emails Confirmation</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -123,7 +123,7 @@ ]]></comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> </field> - <field id="generate_human_friendly_id" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="generate_human_friendly_id" translate="label" type="select" sortOrder="120" showInDefault="1" canRestore="1"> <label>Generate Human-Friendly Customer ID</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -149,33 +149,33 @@ <label>Password Template Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> </field> - <field id="reset_link_expiration_period" translate="label comment" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="reset_link_expiration_period" translate="label comment" type="text" sortOrder="60" showInDefault="1" canRestore="1"> <label>Recovery Link Expiration Period (hours)</label> <comment>Please enter a number 1 or greater in this field.</comment> <validate>required-entry validate-digits validate-digits-range digits-range-1-</validate> <backend_model>Magento\Customer\Model\Config\Backend\Password\Link\Expirationperiod</backend_model> </field> - <field id="required_character_classes_number" translate="label comment" type="text" sortOrder="70" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="required_character_classes_number" translate="label comment" type="text" sortOrder="70" showInDefault="1" canRestore="1"> <label>Number of Required Character Classes</label> <comment>Number of different character classes required in password: Lowercase, Uppercase, Digits, Special Characters.</comment> <validate>required-entry validate-digits validate-digits-range digits-range-1-4</validate> </field> - <field id="minimum_password_length" translate="label comment" type="text" sortOrder="80" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="minimum_password_length" translate="label comment" type="text" sortOrder="80" showInDefault="1" canRestore="1"> <label>Minimum Password Length</label> <comment>Please enter a number 1 or greater in this field.</comment> <validate>required-entry validate-digits validate-digits-range digits-range-1-</validate> </field> - <field id="lockout_failures" translate="label comment" sortOrder="70" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="lockout_failures" translate="label comment" sortOrder="70" showInDefault="1" canRestore="1"> <label>Maximum Login Failures to Lockout Account</label> <comment>Use 0 to disable account locking.</comment> <frontend_class>required-entry validate-digits</frontend_class> </field> - <field id="lockout_threshold" translate="label comment" sortOrder="80" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="lockout_threshold" translate="label comment" sortOrder="80" showInDefault="1" canRestore="1"> <label>Lockout Time (minutes)</label> <comment>Account will be unlocked after provided time.</comment> <frontend_class>required-entry validate-digits</frontend_class> </field> - <field id="autocomplete_on_storefront" type="select" translate="label" sortOrder="65" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="autocomplete_on_storefront" type="select" translate="label" sortOrder="65" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable Autocomplete on login/forgot password forms</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -195,68 +195,68 @@ </group> <group id="address" translate="label" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Name and Address Options</label> - <field id="street_lines" translate="label comment" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="street_lines" translate="label comment" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Number of Lines in a Street Address</label> <backend_model>Magento\Customer\Model\Config\Backend\Address\Street</backend_model> <comment>Valid range: 1-4</comment> <validate>required-entry validate-digits validate-digits-range digits-range-1-4</validate> </field> - <field id="prefix_show" translate="label comment" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="prefix_show" translate="label comment" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Prefix</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <backend_model>Magento\Customer\Model\Config\Backend\Show\Address</backend_model> <comment>The title that goes before name (Mr., Mrs., etc.)</comment> </field> - <field id="prefix_options" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="prefix_options" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Prefix Dropdown Options</label> <comment> <![CDATA[Semicolon (;) separated values.<br/>Leave empty for open text field.]]> </comment> </field> - <field id="middlename_show" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="middlename_show" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Middle Name (initial)</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Always optional.</comment> <backend_model>Magento\Customer\Model\Config\Backend\Show\Address</backend_model> </field> - <field id="suffix_show" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="suffix_show" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Suffix</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <comment>The suffix that goes after name (Jr., Sr., etc.)</comment> <backend_model>Magento\Customer\Model\Config\Backend\Show\Address</backend_model> </field> - <field id="suffix_options" translate="label comment" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="suffix_options" translate="label comment" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Suffix Dropdown Options</label> <comment> <![CDATA[Semicolon (;) separated values.<br/>Leave empty for open text field.]]> </comment> </field> - <field id="dob_show" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="dob_show" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Date of Birth</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <backend_model>Magento\Customer\Model\Config\Backend\Show\Customer</backend_model> </field> - <field id="taxvat_show" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="taxvat_show" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Tax/VAT Number</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <backend_model>Magento\Customer\Model\Config\Backend\Show\Customer</backend_model> </field> - <field id="gender_show" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="gender_show" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Gender</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <backend_model>Magento\Customer\Model\Config\Backend\Show\Customer</backend_model> </field> - <field id="telephone_show" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="telephone_show" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Telephone</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <backend_model>Magento\Customer\Model\Config\Backend\Show\AddressOnly</backend_model> </field> - <field id="company_show" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="company_show" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Company</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <backend_model>Magento\Customer\Model\Config\Backend\Show\AddressOnly</backend_model> </field> - <field id="fax_show" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="fax_show" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show Fax</label> <source_model>Magento\Config\Model\Config\Source\Nooptreq</source_model> <backend_model>Magento\Customer\Model\Config\Backend\Show\AddressOnly</backend_model> @@ -264,7 +264,7 @@ </group> <group id="startup" translate="label" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Login Options</label> - <field id="redirect_dashboard" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="redirect_dashboard" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Redirect Customer to Account Dashboard after Logging in</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Customer will stay on the current page if "No" is selected.</comment> @@ -286,14 +286,14 @@ <label>PDF</label> </field> </group> - <group id="online_customers" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="online_customers" translate="label" type="text" sortOrder="10" showInDefault="1"> <label>Online Customers Options</label> - <field id="online_minutes_interval" translate="label comment" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="online_minutes_interval" translate="label comment" type="text" sortOrder="1" showInDefault="1"> <label>Online Minutes Interval</label> <validate>validate-number validate-greater-than-zero</validate> <comment>Leave empty for default (15 minutes).</comment> </field> - <field id="section_data_lifetime" translate="label comment" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="section_data_lifetime" translate="label comment" type="text" sortOrder="1" showInDefault="1"> <label>Customer Data Lifetime</label> <validate>validate-number validate-greater-than-zero</validate> <comment>Please specify value in minutes.</comment> @@ -302,7 +302,7 @@ </section> <section id="general"> <group id="store_information"> - <field id="validate_vat_number" translate="button_label" sortOrder="62" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="validate_vat_number" translate="button_label" sortOrder="62" showInDefault="1" showInWebsite="1"> <button_label>Validate VAT Number</button_label> <frontend_model>Magento\Customer\Block\Adminhtml\System\Config\Validatevat</frontend_model> </field> diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml index da4b80536e631..e50e6294c924d 100644 --- a/app/code/Magento/Customer/etc/config.xml +++ b/app/code/Magento/Customer/etc/config.xml @@ -12,6 +12,7 @@ <scope>1</scope> </account_share> <create_account> + <auto_group_assign>0</auto_group_assign> <confirm>0</confirm> <default_group>1</default_group> <tax_calculation_address_type>billing</tax_calculation_address_type> @@ -21,7 +22,9 @@ <email_no_password_template>customer_create_account_email_no_password_template</email_no_password_template> <email_confirmation_template>customer_create_account_email_confirmation_template</email_confirmation_template> <email_confirmed_template>customer_create_account_email_confirmed_template</email_confirmed_template> + <viv_disable_auto_group_assign_default>0</viv_disable_auto_group_assign_default> <vat_frontend_visibility>0</vat_frontend_visibility> + <generate_human_friendly_id>0</generate_human_friendly_id> </create_account> <default> <group>1</group> @@ -50,6 +53,7 @@ <suffix_show /> <suffix_options /> <dob_show /> + <taxvat_show /> <gender_show /> <telephone_show>req</telephone_show> <company_show>opt</company_show> diff --git a/app/code/Magento/Customer/etc/fieldset.xml b/app/code/Magento/Customer/etc/fieldset.xml index f89b653981520..ebd0fb2e57efe 100644 --- a/app/code/Magento/Customer/etc/fieldset.xml +++ b/app/code/Magento/Customer/etc/fieldset.xml @@ -57,23 +57,6 @@ <aspect name="update" /> </field> </fieldset> - <fieldset id="customer_address"> - <field name="vat_id"> - <aspect name="to_quote_address" /> - </field> - <field name="vat_is_valid"> - <aspect name="to_quote_address" /> - </field> - <field name="vat_request_id"> - <aspect name="to_quote_address" /> - </field> - <field name="vat_request_date"> - <aspect name="to_quote_address" /> - </field> - <field name="vat_request_success"> - <aspect name="to_quote_address" /> - </field> - </fieldset> <fieldset id="sales_convert_order_address"> <field name="vat_id"> <aspect name="to_quote_address" /> diff --git a/app/code/Magento/CustomerGraphQl/Test/Unit/Model/Resolver/RevokeCustomerTokenTest.php b/app/code/Magento/CustomerGraphQl/Test/Unit/Model/Resolver/RevokeCustomerTokenTest.php new file mode 100644 index 0000000000000..4f8d626ca4e64 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Test/Unit/Model/Resolver/RevokeCustomerTokenTest.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CustomerGraphQl\Test\Unit\Model\Resolver; + +use Magento\CustomerGraphQl\Model\Resolver\RevokeCustomerToken; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\GraphQl\Model\Query\ContextInterface; +use Magento\GraphQl\Model\Query\ContextExtensionInterface; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test class for \Magento\CustomerGraphQl\Model\Resolver\RevokeCustomerToken + */ +class RevokeCustomerTokenTest extends TestCase +{ + /** + * Object Manager Instance + * + * @var ObjectManager + */ + private $objectManager; + + /** + * Testable Object + * + * @var RevokeCustomerToken + */ + private $resolver; + + /** + * @var ContextInterface|MockObject + */ + private $contextMock; + + /** + * @var ContextExtensionInterface|MockObject + */ + private $contextExtensionMock; + + /** + * @var CustomerTokenServiceInterface|MockObject + */ + private $customerTokenServiceMock; + + /** + * @var Field|MockObject + */ + private $fieldMock; + + /** + * @var ResolveInfo|MockObject + */ + private $resolveInfoMock; + + /** + * @inheritdoc + */ + protected function setUp() : void + { + $this->objectManager = new ObjectManager($this); + + $this->contextMock = $this->getMockBuilder(ContextInterface::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'getExtensionAttributes', + 'getUserId', + 'getUserType', + ] + ) + ->getMock(); + + $this->contextExtensionMock = $this->getMockBuilder(ContextExtensionInterface::class) + ->setMethods( + [ + 'getIsCustomer', + 'getStore', + 'setStore', + 'setIsCustomer', + ] + ) + ->getMock(); + + $this->fieldMock = $this->getMockBuilder(Field::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->customerTokenServiceMock = $this->getMockBuilder(CustomerTokenServiceInterface::class) + ->getMockForAbstractClass(); + + $this->resolveInfoMock = $this->getMockBuilder(ResolveInfo::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resolver = $this->objectManager->getObject( + RevokeCustomerToken::class, + [ + 'customerTokenService' => $this->customerTokenServiceMock, + ] + ); + } + + /** + * Test revoke customer token + */ + public function testRevokeCustomerToken() + { + $isCustomer = true; + $revokeCustomerTokenResult = true; + + $this->contextMock + ->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->contextExtensionMock); + + $this->contextExtensionMock + ->expects($this->once()) + ->method('getIsCustomer') + ->willReturn($isCustomer); + + $this->customerTokenServiceMock + ->expects($this->once()) + ->method('revokeCustomerAccessToken') + ->willReturn($revokeCustomerTokenResult); + + $this->assertEquals( + [ + 'result' => true + ], + $this->resolver->resolve( + $this->fieldMock, + $this->contextMock, + $this->resolveInfoMock + ) + ); + } + + /** + * Test mutation when customer isn't authorized. + * + * @expectedException \Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testCustomerNotAuthorized() + { + $isCustomer = false; + + $this->contextMock + ->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->contextExtensionMock); + + $this->contextExtensionMock + ->expects($this->once()) + ->method('getIsCustomer') + ->willReturn($isCustomer); + + $this->resolver->resolve( + $this->fieldMock, + $this->contextMock, + $this->resolveInfoMock + ); + } +} diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index f86ebaea69730..5e5f309915d99 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -408,7 +408,7 @@ protected function _prepareDataForUpdate(array $rowData) $createdAt = (new \DateTime())->setTimestamp(strtotime($rowData['created_at'])); } - $emailInLowercase = strtolower($rowData[self::COLUMN_EMAIL]); + $emailInLowercase = strtolower(trim($rowData[self::COLUMN_EMAIL])); $newCustomer = false; $entityId = $this->_getCustomerId($emailInLowercase, $rowData[self::COLUMN_WEBSITE]); if (!$entityId) { @@ -478,6 +478,8 @@ protected function _prepareDataForUpdate(array $rowData) $entityRow['updated_at'] = $now->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); if (!empty($rowData[self::COLUMN_STORE])) { $entityRow['store_id'] = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + } else { + $entityRow['store_id'] = $this->getCustomerStoreId($emailInLowercase, $rowData[self::COLUMN_WEBSITE]); } $entitiesToUpdate[] = $entityRow; } @@ -666,4 +668,22 @@ public function getValidColumnNames() ) ); } + + /** + * Get customer store ID by email and website ID. + * + * @param string $email + * @param string $websiteCode + * @return bool|int + */ + private function getCustomerStoreId(string $email, string $websiteCode) + { + $websiteId = (int) $this->getWebsiteId($websiteCode); + $storeId = $this->getCustomerStorage()->getCustomerStoreId($email, $websiteId); + if ($storeId === null || $storeId === false) { + $defaultStore = $this->_storeManager->getWebsite($websiteId)->getDefaultStore(); + $storeId = $defaultStore ? $defaultStore->getId() : \Magento\Store\Model\Store::DEFAULT_STORE_ID; + } + return $storeId; + } } diff --git a/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php b/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php index 43623019c005e..7cff2dcc21b1e 100644 --- a/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php +++ b/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/Customer/Storage.php @@ -5,13 +5,12 @@ */ namespace Magento\CustomerImportExport\Model\ResourceModel\Import\Customer; -use Magento\CustomerImportExport\Test\Unit\Model\Import\CustomerCompositeTest; +use Magento\Customer\Model\ResourceModel\Customer\Collection as CustomerCollection; +use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory; use Magento\Framework\DataObject; use Magento\Framework\DB\Select; -use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory; -use Magento\Customer\Model\ResourceModel\Customer\Collection as CustomerCollection; -use Magento\ImportExport\Model\ResourceModel\CollectionByPagesIteratorFactory; use Magento\ImportExport\Model\ResourceModel\CollectionByPagesIterator; +use Magento\ImportExport\Model\ResourceModel\CollectionByPagesIteratorFactory; /** * Storage to check existing customers. @@ -56,6 +55,20 @@ class Storage */ public $_customerCollection; + /** + * Existing customers store IDs. In form of: + * + * [customer email] => array( + * [website id 1] => store id 1, + * [website id 2] => store id 2, + * ... => ... , + * [website id n] => store id n, + * ) + * + * @var array + */ + private $customerStoreIds = []; + /** * @param CustomerCollectionFactory $collectionFactory * @param CollectionByPagesIteratorFactory $colIteratorFactory @@ -91,7 +104,7 @@ private function prepareCollection(array $customerIdentifiers): CustomerCollecti $select = $collection->getSelect(); $customerTableId = array_keys($select->getPart(Select::FROM))[0]; $select->where( - $customerTableId .'.email in (?)', + $customerTableId . '.email in (?)', array_map( function (array $customer) { return $customer['email']; @@ -127,11 +140,15 @@ private function loadCustomersData(array $customerIdentifiers) */ public function addCustomerByArray(array $customer): Storage { - $email = strtolower(trim($customer['email'])); + $email = mb_strtolower(trim($customer['email'])); if (!isset($this->_customerIds[$email])) { $this->_customerIds[$email] = []; } + if (!isset($this->customerStoreIds[$email])) { + $this->customerStoreIds[$email] = []; + } $this->_customerIds[$email][$customer['website_id']] = $customer['entity_id']; + $this->customerStoreIds[$email][$customer['website_id']] = $customer['store_id'] ?? null; return $this; } @@ -164,11 +181,7 @@ public function addCustomer(DataObject $customer): Storage public function getCustomerId(string $email, int $websiteId) { $email = mb_strtolower($email); - //Trying to load the customer. - if (!array_key_exists($email, $this->_customerIds) || !array_key_exists($websiteId, $this->_customerIds[$email]) - ) { - $this->loadCustomersData([['email' => $email, 'website_id' => $websiteId]]); - } + $this->loadCustomerData($email, $websiteId); if (isset($this->_customerIds[$email][$websiteId])) { return $this->_customerIds[$email][$websiteId]; @@ -177,6 +190,25 @@ public function getCustomerId(string $email, int $websiteId) return false; } + /** + * Find customer store ID for unique pair of email and website ID. + * + * @param string $email + * @param int $websiteId + * @return bool|int + */ + public function getCustomerStoreId(string $email, int $websiteId) + { + $email = mb_strtolower($email); + $this->loadCustomerData($email, $websiteId); + + if (isset($this->customerStoreIds[$email][$websiteId])) { + return $this->customerStoreIds[$email][$websiteId]; + } + + return false; + } + /** * Pre-load customers for future checks. * @@ -189,12 +221,10 @@ public function prepareCustomers(array $customersToFind): void foreach ($customersToFind as $customerToFind) { $email = mb_strtolower($customerToFind['email']); $websiteId = $customerToFind['website_id']; - if (!array_key_exists($email, $this->_customerIds) - || !array_key_exists($websiteId, $this->_customerIds[$email]) - ) { + if (!$this->isLoadedCustomerData($email, $websiteId)) { //Only looking for customers we don't already have ID for. //We need unique identifiers. - $uniqueKey = $email .'_' .$websiteId; + $uniqueKey = $email . '_' . $websiteId; $identifiers[$uniqueKey] = [ 'email' => $email, 'website_id' => $websiteId, @@ -202,8 +232,10 @@ public function prepareCustomers(array $customersToFind): void //Recording that we've searched for a customer. if (!array_key_exists($email, $this->_customerIds)) { $this->_customerIds[$email] = []; + $this->customerStoreIds[$email] = []; } $this->_customerIds[$email][$websiteId] = null; + $this->customerStoreIds[$email][$websiteId] = null; } } if (!$identifiers) { @@ -213,4 +245,31 @@ public function prepareCustomers(array $customersToFind): void //Loading customers data. $this->loadCustomersData($identifiers); } + + /** + * Load customer data if it's not loaded. + * + * @param string $email + * @param int $websiteId + * @return void + */ + private function loadCustomerData(string $email, int $websiteId): void + { + if (!$this->isLoadedCustomerData($email, $websiteId)) { + $this->loadCustomersData([['email' => $email, 'website_id' => $websiteId]]); + } + } + + /** + * Check if customer data is loaded + * + * @param string $email + * @param int $websiteId + * @return bool + */ + private function isLoadedCustomerData(string $email, int $websiteId): bool + { + return array_key_exists($email, $this->_customerIds) + && array_key_exists($websiteId, $this->_customerIds[$email]); + } } diff --git a/app/code/Magento/Developer/Model/XmlCatalog/Format/VsCode.php b/app/code/Magento/Developer/Model/XmlCatalog/Format/VsCode.php new file mode 100644 index 0000000000000..568774a112e9a --- /dev/null +++ b/app/code/Magento/Developer/Model/XmlCatalog/Format/VsCode.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare (strict_types = 1); + +namespace Magento\Developer\Model\XmlCatalog\Format; + +use Magento\Framework\DomDocument\DomDocumentFactory; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Filesystem\Directory\ReadFactory; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\DriverPool; +use Magento\Framework\Filesystem\File\WriteFactory; + +/** + * Class VsCode generates URN catalog for VsCode + */ +class VsCode implements FormatInterface +{ + private const PROJECT_PATH_IDENTIFIER = '..'; + public const XMLNS = 'urn:oasis:names:tc:entity:xmlns:xml:catalog'; + public const FILE_MODE_READ = 'r'; + public const FILE_MODE_WRITE = 'w'; + + /** + * @var ReadInterface + */ + private $currentDirRead; + + /** + * @var WriteFactory + */ + private $fileWriteFactory; + + /** + * @var DomDocumentFactory + */ + private $domDocumentFactory; + + /** + * @param ReadFactory $readFactory + * @param WriteFactory $fileWriteFactory + * @param DomDocumentFactory $domDocumentFactory + */ + public function __construct( + ReadFactory $readFactory, + WriteFactory $fileWriteFactory, + DomDocumentFactory $domDocumentFactory + ) { + $this->currentDirRead = $readFactory->create(getcwd()); + $this->fileWriteFactory = $fileWriteFactory; + $this->domDocumentFactory = $domDocumentFactory; + } + + /** + * Generate Catalog of URNs for the VsCode + * + * @param string[] $dictionary + * @param string $configFile relative path to the VsCode catalog.xml + * @return void + */ + public function generateCatalog(array $dictionary, $configFile): void + { + $catalogNode = null; + + try { + $file = $this->fileWriteFactory->create($configFile, DriverPool::FILE, self::FILE_MODE_READ); + $dom = $this->domDocumentFactory->create(); + $fileContent = $file->readAll(); + if (!empty($fileContent)) { + $dom->loadXML($fileContent); + } else { + $this->initEmptyFile($dom); + } + $catalogNode = $dom->getElementsByTagName('catalog')->item(0); + + if ($catalogNode == null) { + $dom = $this->domDocumentFactory->create(); + $catalogNode = $this->initEmptyFile($dom); + } + $file->close(); + } catch (FileSystemException $f) { + //create file if does not exists + $dom = $this->domDocumentFactory->create(); + $catalogNode = $this->initEmptyFile($dom); + } + + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace('xmlns', self::XMLNS); + + foreach ($dictionary as $urn => $xsdPath) { + // Find an existing urn + $existingNode = $xpath->query("/xmlns:catalog/xmlns:system[@systemId='" . $urn . "']")->item(0); + $node = $existingNode ?? $dom->createElement('system'); + $node->setAttribute('systemId', $urn); + $node->setAttribute('uri', $this->getFileLocationInProject($xsdPath)); + $catalogNode->appendChild($node); + } + $dom->formatOutput = true; + $dom->preserveWhiteSpace = false; + + // Reload to keep pretty format + $dom->loadXML($dom->saveXML()); + + $file = $this->fileWriteFactory->create($configFile, DriverPool::FILE, self::FILE_MODE_WRITE); + $file->write($dom->saveXML()); + $file->close(); + } + + /** + * Setup basic empty dom elements + * + * @param \DOMDocument $dom + * @return \DOMElement + */ + private function initEmptyFile(\DOMDocument $dom): \DOMElement + { + $copyrigthComment = $dom->createComment(' +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +'); + $dom->appendChild($copyrigthComment); + + $catalogNode = $dom->createElement('catalog'); + $catalogNode->setAttribute('xmlns', self::XMLNS); + $dom->appendChild($catalogNode); + + return $catalogNode; + } + + /** + * Resolve xsdpath to xml project path + * + * @param string $xsdPath + * @return string + */ + private function getFileLocationInProject(string $xsdPath): string + { + return self::PROJECT_PATH_IDENTIFIER . DIRECTORY_SEPARATOR . $this->currentDirRead->getRelativePath($xsdPath); + } +} diff --git a/app/code/Magento/Developer/Test/Unit/Console/Command/XmlCatalogGenerateCommandTest.php b/app/code/Magento/Developer/Test/Unit/Console/Command/XmlCatalogGenerateCommandTest.php index e5c6525cfeb55..04d41efb793b8 100644 --- a/app/code/Magento/Developer/Test/Unit/Console/Command/XmlCatalogGenerateCommandTest.php +++ b/app/code/Magento/Developer/Test/Unit/Console/Command/XmlCatalogGenerateCommandTest.php @@ -66,4 +66,58 @@ public function testExecuteBadType() $commandTester->execute([XmlCatalogGenerateCommand::IDE_FILE_PATH_ARGUMENT => 'test']); $this->assertEquals('', $commandTester->getDisplay()); } + + public function testExecuteVsCodeFormat() + { + $fixtureXmlFile = __DIR__ . '/_files/test.xml'; + + $filesMock = $this->createPartialMock(\Magento\Framework\App\Utility\Files::class, ['getXmlCatalogFiles']); + $filesMock->expects($this->at(0)) + ->method('getXmlCatalogFiles') + ->will($this->returnValue([[$fixtureXmlFile]])); + $filesMock->expects($this->at(1)) + ->method('getXmlCatalogFiles') + ->will($this->returnValue([])); + $urnResolverMock = $this->createMock(\Magento\Framework\Config\Dom\UrnResolver::class); + $urnResolverMock->expects($this->once()) + ->method('getRealPath') + ->with($this->equalTo('urn:magento:framework:Module/etc/module.xsd')) + ->will($this->returnValue($fixtureXmlFile)); + + $vscodeFormatMock = $this->createMock(\Magento\Developer\Model\XmlCatalog\Format\VsCode::class); + $vscodeFormatMock->expects($this->once()) + ->method('generateCatalog') + ->with( + $this->equalTo(['urn:magento:framework:Module/etc/module.xsd' => $fixtureXmlFile]), + $this->equalTo('test') + )->will($this->returnValue(null)); + + $formats = ['vscode' => $vscodeFormatMock]; + $readFactory = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadFactory::class); + $readDirMock = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadInterface::class); + + $content = file_get_contents($fixtureXmlFile); + + $readDirMock->expects($this->once()) + ->method('readFile') + ->with($this->equalTo('test.xml')) + ->will($this->returnValue($content)); + $readFactory->expects($this->once()) + ->method('create') + ->will($this->returnValue($readDirMock)); + + $this->command = new XmlCatalogGenerateCommand( + $filesMock, + $urnResolverMock, + $readFactory, + $formats + ); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + '--' . XmlCatalogGenerateCommand::IDE_OPTION => 'vscode', + XmlCatalogGenerateCommand::IDE_FILE_PATH_ARGUMENT => 'test', + ]); + $this->assertEquals('', $commandTester->getDisplay()); + } } diff --git a/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/VsCodeTest.php b/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/VsCodeTest.php new file mode 100644 index 0000000000000..55d2a811b2eee --- /dev/null +++ b/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/VsCodeTest.php @@ -0,0 +1,282 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Developer\Test\Unit\Model\XmlCatalog\Format; + +use Magento\Developer\Model\XmlCatalog\Format\VsCode; +use Magento\Framework\DomDocument\DomDocumentFactory; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Filesystem\Directory\ReadFactory; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\DriverPool; +use Magento\Framework\Filesystem\File\Read; +use Magento\Framework\Filesystem\File\Write; +use Magento\Framework\Filesystem\File\WriteFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class VsCodeTest extends TestCase +{ + /** + * @var VsCode + */ + private $vscodeFormat; + + /** + * @var MockObject|ReadFactory + */ + private $readFactoryMock; + + /** + * @var MockObject|WriteFactory + */ + private $fileWriteFactoryMock; + + /** + * @var DomDocumentFactory + */ + private $domFactory; + + /** + * @var ObjectManager + */ + private $objectManagerHelper; + + public function setUp() + { + $this->objectManagerHelper = new ObjectManager($this); + + $currentDirReadMock = $this->createMock(ReadInterface::class); + $currentDirReadMock->expects($this->any()) + ->method('getRelativePath') + ->willReturnCallback(function ($xsdPath) { + return $xsdPath; + }); + + $this->readFactoryMock = $this->createMock(ReadFactory::class); + $this->readFactoryMock->expects($this->once()) + ->method('create') + ->withAnyParameters() + ->willReturn($currentDirReadMock); + + $this->fileWriteFactoryMock = $this->createMock(WriteFactory::class); + $this->domFactory = $this->objectManagerHelper->getObject(DomDocumentFactory::class); + + $vscodeFormatArgs = $this->objectManagerHelper->getConstructArguments( + VsCode::class, + [ + 'readFactory' => $this->readFactoryMock, + 'fileWriteFactory' => $this->fileWriteFactoryMock, + 'domDocumentFactory' => $this->domFactory, + ] + ); + + $this->vscodeFormat = $this->objectManagerHelper->getObject(VsCode::class, $vscodeFormatArgs); + } + + /** + * Test generation of new valid catalog + * + * @param string $content + * @param array $dictionary + * @dataProvider dictionaryDataProvider + * @return void + */ + public function testGenerateNewValidCatalog($content, $dictionary) + { + $configFile = 'test'; + + $message = __("The \"%1.xml\" file doesn't exist.", $configFile); + + $this->fileWriteFactoryMock->expects($this->at(0)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_READ + ) + ->willThrowException(new FileSystemException($message)); + + $fileMock = $this->createMock(Write::class); + $fileMock->expects($this->once()) + ->method('write') + ->with($content); + + $this->fileWriteFactoryMock->expects($this->at(1)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_WRITE + ) + ->willReturn($fileMock); + + $this->vscodeFormat->generateCatalog($dictionary, $configFile); + } + + /** + * Test modify existing valid catalog + * + * @param string $content + * @param array $dictionary + * @dataProvider dictionaryDataProvider + * @return void + */ + public function testGenerateExistingValidCatalog($content, $dictionary) + { + $configFile = 'test'; + + $fileMock = $this->createMock(Read::class); + $fileMock->expects($this->once()) + ->method('readAll') + ->withAnyParameters() + ->willReturn($content); + + $this->fileWriteFactoryMock->expects($this->at(0)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_READ + ) + ->willReturn($fileMock); + + $fileMock = $this->createMock(Write::class); + $fileMock->expects($this->once()) + ->method('write') + ->with($content); + + $this->fileWriteFactoryMock->expects($this->at(1)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_WRITE + ) + ->willReturn($fileMock); + + $this->vscodeFormat->generateCatalog($dictionary, $configFile); + } + + /** + * Test modify existing empty catalog + * + * @param string $content + * @param array $dictionary + * @dataProvider dictionaryDataProvider + * @return void + */ + public function testGenerateExistingEmptyValidCatalog($content, $dictionary) + { + $configFile = 'test'; + + $fileMock = $this->createMock(Read::class); + $fileMock->expects($this->once()) + ->method('readAll') + ->withAnyParameters() + ->willReturn(''); + + $this->fileWriteFactoryMock->expects($this->at(0)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_READ + ) + ->willReturn($fileMock); + + $fileMock = $this->createMock(Write::class); + $fileMock->expects($this->once()) + ->method('write') + ->with($content); + + $this->fileWriteFactoryMock->expects($this->at(1)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_WRITE + ) + ->willReturn($fileMock); + + $this->vscodeFormat->generateCatalog($dictionary, $configFile); + } + + /** + * Test modify existing invalid catalog + * + * @param string $content + * @param array $dictionary + * @dataProvider dictionaryDataProvider + * @return void + */ + public function testGenerateExistingInvalidValidCatalog($content, $dictionary, $invalidContent) + { + $configFile = 'test'; + + $fileMock = $this->createMock(Read::class); + $fileMock->expects($this->once()) + ->method('readAll') + ->withAnyParameters() + ->willReturn($invalidContent); + + $this->fileWriteFactoryMock->expects($this->at(0)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_READ + ) + ->willReturn($fileMock); + + $fileMock = $this->createMock(Write::class); + $fileMock->expects($this->once()) + ->method('write') + ->with($content); + + $this->fileWriteFactoryMock->expects($this->at(1)) + ->method('create') + ->with( + $configFile, + DriverPool::FILE, + VsCode::FILE_MODE_WRITE + ) + ->willReturn($fileMock); + + $this->vscodeFormat->generateCatalog($dictionary, $configFile); + } + + /** + * Data provider for test + * + * @return array + */ + public function dictionaryDataProvider() + { + $fixtureXmlFile = __DIR__ . '/_files/valid_catalog.xml'; + $content = file_get_contents($fixtureXmlFile); + $invalidXmlFile = __DIR__ . '/_files/invalid_catalog.xml'; + $invalidContent = file_get_contents($invalidXmlFile); + + return [ + [ + $content, + [ + 'urn:magento:framework:Acl/etc/acl.xsd' => + 'vendor/magento/framework/Acl/etc/acl.xsd', + 'urn:magento:module:Magento_Store:etc/config.xsd' => + 'vendor/magento/module-store/etc/config.xsd', + 'urn:magento:module:Magento_Cron:etc/crontab.xsd' => + 'vendor/magento/module-cron/etc/crontab.xsd', + 'urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd' => + 'vendor/magento/framework/Setup/Declaration/Schema/etc/schema.xsd', + ], + $invalidContent, + ], + ]; + } +} diff --git a/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/_files/invalid_catalog.xml b/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/_files/invalid_catalog.xml new file mode 100644 index 0000000000000..213448bfc7a68 --- /dev/null +++ b/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/_files/invalid_catalog.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<root /> diff --git a/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/_files/valid_catalog.xml b/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/_files/valid_catalog.xml new file mode 100644 index 0000000000000..43fee113e9930 --- /dev/null +++ b/app/code/Magento/Developer/Test/Unit/Model/XmlCatalog/Format/_files/valid_catalog.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"> + <system systemId="urn:magento:framework:Acl/etc/acl.xsd" uri="../vendor/magento/framework/Acl/etc/acl.xsd"/> + <system systemId="urn:magento:module:Magento_Store:etc/config.xsd" uri="../vendor/magento/module-store/etc/config.xsd"/> + <system systemId="urn:magento:module:Magento_Cron:etc/crontab.xsd" uri="../vendor/magento/module-cron/etc/crontab.xsd"/> + <system systemId="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd" uri="../vendor/magento/framework/Setup/Declaration/Schema/etc/schema.xsd"/> +</catalog> diff --git a/app/code/Magento/Developer/etc/adminhtml/system.xml b/app/code/Magento/Developer/etc/adminhtml/system.xml index 197dc6f981acf..10449ab428726 100644 --- a/app/code/Magento/Developer/etc/adminhtml/system.xml +++ b/app/code/Magento/Developer/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <section id="dev"> <group id="front_end_development_workflow" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Frontend Development Workflow</label> - <field id="type" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="type" translate="label comment" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Workflow type</label> <comment>Not available in production mode.</comment> <source_model>Magento\Developer\Model\Config\Source\WorkflowType</source_model> diff --git a/app/code/Magento/Developer/etc/di.xml b/app/code/Magento/Developer/etc/di.xml index 98adcbb3a8295..d8f8eb6c1221e 100644 --- a/app/code/Magento/Developer/etc/di.xml +++ b/app/code/Magento/Developer/etc/di.xml @@ -18,6 +18,7 @@ <arguments> <argument name="formats" xsi:type="array"> <item name="phpstorm" xsi:type="object">Magento\Developer\Model\XmlCatalog\Format\PhpStorm</item> + <item name="vscode" xsi:type="object">Magento\Developer\Model\XmlCatalog\Format\VsCode</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Dhl/etc/adminhtml/system.xml b/app/code/Magento/Dhl/etc/adminhtml/system.xml index 93a16821e385d..1728219236d9f 100644 --- a/app/code/Magento/Dhl/etc/adminhtml/system.xml +++ b/app/code/Magento/Dhl/etc/adminhtml/system.xml @@ -10,39 +10,39 @@ <section id="carriers"> <group id="dhl" translate="label" type="text" sortOrder="140" showInDefault="1" showInWebsite="1" showInStore="1"> <label>DHL</label> - <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled for Checkout</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="id" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="id" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>Access ID</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="password" translate="label" type="obscure" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="password" translate="label" type="obscure" sortOrder="60" showInDefault="1" showInWebsite="1"> <label>Password</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="account" translate="label" type="text" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="account" translate="label" type="text" sortOrder="70" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Account Number</label> </field> - <field id="content_type" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="content_type" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Content Type (Non Domestic)</label> <comment>Whether to use Documents or NonDocuments service for non domestic shipments. (Shipments within the EU are classed as domestic)</comment> <source_model>Magento\Dhl\Model\Source\Contenttype</source_model> </field> - <field id="handling_type" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_type" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Calculate Handling Fee</label> <source_model>Magento\Shipping\Model\Source\HandlingType</source_model> </field> - <field id="handling_action" translate="label comment" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_action" translate="label comment" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Handling Applied</label> <comment>"Per Order" allows a single handling fee for the entire order. "Per Package" allows an individual handling fee for each package.</comment> <source_model>Magento\Shipping\Model\Source\HandlingAction</source_model> </field> - <field id="handling_fee" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="handling_fee" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="1"> <label>Handling Fee</label> <validate>validate-number validate-zero-or-greater</validate> </field> @@ -78,22 +78,22 @@ <field id="size">1</field> </depends> </field> - <field id="doc_methods" translate="label" type="multiselect" sortOrder="170" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="doc_methods" translate="label" type="multiselect" sortOrder="170" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Documents Allowed Methods</label> <source_model>Magento\Dhl\Model\Source\Method\Doc</source_model> </field> - <field id="nondoc_methods" translate="label" type="multiselect" sortOrder="170" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="nondoc_methods" translate="label" type="multiselect" sortOrder="170" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Non Documents Allowed Methods</label> <source_model>Magento\Dhl\Model\Source\Method\Nondoc</source_model> </field> - <field id="ready_time" translate="label comment" type="text" sortOrder="180" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="ready_time" translate="label comment" type="text" sortOrder="180" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ready time</label> <comment>Package ready time after order submission (in hours).</comment> </field> <field id="specificerrmsg" translate="label" type="textarea" sortOrder="800" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Displayed Error Message</label> </field> - <field id="free_method_doc" translate="label" type="select" sortOrder="1200" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_method_doc" translate="label" type="select" sortOrder="1200" showInDefault="1" showInWebsite="1"> <label>Free Method</label> <frontend_class>free-method</frontend_class> <source_model>Magento\Dhl\Model\Source\Method\Freedoc</source_model> @@ -101,7 +101,7 @@ <field id="content_type">D</field> </depends> </field> - <field id="free_method_nondoc" translate="label" type="select" sortOrder="1200" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_method_nondoc" translate="label" type="select" sortOrder="1200" showInDefault="1" showInWebsite="1"> <label>Free Method</label> <frontend_class>free-method</frontend_class> <source_model>Magento\Dhl\Model\Source\Method\Freenondoc</source_model> @@ -109,41 +109,41 @@ <field id="content_type">N</field> </depends> </field> - <field id="free_shipping_enable" translate="label" type="select" sortOrder="1210" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_enable" translate="label" type="select" sortOrder="1210" showInDefault="1" showInWebsite="1"> <label>Enable Free Shipping Threshold</label> <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> </field> - <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="1220" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="1220" showInDefault="1" showInWebsite="1"> <label>Free Shipping Amount Threshold</label> <validate>validate-number validate-zero-or-greater</validate> <depends> <field id="free_shipping_enable">1</field> </depends> </field> - <field id="sallowspecific" translate="label" type="select" sortOrder="1900" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sallowspecific" translate="label" type="select" sortOrder="1900" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ship to Applicable Countries</label> <frontend_class>shipping-applicable-country</frontend_class> <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="1910" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="1910" showInDefault="1" showInWebsite="1"> <label>Ship to Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="showmethod" translate="label" type="select" sortOrder="1940" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="showmethod" translate="label" type="select" sortOrder="1940" showInDefault="1" showInWebsite="1"> <label>Show Method if Not Applicable</label> <frontend_class>shipping-skip-hide</frontend_class> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="sort_order" translate="label" type="text" sortOrder="2000" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="2000" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="debug" translate="label" type="select" sortOrder="1950" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="1950" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="sandbox_mode" translate="label" type="select" sortOrder="1960" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sandbox_mode" translate="label" type="select" sortOrder="1960" showInDefault="1" showInWebsite="1"> <label>Sandbox Mode</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Directory/Test/Mftf/Test/AdminScheduledImportSettingsHiddenTest.xml b/app/code/Magento/Directory/Test/Mftf/Test/AdminScheduledImportSettingsHiddenTest.xml index 0320b6f422cd6..853872dd907bd 100644 --- a/app/code/Magento/Directory/Test/Mftf/Test/AdminScheduledImportSettingsHiddenTest.xml +++ b/app/code/Magento/Directory/Test/Mftf/Test/AdminScheduledImportSettingsHiddenTest.xml @@ -6,11 +6,12 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminScheduledImportSettingsHiddenTest"> <annotations> <features value="Directory"/> - <title value="Scheduled import settings hidden" /> + <title value="Scheduled import settings hidden"/> + <stories value="Fields visibility according to 'Enable' value"/> <description value="Scheduled Import Settings' should hide fields when 'Enabled' is 'No'"/> <severity value="MINOR"/> </annotations> @@ -24,7 +25,7 @@ <magentoCLI command="config:set currency/import/enabled 0" stepKey="disableCurrencyImport"/> </after> - <amOnPage url="{{AdminCurrencySetupPage.url}}" stepKey="openCurrencyOptionsPage" /> + <amOnPage url="{{AdminCurrencySetupPage.url}}" stepKey="openCurrencyOptionsPage"/> <conditionalClick dependentSelector="{{AdminScheduledImportSettingsSection.enabled}}" visible="false" selector="{{AdminScheduledImportSettingsSection.head}}" stepKey="openCollapsibleBlock"/> <see selector="{{AdminScheduledImportSettingsSection.service}}" userInput="Fixer.io" stepKey="seeServiceFixerIo"/> <selectOption selector="{{AdminScheduledImportSettingsSection.enabled}}" userInput="0" stepKey="disableCurrencyImportOption"/> diff --git a/app/code/Magento/Directory/etc/adminhtml/system.xml b/app/code/Magento/Directory/etc/adminhtml/system.xml index 5f97a5e8d90d6..6fa47037d2645 100644 --- a/app/code/Magento/Directory/etc/adminhtml/system.xml +++ b/app/code/Magento/Directory/etc/adminhtml/system.xml @@ -13,7 +13,7 @@ <resource>Magento_Config::currency</resource> <group id="options" translate="label" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Currency Options</label> - <field id="base" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="base" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Base Currency</label> <frontend_model>Magento\Directory\Block\Adminhtml\Frontend\Currency\Base</frontend_model> <source_model>Magento\Config\Model\Config\Source\Locale\Currency</source_model> @@ -34,31 +34,31 @@ <can_be_empty>1</can_be_empty> </field> </group> - <group id="fixerio" translate="label" sortOrder="35" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="fixerio" translate="label" sortOrder="35" showInDefault="1"> <label>Fixer.io</label> - <field id="api_key" translate="label" type="obscure" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="api_key" translate="label" type="obscure" sortOrder="5" showInDefault="1"> <label>API Key</label> <config_path>currency/fixerio/api_key</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1"> <label>Connection Timeout in Seconds</label> <validate>validate-zero-or-greater validate-number</validate> </field> </group> - <group id="currencyconverterapi" translate="label" sortOrder="45" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="currencyconverterapi" translate="label" sortOrder="45" showInDefault="1"> <label>Currency Converter API</label> - <field id="api_key" translate="label" type="obscure" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="api_key" translate="label" type="obscure" sortOrder="5" showInDefault="1"> <label>API Key</label> <config_path>currency/currencyconverterapi/api_key</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1"> <label>Connection Timeout in Seconds</label> <validate>validate-zero-or-greater validate-number</validate> </field> </group> - <group id="import" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="import" translate="label" type="text" sortOrder="50" showInDefault="1"> <label>Scheduled Import Settings</label> <field id="enabled" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enabled</label> @@ -71,14 +71,14 @@ <field id="enabled">1</field> </depends> </field> - <field id="error_email_identity" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="error_email_identity" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Error Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> <depends> <field id="enabled">1</field> </depends> </field> - <field id="error_email_template" translate="label comment" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="error_email_template" translate="label comment" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Error Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> @@ -110,9 +110,9 @@ </group> </section> <section id="system"> - <group id="currency" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="currency" translate="label" type="text" sortOrder="50" showInDefault="1"> <label>Currency</label> - <field id="installed" translate="label" type="multiselect" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="installed" translate="label" type="multiselect" sortOrder="1" showInDefault="1" canRestore="1"> <label>Installed Currencies</label> <backend_model>Magento\Config\Model\Config\Backend\Locale</backend_model> <source_model>Magento\Config\Model\Config\Source\Locale\Currency\All</source_model> @@ -122,20 +122,20 @@ </section> <section id="general"> <group id="country"> - <field id="optional_zip_countries" translate="label" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="optional_zip_countries" translate="label" type="multiselect" sortOrder="3" showInDefault="1" canRestore="1"> <label>Zip/Postal Code is Optional for</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> </group> - <group id="region" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="region" translate="label" type="text" sortOrder="4" showInDefault="1"> <label>State Options</label> - <field id="state_required" translate="label" type="multiselect" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="state_required" translate="label" type="multiselect" sortOrder="1" showInDefault="1"> <label>State is Required for</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="display_all" translate="label" type="select" sortOrder="8" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="display_all" translate="label" type="select" sortOrder="8" showInDefault="1"> <label>Allow to Choose State if It is Optional for Country</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php index 47c66c98fc8fb..e0026765f269b 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php @@ -11,7 +11,7 @@ * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * - * @deprecated + * @deprecated in favor of new class which adds grid links * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Links */ class Links extends \Magento\Backend\Block\Template @@ -398,7 +398,7 @@ public function getFileFieldName($type) */ public function getUploadUrl($type) { - return $this->_urlFactory->create()->addSessionParam()->getUrl( + return $this->_urlFactory->create()->getUrl( 'adminhtml/downloadable_file/upload', ['type' => $type, '_secure' => true] ); diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php index f245aeeeead67..83a5a93405158 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php @@ -10,7 +10,7 @@ * * @author Magento Core Team <core@magentocommerce.com> * - * @deprecated + * @deprecated because of new class which adds grids samples * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Samples */ class Samples extends \Magento\Backend\Block\Widget @@ -250,7 +250,7 @@ public function getUploadButtonHtml() */ public function getConfigJson() { - $url = $this->_urlFactory->create()->addSessionParam()->getUrl( + $url = $this->_urlFactory->create()->getUrl( 'adminhtml/downloadable_file/upload', ['type' => 'samples', '_secure' => true] ); diff --git a/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/LinksTest.php b/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/LinksTest.php index cff48420938b1..4a1039340342f 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/LinksTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/LinksTest.php @@ -17,7 +17,7 @@ use Magento\Framework\Stdlib\ArrayManager; /** - * Class LinksTest + * Test for class Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Links * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class LinksTest extends \PHPUnit\Framework\TestCase @@ -169,7 +169,7 @@ public function testModifyMeta() ->method('toOptionArray'); $this->shareableMock->expects($this->once()) ->method('toOptionArray'); - $this->urlBuilderMock->expects($this->exactly(2)) + $this->urlBuilderMock->expects($this->never()) ->method('addSessionParam') ->willReturnSelf(); $this->urlBuilderMock->expects($this->exactly(2)) diff --git a/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SamplesTest.php b/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SamplesTest.php index 420950d08e101..41d56f0f380bb 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SamplesTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Ui/DataProvider/Product/Form/Modifier/SamplesTest.php @@ -16,7 +16,7 @@ use Magento\Framework\Stdlib\ArrayManager; /** - * Class SamplesTest + * Test for class Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Samples * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SamplesTest extends \PHPUnit\Framework\TestCase @@ -141,7 +141,7 @@ public function testModifyMeta() ->method('isSingleStoreMode'); $this->typeUploadMock->expects($this->once()) ->method('toOptionArray'); - $this->urlBuilderMock->expects($this->once()) + $this->urlBuilderMock->expects($this->never()) ->method('addSessionParam') ->willReturnSelf(); $this->urlBuilderMock->expects($this->once()) diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php index 0a3ea2fc6ba19..3be1094f7a4b7 100644 --- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php +++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Links.php @@ -16,7 +16,7 @@ use Magento\Framework\Exception\ValidatorException; /** - * Class Links + * Grid class to add links * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Links @@ -162,7 +162,7 @@ protected function addSampleFile(array $linkData, LinkInterface $link) 'name' => $this->downloadableFile->getFileFromPathFile($sampleFile), 'size' => $this->downloadableFile->getFileSize($file), 'status' => 'old', - 'url' => $this->urlBuilder->addSessionParam()->getUrl( + 'url' => $this->urlBuilder->getUrl( 'adminhtml/downloadable_product_edit/link', ['id' => $link->getId(), 'type' => 'sample', '_secure' => true] ), @@ -191,7 +191,7 @@ protected function addLinkFile(array $linkData, LinkInterface $link) 'name' => $this->downloadableFile->getFileFromPathFile($linkFile), 'size' => $this->downloadableFile->getFileSize($file), 'status' => 'old', - 'url' => $this->urlBuilder->addSessionParam()->getUrl( + 'url' => $this->urlBuilder->getUrl( 'adminhtml/downloadable_product_edit/link', ['id' => $link->getId(), 'type' => 'link', '_secure' => true] ), diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Samples.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Samples.php index 988f429de1d87..bf2faede3a6dd 100644 --- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Samples.php +++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Data/Samples.php @@ -16,7 +16,7 @@ use Magento\Downloadable\Api\Data\SampleInterface; /** - * Class Samples + * Class to add samples * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Samples @@ -143,7 +143,7 @@ protected function addSampleFile(array $sampleData, SampleInterface $sample) 'name' => $this->downloadableFile->getFileFromPathFile($sampleFile), 'size' => $this->downloadableFile->getFileSize($file), 'status' => 'old', - 'url' => $this->urlBuilder->addSessionParam()->getUrl( + 'url' => $this->urlBuilder->getUrl( 'adminhtml/downloadable_product_edit/sample', ['id' => $sample->getId(), '_secure' => true] ), 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 c4824f913daf8..8c98d871a12d2 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 @@ -339,7 +339,7 @@ protected function getFileColumn() 'elementTmpl' => 'Magento_Downloadable/components/file-uploader', 'fileInputName' => 'links', 'uploaderConfig' => [ - 'url' => $this->urlBuilder->addSessionParam()->getUrl( + 'url' => $this->urlBuilder->getUrl( 'adminhtml/downloadable_file/upload', ['type' => 'links', '_secure' => true] ), @@ -406,7 +406,7 @@ protected function getSampleColumn() 'fileInputName' => 'link_samples', 'labelVisible' => false, 'uploaderConfig' => [ - 'url' => $this->urlBuilder->addSessionParam()->getUrl( + 'url' => $this->urlBuilder->getUrl( 'adminhtml/downloadable_file/upload', ['type' => 'link_samples', '_secure' => true] ), diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php index 81c5918eb24ae..474fc56c10043 100644 --- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php +++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php @@ -273,7 +273,7 @@ protected function getSampleColumn() 'elementTmpl' => 'Magento_Downloadable/components/file-uploader', 'fileInputName' => 'samples', 'uploaderConfig' => [ - 'url' => $this->urlBuilder->addSessionParam()->getUrl( + 'url' => $this->urlBuilder->getUrl( 'adminhtml/downloadable_file/upload', ['type' => 'samples', '_secure' => true] ), diff --git a/app/code/Magento/Downloadable/etc/adminhtml/system.xml b/app/code/Magento/Downloadable/etc/adminhtml/system.xml index 77f2cc0503631..31b2f64ec018c 100644 --- a/app/code/Magento/Downloadable/etc/adminhtml/system.xml +++ b/app/code/Magento/Downloadable/etc/adminhtml/system.xml @@ -10,15 +10,15 @@ <section id="catalog"> <group id="downloadable" translate="label" type="text" sortOrder="600" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Downloadable Product Options</label> - <field id="order_item_status" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="order_item_status" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Order Item Status to Enable Downloads</label> <source_model>Magento\Downloadable\Model\System\Config\Source\Orderitemstatus</source_model> </field> - <field id="downloads_number" translate="label" type="text" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="downloads_number" translate="label" type="text" sortOrder="200" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Default Maximum Number of Downloads</label> <validate>validate-digits validate-zero-or-greater</validate> </field> - <field id="shareable" translate="label" type="select" sortOrder="300" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="shareable" translate="label" type="select" sortOrder="300" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Shareable</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -28,15 +28,15 @@ <field id="links_title" translate="label" type="text" sortOrder="500" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Default Link Title</label> </field> - <field id="links_target_new_window" translate="label" type="select" sortOrder="600" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="links_target_new_window" translate="label" type="select" sortOrder="600" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Open Links in New Window</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="content_disposition" translate="label" type="select" sortOrder="700" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="content_disposition" translate="label" type="select" sortOrder="700" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Use Content-Disposition</label> <source_model>Magento\Downloadable\Model\System\Config\Source\Contentdisposition</source_model> </field> - <field id="disable_guest_checkout" translate="label comment" type="select" sortOrder="800" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="disable_guest_checkout" translate="label comment" type="select" sortOrder="800" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Disable Guest Checkout if Cart Contains Downloadable Items</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Guest checkout will only work with shareable.</comment> diff --git a/app/code/Magento/Downloadable/etc/config.xml b/app/code/Magento/Downloadable/etc/config.xml index 578ff1e008660..26f2457f6bf3c 100644 --- a/app/code/Magento/Downloadable/etc/config.xml +++ b/app/code/Magento/Downloadable/etc/config.xml @@ -9,8 +9,9 @@ <default> <catalog> <downloadable> - <downloads_number>0</downloads_number> <order_item_status>9</order_item_status> + <downloads_number>0</downloads_number> + <shareable>0</shareable> <samples_title>Samples</samples_title> <links_title>Links</links_title> <links_target_new_window>1</links_target_new_window> diff --git a/app/code/Magento/DownloadableImportExport/Helper/Data.php b/app/code/Magento/DownloadableImportExport/Helper/Data.php index fa4f7d656cdbe..91e290dbbcdf3 100644 --- a/app/code/Magento/DownloadableImportExport/Helper/Data.php +++ b/app/code/Magento/DownloadableImportExport/Helper/Data.php @@ -8,7 +8,7 @@ use Magento\DownloadableImportExport\Model\Import\Product\Type\Downloadable; /** - * Class Data + * Helper for import-export downloadable product */ class Data extends \Magento\Framework\App\Helper\AbstractHelper { @@ -47,6 +47,7 @@ public function isRowDownloadableNoValid(array $rowData) * @param array $option * @param array $existingOptions * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function fillExistOptions(array $base, array $option, array $existingOptions) { @@ -59,6 +60,9 @@ public function fillExistOptions(array $base, array $option, array $existingOpti && $option['sample_file'] == $existingOption['sample_file'] && $option['sample_type'] == $existingOption['sample_type'] && $option['product_id'] == $existingOption['product_id']) { + if (empty($existingOption['website_id'])) { + unset($existingOption['website_id']); + } $result = array_replace($base, $option, $existingOption); } } diff --git a/app/code/Magento/DownloadableImportExport/Helper/Uploader.php b/app/code/Magento/DownloadableImportExport/Helper/Uploader.php index 197250faaea91..e6ead5d5cc021 100644 --- a/app/code/Magento/DownloadableImportExport/Helper/Uploader.php +++ b/app/code/Magento/DownloadableImportExport/Helper/Uploader.php @@ -8,7 +8,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; /** - * Class Uploader + * Uploader helper for downloadable products */ class Uploader extends \Magento\Framework\App\Helper\AbstractHelper { @@ -105,6 +105,17 @@ public function getUploader($type, $parameters) return $this->fileUploader; } + /** + * Check a file or directory exists + * + * @param string $fileName + * @return bool + */ + public function isFileExist(string $fileName): bool + { + return $this->mediaDirectory->isExist($this->fileUploader->getDestDir().$fileName); + } + /** * Get all allowed extensions * diff --git a/app/code/Magento/DownloadableImportExport/Model/Export/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Export/Product/Type/Downloadable.php new file mode 100644 index 0000000000000..716e65e00d1aa --- /dev/null +++ b/app/code/Magento/DownloadableImportExport/Model/Export/Product/Type/Downloadable.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\DownloadableImportExport\Model\Export\Product\Type; + +use Magento\CatalogImportExport\Model\Export\Product\Type\AbstractType; + +/** + * Class Downloadable for composite CatalogImportExport + */ +class Downloadable extends AbstractType +{ +} diff --git a/app/code/Magento/DownloadableImportExport/Model/Export/RowCustomizer.php b/app/code/Magento/DownloadableImportExport/Model/Export/RowCustomizer.php new file mode 100644 index 0000000000000..daa874e829e54 --- /dev/null +++ b/app/code/Magento/DownloadableImportExport/Model/Export/RowCustomizer.php @@ -0,0 +1,170 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DownloadableImportExport\Model\Export; + +use Magento\Downloadable\Model\LinkRepository; +use Magento\Downloadable\Model\SampleRepository; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface; +use Magento\CatalogImportExport\Model\Import\Product as ImportProduct; +use Magento\Downloadable\Model\Product\Type as Type; +use Magento\ImportExport\Model\Import; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\DownloadableImportExport\Model\Import\Product\Type\Downloadable; + +/** + * Customizes output during export + */ +class RowCustomizer implements RowCustomizerInterface +{ + /** + * @var array + */ + private $downloadableData = []; + + /** + * @var string[] + */ + private $downloadableColumns = [ + Downloadable::COL_DOWNLOADABLE_LINKS, + Downloadable::COL_DOWNLOADABLE_SAMPLES, + ]; + + /** + * @var LinkRepository + */ + private $linkRepository; + + /** + * @var SampleRepository + */ + private $sampleRepository; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param StoreManagerInterface $storeManager + * @param LinkRepository $linkRepository + * @param SampleRepository $sampleRepository + */ + public function __construct( + StoreManagerInterface $storeManager, + LinkRepository $linkRepository, + SampleRepository $sampleRepository + ) { + $this->storeManager = $storeManager; + $this->linkRepository = $linkRepository; + $this->sampleRepository = $sampleRepository; + } + + /** + * Prepare configurable data for export + * + * @param ProductCollection $collection + * @param int[] $productIds + * @return void + */ + public function prepareData($collection, $productIds): void + { + $productCollection = clone $collection; + $productCollection->addAttributeToFilter('entity_id', ['in' => $productIds]) + ->addAttributeToFilter('type_id', ['eq' => Type::TYPE_DOWNLOADABLE]) + ->addAttributeToSelect('links_title') + ->addAttributeToSelect('samples_title'); + // set global scope during export + $this->storeManager->setCurrentStore(Store::DEFAULT_STORE_ID); + foreach ($collection as $product) { + $productLinks = $this->linkRepository->getLinksByProduct($product); + $productSamples = $this->sampleRepository->getSamplesByProduct($product); + $this->downloadableData[$product->getId()] = []; + $linksData = []; + $samplesData = []; + foreach ($productLinks as $linkId => $link) { + $linkData = $link->getData(); + $linkData['group_title'] = $product->getData('links_title'); + $linksData[$linkId] = $this->optionRowToCellString($linkData); + } + foreach ($productSamples as $sampleId => $sample) { + $sampleData = $sample->getData(); + $sampleData['group_title'] = $product->getData('samples_title'); + $samplesData[$sampleId] = $this->optionRowToCellString($sampleData); + } + $this->downloadableData[$product->getId()] = [ + Downloadable::COL_DOWNLOADABLE_LINKS => implode( + ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, + $linksData + ), + Downloadable::COL_DOWNLOADABLE_SAMPLES => implode( + Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, + $samplesData + )]; + } + } + + /** + * Convert option row to cell string + * + * @param array $option + * @return string + */ + private function optionRowToCellString(array $option): string + { + $result = []; + foreach ($option as $attributeCode => $value) { + if ($value) { + $result[] = $attributeCode . ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $value; + } + } + return implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $result); + } + + /** + * Set headers columns + * + * @param array $columns + * @return array + */ + public function addHeaderColumns($columns): array + { + return array_merge($columns, $this->downloadableColumns); + } + + /** + * Add downloadable data for export + * + * @param array $dataRow + * @param int $productId + * @return array + */ + public function addData($dataRow, $productId): array + { + if (!empty($this->downloadableData[$productId])) { + $dataRow = array_merge($dataRow, $this->downloadableData[$productId]); + } + return $dataRow; + } + + /** + * Calculate the largest links block + * + * @param array $additionalRowsCount + * @param int $productId + * @return array + */ + public function getAdditionalRowsCount($additionalRowsCount, $productId): array + { + if (!empty($this->downloadableData[$productId])) { + $additionalRowsCount = max($additionalRowsCount, count($this->downloadableData[$productId])); + } + return $additionalRowsCount; + } +} diff --git a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php index c9cdf52f55dd1..f148550dd96bb 100644 --- a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php +++ b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php @@ -896,12 +896,16 @@ protected function parseSampleOption($values) protected function uploadDownloadableFiles($fileName, $type = 'links', $renameFileOff = false) { try { - $res = $this->uploaderHelper->getUploader($type, $this->parameters)->move($fileName, $renameFileOff); - return $res['file']; + $uploader = $this->uploaderHelper->getUploader($type, $this->parameters); + if (!$this->uploaderHelper->isFileExist($fileName)) { + $uploader->move($fileName, $renameFileOff); + $fileName = $uploader['file']; + } } catch (\Exception $e) { $this->_entityModel->addRowError(self::ERROR_MOVE_FILE, $this->rowNum); - return ''; + $fileName = ''; } + return $fileName; } /** diff --git a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php index 9cb6b061b14ee..482bfa4f7c569 100644 --- a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php +++ b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php @@ -9,7 +9,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManager; /** - * Class DownloadableTest + * Class DownloadableTest for downloadable products import * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -164,7 +164,7 @@ protected function setUp() // 7. $fileHelper $this->uploaderHelper = $this->createPartialMock( \Magento\DownloadableImportExport\Helper\Uploader::class, - ['getUploader'] + ['getUploader', 'isFileExist'] ); $this->uploaderHelper->expects($this->any())->method('getUploader')->willReturn($this->uploaderMock); $this->downloadableHelper = $this->createPartialMock( @@ -660,6 +660,7 @@ public function testSetUploaderDirFalse($newSku, $bunch, $allowImport, $parsedOp $metadataPoolMock->expects($this->any())->method('getLinkField')->willReturn('entity_id'); $this->downloadableHelper->expects($this->atLeastOnce()) ->method('fillExistOptions')->willReturn($parsedOptions['link']); + $this->uploaderHelper->method('isFileExist')->willReturn(false); $this->downloadableModelMock = $this->objectManagerHelper->getObject( \Magento\DownloadableImportExport\Model\Import\Product\Type\Downloadable::class, diff --git a/app/code/Magento/DownloadableImportExport/etc/di.xml b/app/code/Magento/DownloadableImportExport/etc/di.xml new file mode 100644 index 0000000000000..06768d3e72a8b --- /dev/null +++ b/app/code/Magento/DownloadableImportExport/etc/di.xml @@ -0,0 +1,16 @@ +<?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\CatalogImportExport\Model\Export\RowCustomizer\Composite"> + <arguments> + <argument name="customizers" xsi:type="array"> + <item name="downloadableProduct" xsi:type="string">Magento\DownloadableImportExport\Model\Export\RowCustomizer</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/DownloadableImportExport/etc/export.xml b/app/code/Magento/DownloadableImportExport/etc/export.xml new file mode 100644 index 0000000000000..b6e419cc2c389 --- /dev/null +++ b/app/code/Magento/DownloadableImportExport/etc/export.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:module:Magento_ImportExport:etc/export.xsd"> + <entityType entity="catalog_product" name="downloadable" model="Magento\DownloadableImportExport\Model\Export\Product\Type\Downloadable" /> +</config> diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index 7649c89a07955..5b6252f2c0d6c 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -1021,6 +1021,8 @@ public function load($object, $entityId, $attributes = []) protected function loadAttributesMetadata($attributes) { $this->loadAttributesForObject($attributes); + + return $this; } /** @@ -1433,8 +1435,10 @@ protected function _processSaveData($saveData) $insertEntity = true; $entityTable = $this->getEntityTable(); $entityIdField = $this->getEntityIdField(); + // phpstan:ignore "Undefined variable" $entityId = $newObject->getId(); + // phpstan:ignore "Undefined variable" unset($entityRow[$entityIdField]); if (!empty($entityId) && is_numeric($entityId)) { $bind = ['entity_id' => $entityId]; @@ -1450,6 +1454,7 @@ protected function _processSaveData($saveData) /** * Process base row */ + // phpstan:ignore "Undefined variable" $entityObject = new DataObject($entityRow); $entityRow = $this->_prepareDataForTable($entityObject, $entityTable); if ($insertEntity) { @@ -1460,6 +1465,7 @@ protected function _processSaveData($saveData) $connection->insert($entityTable, $entityRow); $entityId = $connection->lastInsertId($entityTable); } + // phpstan:ignore "Undefined variable" $newObject->setId($entityId); } else { $where = sprintf('%s=%d', $connection->quoteIdentifier($entityIdField), $entityId); @@ -1472,6 +1478,7 @@ protected function _processSaveData($saveData) if (!empty($insert)) { foreach ($insert as $attributeId => $value) { $attribute = $this->getAttribute($attributeId); + // phpstan:ignore "Undefined variable" $this->_insertAttribute($newObject, $attribute, $value); } } @@ -1482,6 +1489,7 @@ protected function _processSaveData($saveData) if (!empty($update)) { foreach ($update as $attributeId => $v) { $attribute = $this->getAttribute($attributeId); + // phpstan:ignore "Undefined variable" $this->_updateAttribute($newObject, $attribute, $v['value_id'], $v['value']); } } @@ -1491,12 +1499,14 @@ protected function _processSaveData($saveData) */ if (!empty($delete)) { foreach ($delete as $table => $values) { + // phpstan:ignore "Undefined variable" $this->_deleteAttributes($newObject, $table, $values); } } $this->_processAttributeValues(); + // phpstan:ignore "Undefined variable" $newObject->isObjectNew(false); return $this; @@ -1573,7 +1583,7 @@ protected function _processAttributeValues() { $connection = $this->getConnection(); foreach ($this->_attributeValuesToSave as $table => $data) { - $connection->insertOnDuplicate($table, $data, ['value']); + $connection->insertOnDuplicate($table, $data, array_keys($data[0])); } foreach ($this->_attributeValuesToDelete as $table => $valueIds) { @@ -1607,7 +1617,9 @@ protected function _prepareValueForSave($value, AbstractAttribute $attribute) self::$_attributeBackendTables[$backendTable] = $this->getConnection()->describeTable($backendTable); } $describe = self::$_attributeBackendTables[$backendTable]; - return $this->getConnection()->prepareColumnValue($describe['value'], $value); + $columnName = $attribute->isStatic() ? $attribute->getAttributeCode() : 'value'; + + return $this->getConnection()->prepareColumnValue($describe[$columnName], $value); } /** diff --git a/app/code/Magento/Eav/etc/adminhtml/system.xml b/app/code/Magento/Eav/etc/adminhtml/system.xml index 86916abe812d9..2fe1564786997 100644 --- a/app/code/Magento/Eav/etc/adminhtml/system.xml +++ b/app/code/Magento/Eav/etc/adminhtml/system.xml @@ -8,9 +8,9 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="dev"> - <group id="caching" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="caching" translate="label" type="text" sortOrder="120" showInDefault="1"> <label>Caching Settings</label> - <field id="cache_user_defined_attributes" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="cache_user_defined_attributes" translate="label comment" type="select" sortOrder="10" showInDefault="1" canRestore="1"> <label>Cache User Defined Attributes</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>By default only system EAV attributes are cached.</comment> diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php index b9102bc5e00c4..ddf79f413df37 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php @@ -285,7 +285,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => false, + 'index' => true, ], ], ], @@ -296,7 +296,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'mapping' => $this->prepareFieldInfo( [ 'type' => 'text', - 'index' => false, + 'index' => true, ] ), ], diff --git a/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php index d933d8bb5d0b5..d2b677a95c7c0 100644 --- a/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php @@ -278,7 +278,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'match_mapping' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => 'no' + 'index' => 'not_analyzed', ], ], ], @@ -288,7 +288,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'match_mapping' => 'string', 'mapping' => [ 'type' => 'string', - 'index' => 'no' + 'index' => 'not_analyzed', ], ], ] diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index 2ee34b6b4c5b1..4693b7502c5c1 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -7,8 +7,8 @@ namespace Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; -use Magento\Framework\Data\Collection; use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Framework\Data\Collection; /** * Resolve specific attributes for search criteria. @@ -60,6 +60,7 @@ public function apply() { if (empty($this->searchResult->getItems())) { $this->collection->getSelect()->where('NULL'); + return; } @@ -71,7 +72,7 @@ public function apply() $this->collection->getSelect()->where('e.entity_id IN (?)', $ids); $orderList = join(',', $ids); $this->collection->getSelect()->reset(\Magento\Framework\DB\Select::ORDER); - $this->collection->getSelect()->order("FIELD(e.entity_id,$orderList)"); + $this->collection->getSelect()->order(new \Zend_Db_Expr("FIELD(e.entity_id,$orderList)")); } /** @@ -85,15 +86,33 @@ public function apply() private function sliceItems(array $items, int $size, int $currentPage): array { if ($size !== 0) { - $totalPages = (int) ceil(count($items)/$size); - $currentPage = min($currentPage, $totalPages); - $offset = ($currentPage - 1) * $size; - if ($offset < 0) { - $offset = 0; + // Check that current page is in a range of allowed page numbers, based on items count and items per page, + // than calculate offset for slicing items array. + $itemsCount = count($items); + $maxAllowedPageNumber = ceil($itemsCount/$size); + if ($currentPage < 1) { + $currentPage = 1; } - $items = array_slice($items, $offset, $this->size); + if ($currentPage > $maxAllowedPageNumber) { + $currentPage = $maxAllowedPageNumber; + } + + $offset = $this->getOffset($currentPage, $size); + $items = array_slice($items, $offset, $size); } return $items; } + + /** + * Get offset for given page. + * + * @param int $pageNumber + * @param int $pageSize + * @return int + */ + private function getOffset(int $pageNumber, int $pageSize): int + { + return ($pageNumber - 1) * $pageSize; + } } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php index 99fd416b5cd3e..5a735da96b754 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php @@ -11,7 +11,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; /** - * Class ElasticsearchTest + * Test elasticsearch client methods. */ class ElasticsearchTest extends \PHPUnit\Framework\TestCase { @@ -379,7 +379,7 @@ public function testAddFieldsMapping() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => false + 'index' => true ], ], ], @@ -389,7 +389,7 @@ public function testAddFieldsMapping() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'text', - 'index' => false, + 'index' => true, ], ], ], @@ -449,7 +449,7 @@ public function testAddFieldsMappingFailure() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => false + 'index' => true, ], ], ], @@ -459,7 +459,7 @@ public function testAddFieldsMappingFailure() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'text', - 'index' => false, + 'index' => true, ], ], ] diff --git a/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml index 7e3c83dae9847..6d87c4948a9d9 100644 --- a/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml +++ b/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml @@ -10,52 +10,52 @@ <section id="catalog"> <group id="search"> <!-- Elasticsearch 2.0+ --> - <field id="elasticsearch_server_hostname" translate="label" type="text" sortOrder="61" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_server_hostname" translate="label" type="text" sortOrder="61" showInDefault="1"> <label>Elasticsearch Server Hostname</label> <depends> <field id="engine">elasticsearch</field> </depends> </field> - <field id="elasticsearch_server_port" translate="label" type="text" sortOrder="62" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_server_port" translate="label" type="text" sortOrder="62" showInDefault="1"> <label>Elasticsearch Server Port</label> <depends> <field id="engine">elasticsearch</field> </depends> </field> - <field id="elasticsearch_index_prefix" translate="label" type="text" sortOrder="63" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_index_prefix" translate="label" type="text" sortOrder="63" showInDefault="1"> <label>Elasticsearch Index Prefix</label> <depends> <field id="engine">elasticsearch</field> </depends> </field> - <field id="elasticsearch_enable_auth" translate="label" type="select" sortOrder="64" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_enable_auth" translate="label" type="select" sortOrder="64" showInDefault="1"> <label>Enable Elasticsearch HTTP Auth</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> <field id="engine">elasticsearch</field> </depends> </field> - <field id="elasticsearch_username" translate="label" type="text" sortOrder="65" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_username" translate="label" type="text" sortOrder="65" showInDefault="1"> <label>Elasticsearch HTTP Username</label> <depends> <field id="engine">elasticsearch</field> <field id="elasticsearch_enable_auth">1</field> </depends> </field> - <field id="elasticsearch_password" translate="label" type="text" sortOrder="66" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_password" translate="label" type="text" sortOrder="66" showInDefault="1"> <label>Elasticsearch HTTP Password</label> <depends> <field id="engine">elasticsearch</field> <field id="elasticsearch_enable_auth">1</field> </depends> </field> - <field id="elasticsearch_server_timeout" translate="label" type="text" sortOrder="67" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_server_timeout" translate="label" type="text" sortOrder="67" showInDefault="1"> <label>Elasticsearch Server Timeout</label> <depends> <field id="engine">elasticsearch</field> </depends> </field> - <field id="elasticsearch_test_connect_wizard" translate="button_label" sortOrder="68" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_test_connect_wizard" translate="button_label" sortOrder="68" showInDefault="1"> <label/> <button_label>Test Connection</button_label> <frontend_model>Magento\Elasticsearch\Block\Adminhtml\System\Config\TestConnection</frontend_model> @@ -63,7 +63,7 @@ <field id="engine">elasticsearch</field> </depends> </field> - <field id="elasticsearch_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1"> <label>Minimum Terms to Match</label> <depends> <field id="engine">elasticsearch</field> @@ -72,52 +72,52 @@ <backend_model>Magento\Elasticsearch\Model\Config\Backend\MinimumShouldMatch</backend_model> </field> <!-- Elasticsearch 5.x --> - <field id="elasticsearch5_server_hostname" translate="label" type="text" sortOrder="61" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_server_hostname" translate="label" type="text" sortOrder="61" showInDefault="1"> <label>Elasticsearch Server Hostname</label> <depends> <field id="engine">elasticsearch5</field> </depends> </field> - <field id="elasticsearch5_server_port" translate="label" type="text" sortOrder="62" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_server_port" translate="label" type="text" sortOrder="62" showInDefault="1"> <label>Elasticsearch Server Port</label> <depends> <field id="engine">elasticsearch5</field> </depends> </field> - <field id="elasticsearch5_index_prefix" translate="label" type="text" sortOrder="63" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_index_prefix" translate="label" type="text" sortOrder="63" showInDefault="1"> <label>Elasticsearch Index Prefix</label> <depends> <field id="engine">elasticsearch5</field> </depends> </field> - <field id="elasticsearch5_enable_auth" translate="label" type="select" sortOrder="64" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_enable_auth" translate="label" type="select" sortOrder="64" showInDefault="1"> <label>Enable Elasticsearch HTTP Auth</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> <field id="engine">elasticsearch5</field> </depends> </field> - <field id="elasticsearch5_username" translate="label" type="text" sortOrder="65" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_username" translate="label" type="text" sortOrder="65" showInDefault="1"> <label>Elasticsearch HTTP Username</label> <depends> <field id="engine">elasticsearch5</field> <field id="elasticsearch5_enable_auth">1</field> </depends> </field> - <field id="elasticsearch5_password" translate="label" type="text" sortOrder="66" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_password" translate="label" type="text" sortOrder="66" showInDefault="1"> <label>Elasticsearch HTTP Password</label> <depends> <field id="engine">elasticsearch5</field> <field id="elasticsearch5_enable_auth">1</field> </depends> </field> - <field id="elasticsearch5_server_timeout" translate="label" type="text" sortOrder="67" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_server_timeout" translate="label" type="text" sortOrder="67" showInDefault="1"> <label>Elasticsearch Server Timeout</label> <depends> <field id="engine">elasticsearch5</field> </depends> </field> - <field id="elasticsearch5_test_connect_wizard" translate="button_label" sortOrder="68" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_test_connect_wizard" translate="button_label" sortOrder="68" showInDefault="1"> <label/> <button_label>Test Connection</button_label> <frontend_model>Magento\Elasticsearch\Block\Adminhtml\System\Config\Elasticsearch5\TestConnection</frontend_model> @@ -125,7 +125,7 @@ <field id="engine">elasticsearch5</field> </depends> </field> - <field id="elasticsearch5_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch5_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1"> <label>Minimum Terms to Match</label> <depends> <field id="engine">elasticsearch5</field> diff --git a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php index ff299be786aa8..2c1c283c5b24d 100644 --- a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php @@ -292,7 +292,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => false, + 'index' => true, ], ], ], @@ -302,7 +302,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'text', - 'index' => false, + 'index' => true, 'copy_to' => '_search' ], ], diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearch6Test.xml b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearch6Test.xml new file mode 100644 index 0000000000000..e763df7dd3227 --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearch6Test.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="StorefrontProductQuickSearchUsingElasticSearch6Test"> + <annotations> + <features value="CatalogSearch"/> + <stories value="Storefront Search"/> + <title value="Product quick search doesn't throw exception after ES is chosen as search engine with different amount per page"/> + <description value="Verify no elastic search exception is thrown when searching for products, when displayed products per page are greater or equal the size of available products."/> + <severity value="BLOCKER"/> + <testCaseId value="MC-28917"/> + <useCaseId value="MC-25138"/> + <group value="catalog"/> + <group value="elasticsearch"/> + <group value="SearchEngineElasticsearch"/> + <group value="catalog_search"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="createFirstProduct"> + <field key="name">AAA Product Simple AAA</field> + </createData> + <createData entity="SimpleProduct2" stepKey="createSecondProduct"> + <field key="name">Product Simple AAA</field> + </createData> + <magentoCLI command="config:set {{CustomGridPerPageValuesConfigData.path}} {{CustomGridPerPageValuesConfigData.value}}" stepKey="setCustomGridPerPageValues"/> + </before> + + <after> + <deleteData createDataKey="createFirstProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="createSecondProduct" stepKey="deleteSecondProduct"/> + <magentoCLI command="config:set {{DefaultGridPerPageValuesConfigData.path}} {{DefaultGridPerPageValuesConfigData.value}}" stepKey="setDefaultGridPerPageValues"/> + </after> + + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStorefrontHomePage"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchSimpleProduct"> + <argument name="phrase" value="AAA"/> + </actionGroup> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertFirstProductOnCatalogSearchPage"> + <argument name="product" value="$createFirstProduct$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup" stepKey="assertSecondProductIsMissingOnCatalogSearchPage"> + <argument name="productName" value="$createSecondProduct.name$"/> + </actionGroup> + <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickNextPageCatalogSearchPager"/> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertSecondProductOnCatalogSearchPage"> + <argument name="product" value="$createSecondProduct$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup" stepKey="assertFirstProductIsMissingOnCatalogSearchPage"> + <argument name="productName" value="$createFirstProduct.name$"/> + </actionGroup> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="2" stepKey="selectDisplayedProductInGridPerPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertFirstProductDisplayedOnCatalogSearchPage"> + <argument name="product" value="$createFirstProduct$"/> + </actionGroup> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertSecondProductDisplayedOnCatalogSearchPage"> + <argument name="product" value="$createSecondProduct$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearch6WithNotAvailablePageTest.xml b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearch6WithNotAvailablePageTest.xml new file mode 100644 index 0000000000000..b4eb436fc1b2a --- /dev/null +++ b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearch6WithNotAvailablePageTest.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="StorefrontProductQuickSearchUsingElasticSearch6WithNotAvailablePageTest" extends="StorefrontProductQuickSearchUsingElasticSearch6Test"> + <annotations> + <features value="CatalogSearch"/> + <stories value="Storefront Search"/> + <title value="Product quick search doesn't throw exception after ES is chosen as search engine with selected page out of range"/> + <description value="Verify no elastic search exception is thrown when try to get page with selected page out of range."/> + <severity value="BLOCKER"/> + <testCaseId value="MC-29383"/> + <useCaseId value="MC-25138"/> + <group value="catalog"/> + <group value="elasticsearch"/> + <group value="SearchEngineElasticsearch"/> + <group value="catalog_search"/> + </annotations> + <remove keyForRemoval="selectDisplayedProductInGridPerPage"/> + <remove keyForRemoval="assertFirstProductDisplayedOnCatalogSearchPage"/> + <remove keyForRemoval="assertSecondProductDisplayedOnCatalogSearchPage"/> + <grabTextFrom selector="{{StorefrontCategoryBottomToolbarSection.currentPage}}" stepKey="grabNumberOfLastPage"/> + <actionGroup ref="StorefrontQuickSearchWithPaginationActionGroup" stepKey="navigateToUnavailableCatalogSearchResultPage"> + <argument name="phrase" value="AAA"/> + <argument name="pageNumber" value="999"/> + </actionGroup> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.currentPage}}" stepKey="scrollToBottomToolbarPager"/> + <grabTextFrom selector="{{StorefrontCategoryBottomToolbarSection.currentPage}}" stepKey="grabNumberOfCurrentPage"/> + <assertEquals stepKey="assertCurrentPageIsLastPageOfCatalogSearchResult"> + <expectedResult type="variable">grabNumberOfLastPage</expectedResult> + <actualResult type="variable">grabNumberOfCurrentPage</actualResult> + </assertEquals> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertProductOnLastCatalogSearchPage"> + <argument name="product" value="$createSecondProduct$"/> + </actionGroup> + <actionGroup ref="StorefrontCheckProductIsMissingInCategoryProductsPageActionGroup" stepKey="assertFirstProductIsMissingOnLastCatalogSearchPage"> + <argument name="productName" value="$createFirstProduct.name$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php index 607624d7b5e8e..3d840d5a808af 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php @@ -445,7 +445,7 @@ public function testAddFieldsMapping() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => false + 'index' => true, ], ], ], @@ -455,7 +455,7 @@ public function testAddFieldsMapping() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'text', - 'index' => false, + 'index' => true, 'copy_to' => '_search' ], ], @@ -515,7 +515,7 @@ public function testAddFieldsMappingFailure() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => false + 'index' => true, ], ], ], @@ -525,7 +525,7 @@ public function testAddFieldsMappingFailure() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'text', - 'index' => false, + 'index' => true, 'copy_to' => '_search' ], ], diff --git a/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml index 5c6a9357a5f6f..fee234ada43b4 100644 --- a/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml +++ b/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml @@ -11,32 +11,28 @@ <section id="catalog"> <group id="search"> <!-- Elasticsearch 6.x --> - <field id="elasticsearch6_server_hostname" translate="label" type="text" sortOrder="71" - showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_server_hostname" translate="label" type="text" sortOrder="71" showInDefault="1"> <label>Elasticsearch Server Hostname</label> <depends> <field id="engine">elasticsearch6</field> </depends> </field> - <field id="elasticsearch6_server_port" translate="label" type="text" sortOrder="72" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_server_port" translate="label" type="text" sortOrder="72" showInDefault="1"> <label>Elasticsearch Server Port</label> <depends> <field id="engine">elasticsearch6</field> </depends> </field> - <field id="elasticsearch6_index_prefix" translate="label" type="text" sortOrder="73" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_index_prefix" translate="label" type="text" sortOrder="73" showInDefault="1"> <label>Elasticsearch Index Prefix</label> <depends> <field id="engine">elasticsearch6</field> </depends> </field> - <field id="elasticsearch6_enable_auth" translate="label" type="select" sortOrder="74" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_enable_auth" translate="label" type="select" sortOrder="74" showInDefault="1"> <label>Enable Elasticsearch HTTP Auth</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> @@ -44,8 +40,7 @@ </depends> </field> - <field id="elasticsearch6_username" translate="label" type="text" sortOrder="75" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_username" translate="label" type="text" sortOrder="75" showInDefault="1"> <label>Elasticsearch HTTP Username</label> <depends> <field id="engine">elasticsearch6</field> @@ -53,8 +48,7 @@ </depends> </field> - <field id="elasticsearch6_password" translate="label" type="text" sortOrder="76" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_password" translate="label" type="text" sortOrder="76" showInDefault="1"> <label>Elasticsearch HTTP Password</label> <depends> <field id="engine">elasticsearch6</field> @@ -62,16 +56,14 @@ </depends> </field> - <field id="elasticsearch6_server_timeout" translate="label" type="text" sortOrder="77" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_server_timeout" translate="label" type="text" sortOrder="77" showInDefault="1"> <label>Elasticsearch Server Timeout</label> <depends> <field id="engine">elasticsearch6</field> </depends> </field> - <field id="elasticsearch6_test_connect_wizard" translate="button_label" sortOrder="78" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_test_connect_wizard" translate="button_label" sortOrder="78" showInDefault="1"> <label/> <button_label>Test Connection</button_label> <frontend_model>Magento\Elasticsearch6\Block\Adminhtml\System\Config\TestConnection</frontend_model> @@ -79,8 +71,7 @@ <field id="engine">elasticsearch6</field> </depends> </field> - <field id="elasticsearch6_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1" - showInWebsite="0" showInStore="0"> + <field id="elasticsearch6_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1"> <label>Minimum Terms to Match</label> <depends> <field id="engine">elasticsearch6</field> diff --git a/app/code/Magento/Email/Model/AbstractTemplate.php b/app/code/Magento/Email/Model/AbstractTemplate.php index 8acb0a9fddb75..3d4fc252b57ff 100644 --- a/app/code/Magento/Email/Model/AbstractTemplate.php +++ b/app/code/Magento/Email/Model/AbstractTemplate.php @@ -339,7 +339,6 @@ public function loadDefault($templateId) public function getProcessedTemplate(array $variables = []) { $processor = $this->getTemplateFilter() - ->setUseSessionInUrl(false) ->setPlainTemplateMode($this->isPlain()) ->setIsChildTemplate($this->isChildTemplate()) ->setTemplateProcessor([$this, 'getTemplateContent']); diff --git a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php index 036ab1b273fb0..8598138e77c50 100644 --- a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php @@ -69,9 +69,6 @@ protected function setUp() $this->design = $this->getMockBuilder(\Magento\Framework\View\DesignInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->registry = $this->getMockBuilder(\Magento\Framework\Registry::class) - ->disableOriginalConstructor() - ->getMock(); $this->appEmulation = $this->getMockBuilder(\Magento\Store\Model\App\Emulation::class) ->disableOriginalConstructor() ->getMock(); @@ -177,7 +174,7 @@ public function testGetProcessedTemplate($variables, $templateType, $storeId, $e ->disableOriginalConstructor() ->getMock(); - $filterTemplate->expects($this->once()) + $filterTemplate->expects($this->never()) ->method('setUseSessionInUrl') ->with(false) ->will($this->returnSelf()); @@ -253,7 +250,6 @@ public function testGetProcessedTemplateException() $filterTemplate = $this->getMockBuilder(\Magento\Email\Model\Template\Filter::class) ->setMethods( [ - 'setUseSessionInUrl', 'setPlainTemplateMode', 'setIsChildTemplate', 'setDesignParams', @@ -267,9 +263,7 @@ public function testGetProcessedTemplateException() ) ->disableOriginalConstructor() ->getMock(); - $filterTemplate->expects($this->once()) - ->method('setUseSessionInUrl') - ->will($this->returnSelf()); + $filterTemplate->expects($this->once()) ->method('setPlainTemplateMode') ->will($this->returnSelf()); diff --git a/app/code/Magento/Email/etc/config.xml b/app/code/Magento/Email/etc/config.xml index 0731fc79c15f7..a9837a4f2917c 100644 --- a/app/code/Magento/Email/etc/config.xml +++ b/app/code/Magento/Email/etc/config.xml @@ -27,6 +27,7 @@ <disable>0</disable> <host>localhost</host> <port>25</port> + <set_return_path>0</set_return_path> </smtp> </system> <trans_email> diff --git a/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml b/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml index 928ce5a2a918f..016c609ae7762 100644 --- a/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml +++ b/app/code/Magento/Fedex/Test/Mftf/Test/AdminCreatingShippingLabelTest.xml @@ -111,7 +111,7 @@ <!--Create Invoice--> <actionGroup ref="AdminCreateInvoiceActionGroup" stepKey="createInvoice"/> <!--Create shipping label--> - <actionGroup ref="goToShipmentIntoOrder" stepKey="goToShipmentIntoOrder"/> + <actionGroup ref="GoToShipmentIntoOrderActionGroup" stepKey="goToShipmentIntoOrder"/> <checkOption selector="{{AdminShipmentTotalSection.createShippingLabel}}" stepKey="checkCreateShippingLabel"/> <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> <actionGroup ref="AdminShipmentCreateShippingLabelActionGroup" stepKey="createPackage"> diff --git a/app/code/Magento/Fedex/etc/adminhtml/system.xml b/app/code/Magento/Fedex/etc/adminhtml/system.xml index fdbe10a1a352e..f164a8e21e0ae 100644 --- a/app/code/Magento/Fedex/etc/adminhtml/system.xml +++ b/app/code/Magento/Fedex/etc/adminhtml/system.xml @@ -10,101 +10,101 @@ <section id="carriers"> <group id="fedex" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="1"> <label>FedEx</label> - <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled for Checkout</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="account" translate="label comment" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="account" translate="label comment" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1"> <label>Account ID</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <comment>Please make sure to use only digits here. No dashes are allowed.</comment> </field> - <field id="meter_number" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="meter_number" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>Meter Number</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="key" translate="label" type="obscure" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="key" translate="label" type="obscure" sortOrder="60" showInDefault="1" showInWebsite="1"> <label>Key</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="password" translate="label" type="obscure" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="password" translate="label" type="obscure" sortOrder="70" showInDefault="1" showInWebsite="1"> <label>Password</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="sandbox_mode" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sandbox_mode" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Sandbox Mode</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="production_webservices_url" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="production_webservices_url" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Web-Services URL (Production)</label> <depends> <field id="sandbox_mode">0</field> </depends> </field> - <field id="sandbox_webservices_url" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sandbox_webservices_url" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Web-Services URL (Sandbox)</label> <depends> <field id="sandbox_mode">1</field> </depends> </field> - <field id="shipment_requesttype" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="shipment_requesttype" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Packages Request Type</label> <source_model>Magento\Shipping\Model\Config\Source\Online\Requesttype</source_model> </field> - <field id="packaging" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="packaging" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Packaging</label> <source_model>Magento\Fedex\Model\Source\Packaging</source_model> </field> - <field id="dropoff" translate="label" type="select" sortOrder="130" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="dropoff" translate="label" type="select" sortOrder="130" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Dropoff</label> <source_model>Magento\Fedex\Model\Source\Dropoff</source_model> </field> - <field id="unit_of_measure" translate="label" type="select" sortOrder="135" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="unit_of_measure" translate="label" type="select" sortOrder="135" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Weight Unit</label> <source_model>Magento\Fedex\Model\Source\Unitofmeasure</source_model> </field> - <field id="max_package_weight" translate="label" type="text" sortOrder="140" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="max_package_weight" translate="label" type="text" sortOrder="140" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Maximum Package Weight (Please consult your shipping carrier for maximum supported shipping weight)</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="handling_type" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_type" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Calculate Handling Fee</label> <source_model>Magento\Shipping\Model\Source\HandlingType</source_model> </field> - <field id="handling_action" translate="label" type="select" sortOrder="160" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_action" translate="label" type="select" sortOrder="160" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Handling Applied</label> <source_model>Magento\Shipping\Model\Source\HandlingAction</source_model> </field> - <field id="handling_fee" translate="label" type="text" sortOrder="170" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="handling_fee" translate="label" type="text" sortOrder="170" showInDefault="1" showInWebsite="1"> <label>Handling Fee</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="residence_delivery" translate="label" type="select" sortOrder="180" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="residence_delivery" translate="label" type="select" sortOrder="180" showInDefault="1" showInWebsite="1"> <label>Residential Delivery</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="allowed_methods" translate="label" type="multiselect" sortOrder="190" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowed_methods" translate="label" type="multiselect" sortOrder="190" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allowed Methods</label> <source_model>Magento\Fedex\Model\Source\Method</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="smartpost_hubid" translate="label comment" type="text" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="smartpost_hubid" translate="label comment" type="text" sortOrder="200" showInDefault="1" showInWebsite="1"> <label>Hub ID</label> <comment>The field is applicable if the Smart Post method is selected.</comment> </field> - <field id="free_method" translate="label" type="select" sortOrder="210" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="free_method" translate="label" type="select" sortOrder="210" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Free Method</label> <frontend_class>free-method</frontend_class> <source_model>Magento\Fedex\Model\Source\Freemethod</source_model> </field> - <field id="free_shipping_enable" translate="label" type="select" sortOrder="220" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_enable" translate="label" type="select" sortOrder="220" showInDefault="1" showInWebsite="1"> <label>Enable Free Shipping Threshold</label> <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> </field> - <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="230" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="230" showInDefault="1" showInWebsite="1"> <label>Free Shipping Amount Threshold</label> <validate>validate-number validate-zero-or-greater</validate> <depends> @@ -114,26 +114,26 @@ <field id="specificerrmsg" translate="label" type="textarea" sortOrder="240" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Displayed Error Message</label> </field> - <field id="sallowspecific" translate="label" type="select" sortOrder="250" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sallowspecific" translate="label" type="select" sortOrder="250" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ship to Applicable Countries</label> <frontend_class>shipping-applicable-country</frontend_class> <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="260" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="260" showInDefault="1" showInWebsite="1"> <label>Ship to Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="debug" translate="label" type="select" sortOrder="270" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="270" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="showmethod" translate="label" type="select" sortOrder="280" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="showmethod" translate="label" type="select" sortOrder="280" showInDefault="1" showInWebsite="1"> <label>Show Method if Not Applicable</label> <frontend_class>shipping-skip-hide</frontend_class> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="sort_order" translate="label" type="text" sortOrder="290" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="290" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number validate-zero-or-greater</validate> </field> diff --git a/app/code/Magento/GiftMessage/etc/adminhtml/system.xml b/app/code/Magento/GiftMessage/etc/adminhtml/system.xml index 6779ea6f28174..fa437a0735cf4 100644 --- a/app/code/Magento/GiftMessage/etc/adminhtml/system.xml +++ b/app/code/Magento/GiftMessage/etc/adminhtml/system.xml @@ -8,13 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="sales"> - <group id="gift_options" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="gift_options" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Gift Options</label> - <field id="allow_order" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allow_order" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allow Gift Messages on Order Level</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="allow_items" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allow_items" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allow Gift Messages for Order Items</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/GoogleAdwords/Test/Mftf/ActionGroup/AdminNavigateToGoogleAdwordsConfigurationActionGroup.xml b/app/code/Magento/GoogleAdwords/Test/Mftf/ActionGroup/AdminNavigateToGoogleAdwordsConfigurationActionGroup.xml new file mode 100644 index 0000000000000..c581862a3b34c --- /dev/null +++ b/app/code/Magento/GoogleAdwords/Test/Mftf/ActionGroup/AdminNavigateToGoogleAdwordsConfigurationActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminNavigateToGoogleAdwordsConfigurationActionGroup"> + <amOnPage url="{{AdminGoogleAdwordsConfigPage.url}}" stepKey="navigateToConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/GoogleAdwords/Test/Mftf/Page/AdminGoogleAdwordsConfigPage.xml b/app/code/Magento/GoogleAdwords/Test/Mftf/Page/AdminGoogleAdwordsConfigPage.xml new file mode 100644 index 0000000000000..842b867ed9407 --- /dev/null +++ b/app/code/Magento/GoogleAdwords/Test/Mftf/Page/AdminGoogleAdwordsConfigPage.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="AdminGoogleAdwordsConfigPage" url="admin/system_config/edit/section/google/" area="admin" + module="Magento_Config"> + <section name="AdminGoogleAdwordsConfigSection"/> + </page> +</pages> diff --git a/app/code/Magento/GoogleAdwords/Test/Mftf/Section/AdminGoogleAdwordsConfigSection.xml b/app/code/Magento/GoogleAdwords/Test/Mftf/Section/AdminGoogleAdwordsConfigSection.xml new file mode 100644 index 0000000000000..d6ee2ced1afe1 --- /dev/null +++ b/app/code/Magento/GoogleAdwords/Test/Mftf/Section/AdminGoogleAdwordsConfigSection.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="AdminGoogleAdwordsConfigSection"> + <element name="active" type="text" selector="#google_adwords_active"/> + <element name="conversionId" type="text" selector="#google_adwords_conversion_id"/> + </section> +</sections> diff --git a/app/code/Magento/GoogleAdwords/Test/Mftf/Test/AdminValidateConversionIdConfigTest.xml b/app/code/Magento/GoogleAdwords/Test/Mftf/Test/AdminValidateConversionIdConfigTest.xml new file mode 100644 index 0000000000000..7c0214a8654c8 --- /dev/null +++ b/app/code/Magento/GoogleAdwords/Test/Mftf/Test/AdminValidateConversionIdConfigTest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminValidateConversionIdConfigTest"> + <annotations> + <stories value="Admin validates the conversion ID when configuring the Google Adwords"/> + <title value="Admin validates the conversion ID when configuring the Google Adwords"/> + <description value="Testing for a required Conversion ID when configuring the Google Adwords"/> + <severity value="MINOR"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateToGoogleAdwordsConfigurationActionGroup" stepKey="goToConfigPage"/> + <actionGroup ref="AdminExpandConfigSectionActionGroup" stepKey="expandingGoogleAdwordsSection"> + <argument name="sectionName" value="Google AdWords"/> + </actionGroup> + <actionGroup ref="AdminUncheckUseSystemValueActionGroup" stepKey="uncheckUseSystemValue"> + <argument name="rowId" value="row_google_adwords_active"/> + </actionGroup> + <actionGroup ref="AdminToggleEnabledActionGroup" stepKey="enableGoogleAdwordsConfig"> + <argument name="element" value="{{AdminGoogleAdwordsConfigSection.active}}"/> + </actionGroup> + <actionGroup ref="AdminClickFormActionButtonActionGroup" stepKey="clickSaveCustomVariable"> + <argument name="buttonSelector" value="{{AdminMainActionsSection.save}}"/> + </actionGroup> + <actionGroup ref="AssertAdminValidationErrorActionGroup" stepKey="seeRequiredValidationErrorForConversionId"> + <argument name="inputId" value="google_adwords_conversion_id"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/GoogleAdwords/etc/adminhtml/system.xml b/app/code/Magento/GoogleAdwords/etc/adminhtml/system.xml index c312028cf63be..74a16ae6acc62 100644 --- a/app/code/Magento/GoogleAdwords/etc/adminhtml/system.xml +++ b/app/code/Magento/GoogleAdwords/etc/adminhtml/system.xml @@ -17,6 +17,7 @@ <field id="conversion_id" translate="label" type="text" sortOrder="11" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Conversion ID</label> <backend_model>Magento\GoogleAdwords\Model\Config\Backend\ConversionId</backend_model> + <validate>required-entry validate-number</validate> <depends> <field id="*/*/active">1</field> </depends> diff --git a/app/code/Magento/GoogleAnalytics/etc/adminhtml/system.xml b/app/code/Magento/GoogleAnalytics/etc/adminhtml/system.xml index 3491b4d456b81..dd59de40a8528 100644 --- a/app/code/Magento/GoogleAnalytics/etc/adminhtml/system.xml +++ b/app/code/Magento/GoogleAnalytics/etc/adminhtml/system.xml @@ -13,7 +13,7 @@ <resource>Magento_GoogleAnalytics::google</resource> <group id="analytics" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Google Analytics</label> - <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enable</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/GoogleAnalytics/etc/config.xml b/app/code/Magento/GoogleAnalytics/etc/config.xml new file mode 100644 index 0000000000000..f3c9cc67f86ae --- /dev/null +++ b/app/code/Magento/GoogleAnalytics/etc/config.xml @@ -0,0 +1,16 @@ +<?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> + <google> + <analytics> + <active>0</active> + </analytics> + </google> + </default> +</config> diff --git a/app/code/Magento/GraphQl/etc/graphql/di.xml b/app/code/Magento/GraphQl/etc/graphql/di.xml index 03bae5c80e12c..2bcd44e9ae410 100644 --- a/app/code/Magento/GraphQl/etc/graphql/di.xml +++ b/app/code/Magento/GraphQl/etc/graphql/di.xml @@ -7,6 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Framework\App\FrontControllerInterface" type="Magento\GraphQl\Controller\GraphQl" /> + <preference for="Magento\Framework\Authorization\RoleLocatorInterface" type="Magento\Webapi\Model\WebapiRoleLocator" /> <type name="Magento\Authorization\Model\CompositeUserContext"> <arguments> <argument name="userContexts" xsi:type="array"> diff --git a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php index 187fd27fa0554..8eac8d0b0e163 100644 --- a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php @@ -344,7 +344,7 @@ protected function getProductInfo(\Magento\Framework\DataObject $buyRequest, $pr } foreach ($associatedProducts as $subProduct) { if (!isset($productsInfo[$subProduct->getId()])) { - if ($isStrictProcessMode && !$subProduct->getQty()) { + if ($isStrictProcessMode && !$subProduct->getQty() && $subProduct->isSalable()) { return __('Please specify the quantity of product(s).')->render(); } $productsInfo[$subProduct->getId()] = $subProduct->isSalable() ? (float)$subProduct->getQty() : 0; diff --git a/app/code/Magento/GroupedProduct/Model/Wishlist/Product/Item.php b/app/code/Magento/GroupedProduct/Model/Wishlist/Product/Item.php new file mode 100644 index 0000000000000..d84df510195f3 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Model/Wishlist/Product/Item.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GroupedProduct\Model\Wishlist\Product; + +use Magento\Wishlist\Model\Item as WishlistItem; +use Magento\GroupedProduct\Model\Product\Type\Grouped as TypeGrouped; +use Magento\Catalog\Model\Product; + +/** + * Wishlist logic for grouped product + */ +class Item +{ + /** + * Modify Wishlist item based on associated product qty + * + * @param WishlistItem $subject + * @param Product $product + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function beforeRepresentProduct( + WishlistItem $subject, + Product $product + ): array { + if ($product->getTypeId() === TypeGrouped::TYPE_CODE + && $product->getId() === $subject->getProduct()->getId() + ) { + $itemOptions = $subject->getOptionsByCode(); + $productOptions = $product->getCustomOptions(); + + $diff = array_diff_key($itemOptions, $productOptions); + + if (!$diff) { + $buyRequest = $subject->getBuyRequest(); + $superGroupInfo = $buyRequest->getData('super_group'); + + foreach ($itemOptions as $key => $itemOption) { + if (preg_match('/associated_product_\d+/', $key)) { + $simpleId = str_replace('associated_product_', '', $key); + $prodQty = $productOptions[$key]->getValue(); + + $itemOption->setValue($itemOption->getValue() + $prodQty); + + if (isset($superGroupInfo[$simpleId])) { + $superGroupInfo[$simpleId] = $itemOptions[$key]->getValue(); + } + } + } + + $buyRequest->setData('super_group', $superGroupInfo); + + $subject->setOptions($itemOptions); + $subject->mergeBuyRequest($buyRequest); + } + } + + return [$product]; + } + + /** + * Remove associated_product_id key. associated_product_id contains qty + * + * @param WishlistItem $subject + * @param array $options1 + * @param array $options2 + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeCompareOptions( + WishlistItem $subject, + array $options1, + array $options2 + ): array { + $diff = array_diff_key($options1, $options2); + + if (!$diff) { + foreach (array_keys($options1) as $key) { + if (preg_match('/associated_product_\d+/', $key)) { + unset($options1[$key]); + unset($options2[$key]); + } + } + } + + return [$options1, $options2]; + } +} diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/GroupedTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/GroupedTest.php index e50d6491a6aca..407224bf28f32 100644 --- a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/GroupedTest.php +++ b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/GroupedTest.php @@ -8,6 +8,8 @@ use Magento\GroupedProduct\Model\Product\Type\Grouped; /** + * Tests for Grouped product + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class GroupedTest extends \PHPUnit\Framework\TestCase @@ -42,6 +44,9 @@ class GroupedTest extends \PHPUnit\Framework\TestCase */ private $serializer; + /** + * @inheritdoc + */ protected function setUp() { $this->objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -74,12 +79,22 @@ protected function setUp() ); } - public function testHasWeightFalse() + /** + * Verify has weight is false + * + * @return void + */ + public function testHasWeightFalse(): void { $this->assertFalse($this->_model->hasWeight(), 'This product has weight, but it should not'); } - public function testGetChildrenIds() + /** + * Verify children ids. + * + * @return void + */ + public function testGetChildrenIds(): void { $parentId = 12345; $childrenIds = [100, 200, 300]; @@ -96,7 +111,12 @@ public function testGetChildrenIds() $this->assertEquals($childrenIds, $this->_model->getChildrenIds($parentId)); } - public function testGetParentIdsByChild() + /** + * Verify get parents by child products + * + * @return void + */ + public function testGetParentIdsByChild(): void { $childId = 12345; $parentIds = [100, 200, 300]; @@ -113,7 +133,12 @@ public function testGetParentIdsByChild() $this->assertEquals($parentIds, $this->_model->getParentIdsByChild($childId)); } - public function testGetAssociatedProducts() + /** + * Verify get associated products + * + * @return void + */ + public function testGetAssociatedProducts(): void { $cached = true; $associatedProducts = [5, 7, 11, 13, 17]; @@ -123,12 +148,14 @@ public function testGetAssociatedProducts() } /** + * Verify able to set status filter + * * @param int $status * @param array $filters * @param array $result * @dataProvider addStatusFilterDataProvider */ - public function testAddStatusFilter($status, $filters, $result) + public function testAddStatusFilter($status, $filters, $result): void { $this->product->expects($this->once())->method('getData')->will($this->returnValue($filters)); $this->product->expects($this->once())->method('setData')->with('_cache_instance_status_filters', $result); @@ -136,14 +163,21 @@ public function testAddStatusFilter($status, $filters, $result) } /** + * Data Provider for Status Filter + * * @return array */ - public function addStatusFilterDataProvider() + public function addStatusFilterDataProvider(): array { return [[1, [], [1]], [1, false, [1]]]; } - public function testSetSaleableStatus() + /** + * Verify able to set salable status + * + * @return void + */ + public function testSetSaleableStatus(): void { $key = '_cache_instance_status_filters'; $saleableIds = [300, 800, 500]; @@ -159,7 +193,12 @@ public function testSetSaleableStatus() $this->assertEquals($this->_model, $this->_model->setSaleableStatus($this->product)); } - public function testGetStatusFiltersNoData() + /** + * Verify status filter with no data. + * + * @return void + */ + public function testGetStatusFiltersNoData(): void { $result = [ \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED, @@ -169,7 +208,12 @@ public function testGetStatusFiltersNoData() $this->assertEquals($result, $this->_model->getStatusFilters($this->product)); } - public function testGetStatusFiltersWithData() + /** + * Verify status filter with data + * + * @return void + */ + public function testGetStatusFiltersWithData(): void { $result = [ \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED, @@ -180,7 +224,12 @@ public function testGetStatusFiltersWithData() $this->assertEquals($result, $this->_model->getStatusFilters($this->product)); } - public function testGetAssociatedProductIdsCached() + /** + * Verify AssociatedProducts Ids with cache + * + * @return void + */ + public function testGetAssociatedProductIdsCached(): void { $key = '_cache_instance_associated_product_ids'; $cachedData = [300, 303, 306]; @@ -192,7 +241,12 @@ public function testGetAssociatedProductIdsCached() $this->assertEquals($cachedData, $this->_model->getAssociatedProductIds($this->product)); } - public function testGetAssociatedProductIdsNonCached() + /** + * Verify AssociatedProducts Ids with no cached. + * + * @return void + */ + public function testGetAssociatedProductIdsNonCached(): void { $args = $this->objectHelper->getConstructArguments( \Magento\GroupedProduct\Model\Product\Type\Grouped::class, @@ -236,7 +290,12 @@ public function testGetAssociatedProductIdsNonCached() $this->assertEquals($associatedIds, $model->getAssociatedProductIds($this->product)); } - public function testGetAssociatedProductCollection() + /** + * Verify Associated Product collection + * + * @return void + */ + public function testGetAssociatedProductCollection(): void { $link = $this->createPartialMock( \Magento\Catalog\Model\Product\Link::class, @@ -261,6 +320,8 @@ public function testGetAssociatedProductCollection() } /** + * Verify Proccess buy request + * * @param array $superGroup * @param array $result * @dataProvider processBuyRequestDataProvider @@ -274,9 +335,11 @@ public function testProcessBuyRequest($superGroup, $result) } /** + * dataProvider for buy request + * * @return array */ - public function processBuyRequestDataProvider() + public function processBuyRequestDataProvider(): array { return [ 'positive' => [[1, 2, 3], ['super_group' => [1, 2, 3]]], @@ -285,9 +348,12 @@ public function processBuyRequestDataProvider() } /** + * Get Children Msrp when children product with Msrp + * + * @return void * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - public function testGetChildrenMsrpWhenNoChildrenWithMsrp() + public function testGetChildrenMsrpWhenNoChildrenWithMsrp(): void { $key = '_cache_instance_associated_products'; @@ -298,7 +364,12 @@ public function testGetChildrenMsrpWhenNoChildrenWithMsrp() $this->assertEquals(0, $this->_model->getChildrenMsrp($this->product)); } - public function testPrepareForCartAdvancedEmpty() + /** + * Prepare for card method with advanced empty + * + * @return void + */ + public function testPrepareForCartAdvancedEmpty(): void { $this->product = $this->createMock(\Magento\Catalog\Model\Product::class); $buyRequest = new \Magento\Framework\DataObject(); @@ -381,7 +452,12 @@ public function testPrepareForCartAdvancedEmpty() ); } - public function testPrepareForCartAdvancedNoProductsStrictTrue() + /** + * Prepare for card with no products set strict option true + * + * @return void + */ + public function testPrepareForCartAdvancedNoProductsStrictTrue(): void { $buyRequest = new \Magento\Framework\DataObject(); $buyRequest->setSuperGroup([0 => 0]); @@ -404,7 +480,12 @@ public function testPrepareForCartAdvancedNoProductsStrictTrue() ); } - public function testPrepareForCartAdvancedNoProductsStrictFalse() + /** + * Prepare for card with no products and set strict to false + * + * @return void + */ + public function testPrepareForCartAdvancedNoProductsStrictFalse(): void { $buyRequest = new \Magento\Framework\DataObject(); $buyRequest->setSuperGroup([0 => 0]); @@ -429,7 +510,12 @@ public function testPrepareForCartAdvancedNoProductsStrictFalse() ); } - public function testPrepareForCartAdvancedWithProductsStrictFalseStringResult() + /** + * Verify Prepare for cart product with Product strict flase and string result + * + * @return false + */ + public function testPrepareForCartAdvancedWithProductsStrictFalseStringResult(): void { $associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class); $associatedId = 9384; @@ -463,7 +549,12 @@ public function testPrepareForCartAdvancedWithProductsStrictFalseStringResult() ); } - public function testPrepareForCartAdvancedWithProductsStrictFalseEmptyArrayResult() + /** + * Verify prepare for cart with strict option set to false and empty array + * + * @return void + */ + public function testPrepareForCartAdvancedWithProductsStrictFalseEmptyArrayResult(): void { $expectedMsg = "Cannot process the item."; $associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class); @@ -498,7 +589,12 @@ public function testPrepareForCartAdvancedWithProductsStrictFalseEmptyArrayResul ); } - public function testPrepareForCartAdvancedWithProductsStrictFalse() + /** + * Prepare for cart product with Product strict option st to false. + * + * @return void + */ + public function testPrepareForCartAdvancedWithProductsStrictFalse(): void { $associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class); $associatedId = 9384; @@ -541,61 +637,53 @@ public function testPrepareForCartAdvancedWithProductsStrictFalse() ); } - public function testPrepareForCartAdvancedWithProductsStrictTrue() - { - $associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class); - $associatedId = 9384; - $associatedProduct->expects($this->atLeastOnce())->method('getId')->will($this->returnValue($associatedId)); - - $typeMock = $this->createPartialMock( - \Magento\Catalog\Model\Product\Type\AbstractType::class, - ['_prepareProduct', 'deleteTypeSpecificData'] - ); - $associatedPrepareResult = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->setMockClassName('resultProduct') - ->disableOriginalConstructor() - ->getMock(); - $typeMock->expects($this->once())->method('_prepareProduct')->willReturn([$associatedPrepareResult]); - - $associatedProduct->expects($this->once())->method('getTypeInstance')->willReturn($typeMock); - - $buyRequest = new \Magento\Framework\DataObject(); - $buyRequest->setSuperGroup([$associatedId => 1]); - - $this->serializer->expects($this->any()) - ->method('serialize') - ->willReturn(json_encode($buyRequest->getData())); - - $cached = true; - $this->product - ->expects($this->atLeastOnce()) - ->method('hasData') - ->will($this->returnValue($cached)); - $this->product - ->expects($this->atLeastOnce()) - ->method('getData') - ->will($this->returnValue([$associatedProduct])); - - $associatedPrepareResult->expects($this->at(1))->method('addCustomOption')->with( - 'product_type', - 'grouped', - $this->product - ); + /** + * Test prepareForCartAdvanced() method in full mode + * + * @dataProvider prepareForCartAdvancedWithProductsStrictTrueDataProvider + * @param array $subProducts + * @param array $buyRequest + * @param mixed $expectedResult + */ + public function testPrepareForCartAdvancedWithProductsStrictTrue( + array $subProducts, + array $buyRequest, + $expectedResult + ) { + $associatedProducts = $this->configureProduct($subProducts); + $buyRequestObject = new \Magento\Framework\DataObject(); + $buyRequestObject->setSuperGroup($buyRequest); + $associatedProductsById = []; + foreach ($associatedProducts as $associatedProduct) { + $associatedProductsById[$associatedProduct->getId()] = $associatedProduct; + } + if (is_array($expectedResult)) { + $expectedResultArray = $expectedResult; + $expectedResult = []; + foreach ($expectedResultArray as $id) { + $expectedResult[] = $associatedProductsById[$id]; + } + } $this->assertEquals( - [$associatedPrepareResult], - $this->_model->prepareForCartAdvanced($buyRequest, $this->product) + $expectedResult, + $this->_model->prepareForCartAdvanced($buyRequestObject, $this->product) ); } - public function testPrepareForCartAdvancedZeroQty() + /** + * Verify prepare for card with sold out option + * + * @return void + */ + public function testPrepareForCartAdvancedZeroQtyAndSoldOutOption(): void { $expectedMsg = "Please specify the quantity of product(s)."; - $associatedId = 9384; + $associatedId = 91; $associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class); - $associatedProduct->expects($this->atLeastOnce())->method('getId')->will($this->returnValue($associatedId)); - + $associatedProduct->expects($this->atLeastOnce())->method('getId')->will($this->returnValue(90)); + $associatedProduct->expects($this->once())->method('isSalable')->willReturn(true); $buyRequest = new \Magento\Framework\DataObject(); - $buyRequest->setSuperGroup([$associatedId => 0]); + $buyRequest->setSuperGroup([$associatedId => 90]); $cached = true; $this->product @@ -609,7 +697,12 @@ public function testPrepareForCartAdvancedZeroQty() $this->assertEquals($expectedMsg, $this->_model->prepareForCartAdvanced($buyRequest, $this->product)); } - public function testFlushAssociatedProductsCache() + /** + * Verify flush cache for associated products + * + * @return void + */ + public function testFlushAssociatedProductsCache(): void { $productMock = $this->createPartialMock(\Magento\Catalog\Model\Product::class, ['unsetData']); $productMock->expects($this->once()) @@ -618,4 +711,120 @@ public function testFlushAssociatedProductsCache() ->willReturnSelf(); $this->assertEquals($productMock, $this->_model->flushAssociatedProductsCache($productMock)); } + + /** + * @return array + */ + public function prepareForCartAdvancedWithProductsStrictTrueDataProvider(): array + { + return [ + [ + [ + [ + 'getId' => 1, + 'getQty' => 100, + 'isSalable' => true + ], + [ + 'getId' => 2, + 'getQty' => 200, + 'isSalable' => true + ] + ], + [ + 1 => 2, + 2 => 1, + ], + [1, 2] + ], + [ + [ + [ + 'getId' => 1, + 'getQty' => 100, + 'isSalable' => true + ], + [ + 'getId' => 2, + 'getQty' => 0, + 'isSalable' => false + ] + ], + [ + 1 => 2, + ], + [1] + ], + [ + [ + [ + 'getId' => 1, + 'getQty' => 0, + 'isSalable' => true + ], + [ + 'getId' => 2, + 'getQty' => 0, + 'isSalable' => false + ] + ], + [ + ], + 'Please specify the quantity of product(s).' + ], + [ + [ + [ + 'getId' => 1, + 'getQty' => 0, + 'isSalable' => false + ], + [ + 'getId' => 2, + 'getQty' => 0, + 'isSalable' => false + ] + ], + [ + ], + 'Please specify the quantity of product(s).' + ] + ]; + } + + /** + * Configure sub-products of grouped product + * + * @param array $subProducts + * @return array + */ + private function configureProduct(array $subProducts): array + { + $associatedProducts = []; + foreach ($subProducts as $data) { + $associatedProduct = $this->createMock(\Magento\Catalog\Model\Product::class); + foreach ($data as $method => $value) { + $associatedProduct->method($method)->willReturn($value); + } + $associatedProducts[] = $associatedProduct; + + $typeMock = $this->createPartialMock( + \Magento\Catalog\Model\Product\Type\AbstractType::class, + ['_prepareProduct', 'deleteTypeSpecificData'] + ); + $typeMock->method('_prepareProduct')->willReturn([$associatedProduct]); + $associatedProduct->method('getTypeInstance')->willReturn($typeMock); + } + $this->product + ->expects($this->atLeastOnce()) + ->method('hasData') + ->with('_cache_instance_associated_products') + ->willReturn(true); + $this->product + ->expects($this->atLeastOnce()) + ->method('getData') + ->with('_cache_instance_associated_products') + ->willReturn($associatedProducts); + return $associatedProducts; + } } diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Model/Wishlist/Product/ItemTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Model/Wishlist/Product/ItemTest.php new file mode 100644 index 0000000000000..1edf5e8ce2d95 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Unit/Model/Wishlist/Product/ItemTest.php @@ -0,0 +1,183 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\GroupedProduct\Test\Unit\Model\Wishlist\Product; + +use Magento\GroupedProduct\Model\Product\Type\Grouped as TypeGrouped; + +/** + * Unit test for Wishlist Item Plugin. + */ +class ItemTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\GroupedProduct\Model\Wishlist\Product\Item + */ + protected $model; + + /** + * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject + */ + protected $productMock; + + /** + * @var \Magento\Wishlist\Model\Item|\PHPUnit_Framework_MockObject_MockObject + */ + protected $subjectMock; + + /** + * Init Mock Objects + */ + protected function setUp() + { + $this->subjectMock = $this->createPartialMock( + \Magento\Wishlist\Model\Item::class, + [ + 'getOptionsByCode', + 'getBuyRequest', + 'setOptions', + 'mergeBuyRequest', + 'getProduct' + ] + ); + + $this->productMock = $this->createPartialMock( + \Magento\Catalog\Model\Product::class, + [ + 'getId', + 'getTypeId', + 'getCustomOptions' + ] + ); + + $this->model = new \Magento\GroupedProduct\Model\Wishlist\Product\Item(); + } + + /** + * Test Before Represent Product method + */ + public function testBeforeRepresentProduct() + { + $testSimpleProdId = 34; + $prodInitQty = 2; + $prodQtyInWishlist = 3; + $resWishlistQty = $prodInitQty + $prodQtyInWishlist; + $superGroup = [ + 'super_group' => [ + 33 => "0", + 34 => 3, + 35 => "0" + ] + ]; + + $superGroupObj = new \Magento\Framework\DataObject($superGroup); + + $this->productMock->expects($this->once())->method('getId')->willReturn($testSimpleProdId); + $this->productMock->expects($this->once())->method('getTypeId') + ->willReturn(TypeGrouped::TYPE_CODE); + $this->productMock->expects($this->once())->method('getCustomOptions') + ->willReturn( + $this->getProductAssocOption($prodInitQty, $testSimpleProdId) + ); + + $wishlistItemProductMock = $this->createPartialMock( + \Magento\Catalog\Model\Product::class, + [ + 'getId', + ] + ); + $wishlistItemProductMock->expects($this->once())->method('getId')->willReturn($testSimpleProdId); + + $this->subjectMock->expects($this->once())->method('getProduct') + ->willReturn($wishlistItemProductMock); + $this->subjectMock->expects($this->once())->method('getOptionsByCode') + ->willReturn( + $this->getWishlistAssocOption($prodQtyInWishlist, $resWishlistQty, $testSimpleProdId) + ); + $this->subjectMock->expects($this->once())->method('getBuyRequest')->willReturn($superGroupObj); + + $this->model->beforeRepresentProduct($this->subjectMock, $this->productMock); + } + + /** + * Test Before Compare Options method with same keys + */ + public function testBeforeCompareOptionsSameKeys() + { + $options1 = ['associated_product_34' => 3]; + $options2 = ['associated_product_34' => 2]; + + $res = $this->model->beforeCompareOptions($this->subjectMock, $options1, $options2); + + $this->assertEquals([], $res[0]); + $this->assertEquals([], $res[1]); + } + + /** + * Test Before Compare Options method with diff keys + */ + public function testBeforeCompareOptionsDiffKeys() + { + $options1 = ['associated_product_1' => 3]; + $options2 = ['associated_product_34' => 2]; + + $res = $this->model->beforeCompareOptions($this->subjectMock, $options1, $options2); + + $this->assertEquals($options1, $res[0]); + $this->assertEquals($options2, $res[1]); + } + + /** + * Return mock array with wishlist options + * + * @param int $initVal + * @param int $resVal + * @param int $prodId + * @return array + */ + private function getWishlistAssocOption($initVal, $resVal, $prodId) + { + $items = []; + + $optionMock = $this->createPartialMock( + \Magento\Wishlist\Model\Item\Option::class, + [ + 'getValue', + ] + ); + $optionMock->expects($this->at(0))->method('getValue')->willReturn($initVal); + $optionMock->expects($this->at(1))->method('getValue')->willReturn($resVal); + + $items['associated_product_' . $prodId] = $optionMock; + + return $items; + } + + /** + * Return mock array with product options + * + * @param int $initVal + * @param int $prodId + * @return array + */ + private function getProductAssocOption($initVal, $prodId) + { + $items = []; + + $optionMock = $this->createPartialMock( + \Magento\Catalog\Model\Product\Configuration\Item\Option::class, + [ + 'getValue', + ] + ); + + $optionMock->expects($this->once())->method('getValue')->willReturn($initVal); + + $items['associated_product_' . $prodId] = $optionMock; + + return $items; + } +} diff --git a/app/code/Magento/GroupedProduct/composer.json b/app/code/Magento/GroupedProduct/composer.json index 68063c05ddf7b..3cb41387d2c6d 100644 --- a/app/code/Magento/GroupedProduct/composer.json +++ b/app/code/Magento/GroupedProduct/composer.json @@ -18,7 +18,8 @@ "magento/module-quote": "*", "magento/module-sales": "*", "magento/module-store": "*", - "magento/module-ui": "*" + "magento/module-ui": "*", + "magento/module-wishlist": "*" }, "suggest": { "magento/module-grouped-product-sample-data": "*" diff --git a/app/code/Magento/GroupedProduct/etc/frontend/di.xml b/app/code/Magento/GroupedProduct/etc/frontend/di.xml new file mode 100644 index 0000000000000..21ed87e91cb6c --- /dev/null +++ b/app/code/Magento/GroupedProduct/etc/frontend/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\Wishlist\Model\Item"> + <plugin name="groupedProductWishlistProcessor" type="Magento\GroupedProduct\Model\Wishlist\Product\Item" /> + </type> +</config> diff --git a/app/code/Magento/ImportExport/Model/Export/Adapter/Csv.php b/app/code/Magento/ImportExport/Model/Export/Adapter/Csv.php index 25081b5797c25..09b17371ae4e8 100644 --- a/app/code/Magento/ImportExport/Model/Export/Adapter/Csv.php +++ b/app/code/Magento/ImportExport/Model/Export/Adapter/Csv.php @@ -5,13 +5,16 @@ */ namespace Magento\ImportExport\Model\Export\Adapter; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem\File\Write; + /** * Export adapter csv. * * @api * @since 100.0.2 */ -class Csv extends \Magento\ImportExport\Model\Export\Adapter\AbstractAdapter +class Csv extends AbstractAdapter { /** * Field delimiter. @@ -30,21 +33,20 @@ class Csv extends \Magento\ImportExport\Model\Export\Adapter\AbstractAdapter /** * Source file handler. * - * @var \Magento\Framework\Filesystem\File\Write + * @var Write */ protected $_fileHandler; /** - * {@inheritdoc } + * Object destructor */ - public function __construct(\Magento\Framework\Filesystem $filesystem, $destination = null) + public function __destruct() { - register_shutdown_function([$this, 'destruct']); - parent::__construct($filesystem, $destination); + $this->destruct(); } /** - * Object destructor. + * Clean cached values * * @return void */ @@ -52,6 +54,7 @@ public function destruct() { if (is_object($this->_fileHandler)) { $this->_fileHandler->close(); + $this->_directoryHandle->delete($this->_destination); } } @@ -96,7 +99,7 @@ public function getFileExtension() public function setHeaderCols(array $headerColumns) { if (null !== $this->_headerCols) { - throw new \Magento\Framework\Exception\LocalizedException(__('The header column names are already set.')); + throw new LocalizedException(__('The header column names are already set.')); } if ($headerColumns) { foreach ($headerColumns as $columnName) { diff --git a/app/code/Magento/Indexer/Model/ResourceModel/Indexer/State.php b/app/code/Magento/Indexer/Model/ResourceModel/Indexer/State.php index d1e27be21ae41..d0ab887b2e335 100644 --- a/app/code/Magento/Indexer/Model/ResourceModel/Indexer/State.php +++ b/app/code/Magento/Indexer/Model/ResourceModel/Indexer/State.php @@ -5,6 +5,11 @@ */ namespace Magento\Indexer\Model\ResourceModel\Indexer; +use Magento\Framework\Indexer\StateInterface; + +/** + * Resource model for indexer state + */ class State extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { /** @@ -17,4 +22,22 @@ protected function _construct() $this->_init('indexer_state', 'state_id'); $this->addUniqueField(['field' => ['indexer_id'], 'title' => __('State for the same indexer')]); } + + /** + * @inheritDoc + */ + protected function prepareDataForUpdate($object) + { + $data = parent::prepareDataForUpdate($object); + + if (isset($data['status']) && StateInterface::STATUS_VALID === $data['status']) { + $data['status'] = $this->getConnection()->getCheckSql( + $this->getConnection()->quoteInto('status = ?', StateInterface::STATUS_WORKING), + $this->getConnection()->quote($data['status']), + 'status' + ); + } + + return $data; + } } diff --git a/app/code/Magento/Indexer/Setup/Recurring.php b/app/code/Magento/Indexer/Setup/Recurring.php index 2f1946fab1ab7..30e4ad438e73d 100644 --- a/app/code/Magento/Indexer/Setup/Recurring.php +++ b/app/code/Magento/Indexer/Setup/Recurring.php @@ -13,12 +13,15 @@ use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Indexer\IndexerInterfaceFactory; use Magento\Framework\Indexer\ConfigInterface; use Magento\Indexer\Model\Indexer\State; use Magento\Indexer\Model\Indexer\StateFactory; use Magento\Indexer\Model\ResourceModel\Indexer\State\CollectionFactory; /** + * Indexer recurring setup + * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -51,6 +54,11 @@ class Recurring implements InstallSchemaInterface */ private $stateFactory; + /** + * @var IndexerInterfaceFactory + */ + private $indexerFactory; + /** * Init * @@ -59,23 +67,26 @@ class Recurring implements InstallSchemaInterface * @param ConfigInterface $config * @param EncryptorInterface $encryptor * @param EncoderInterface $encoder + * @param IndexerInterfaceFactory $indexerFactory */ public function __construct( CollectionFactory $statesFactory, StateFactory $stateFactory, ConfigInterface $config, EncryptorInterface $encryptor, - EncoderInterface $encoder + EncoderInterface $encoder, + IndexerInterfaceFactory $indexerFactory ) { $this->statesFactory = $statesFactory; $this->stateFactory = $stateFactory; $this->config = $config; $this->encryptor = $encryptor; $this->encoder = $encoder; + $this->indexerFactory = $indexerFactory; } /** - * {@inheritdoc} + * @inheritdoc */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { @@ -107,6 +118,11 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con $state->setStatus(StateInterface::STATUS_INVALID); $state->save(); } + + $indexer = $this->indexerFactory->create()->load($indexerId); + if ($indexer->isScheduled()) { + $indexer->getView()->unsubscribe()->subscribe(); + } } } } diff --git a/app/code/Magento/Indexer/Setup/RecurringData.php b/app/code/Magento/Indexer/Setup/RecurringData.php deleted file mode 100644 index 1f6ea09ba853f..0000000000000 --- a/app/code/Magento/Indexer/Setup/RecurringData.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Indexer\Setup; - -use Magento\Framework\Indexer\IndexerInterfaceFactory; -use Magento\Framework\Setup\InstallDataInterface; -use Magento\Framework\Setup\ModuleContextInterface; -use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Framework\Indexer\ConfigInterface; - -/** - * Recurring data upgrade for indexer module - */ -class RecurringData implements InstallDataInterface -{ - /** - * @var IndexerInterfaceFactory - */ - private $indexerFactory; - - /** - * @var ConfigInterface - */ - private $configInterface; - - /** - * RecurringData constructor. - * - * @param IndexerInterfaceFactory $indexerFactory - * @param ConfigInterface $configInterface - */ - public function __construct( - IndexerInterfaceFactory $indexerFactory, - ConfigInterface $configInterface - ) { - $this->indexerFactory = $indexerFactory; - $this->configInterface = $configInterface; - } - - /** - * {@inheritdoc} - */ - public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) - { - foreach (array_keys($this->configInterface->getIndexers()) as $indexerId) { - $indexer = $this->indexerFactory->create()->load($indexerId); - if ($indexer->isScheduled()) { - $indexer->getView()->unsubscribe()->subscribe(); - } - } - } -} diff --git a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml index 8e7df86d01329..020cd9654e36b 100644 --- a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml +++ b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml @@ -15,7 +15,7 @@ <element name="massActionSubmit" type="button" selector="#gridIndexer_massaction-form button"/> <element name="indexerSelect" type="select" selector="//select[contains(@class,'action-select-multiselect')]"/> <element name="indexerStatus" type="text" selector="//tr[descendant::td[contains(., '{{status}}')]]//*[contains(@class, 'col-indexer_status')]/span" parameterized="true"/> - <element name="successMessage" type="text" selector="//*[@data-ui-id='messages-message-success']"/> + <element name="successMessage" type="text" selector="//*[@data-ui-id='messages-message-success']" timeout="120"/> <element name="selectMassAction" type="select" selector="#gridIndexer_massaction-mass-select"/> </section> </sections> diff --git a/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml b/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml index 76785c023ed0b..d87c91b293717 100644 --- a/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml +++ b/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml @@ -17,6 +17,9 @@ </field> <field id="button_text" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Button Text</label> + <depends> + <field id="active">1</field> + </depends> </field> </group> </section> diff --git a/app/code/Magento/Integration/Test/Mftf/Test/AdminDeleteIntegrationEntityTest.xml b/app/code/Magento/Integration/Test/Mftf/Test/AdminDeleteIntegrationEntityTest.xml index 6f46bbf99d218..966be94d9e404 100644 --- a/app/code/Magento/Integration/Test/Mftf/Test/AdminDeleteIntegrationEntityTest.xml +++ b/app/code/Magento/Integration/Test/Mftf/Test/AdminDeleteIntegrationEntityTest.xml @@ -14,6 +14,8 @@ <stories value="System Integration"/> <title value="Admin system integration"/> <description value="Admin Deletes Created Integration"/> + <severity value="MAJOR"/> + <testCaseId value="MC-28027"/> <group value="integration"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Integration/etc/adminhtml/system.xml b/app/code/Magento/Integration/etc/adminhtml/system.xml index ddaae76700255..6ef569a1d8a2f 100644 --- a/app/code/Magento/Integration/etc/adminhtml/system.xml +++ b/app/code/Magento/Integration/etc/adminhtml/system.xml @@ -11,58 +11,58 @@ <label>OAuth</label> <tab>service</tab> <resource>Magento_Integration::config_oauth</resource> - <group id="access_token_lifetime" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="access_token_lifetime" translate="label" type="text" sortOrder="100" showInDefault="1"> <label>Access Token Expiration</label> - <field id="customer" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="customer" translate="label comment" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>Customer Token Lifetime (hours)</label> <comment>We will disable this feature if the value is empty.</comment> <validate>required-entry validate-zero-or-greater validate-number</validate> </field> - <field id="admin" translate="label comment" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="admin" translate="label comment" type="text" sortOrder="60" showInDefault="1" canRestore="1"> <label>Admin Token Lifetime (hours)</label> <comment>We will disable this feature if the value is empty.</comment> <validate>required-entry validate-zero-or-greater validate-number</validate> </field> </group> - <group id="cleanup" translate="label" type="text" sortOrder="300" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="cleanup" translate="label" type="text" sortOrder="300" showInDefault="1"> <label>Cleanup Settings</label> - <field id="cleanup_probability" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="cleanup_probability" translate="label comment" type="text" sortOrder="10" showInDefault="1" canRestore="1"> <label>Cleanup Probability</label> <comment>Integer. Launch cleanup in X OAuth requests. 0 (not recommended) - to disable cleanup</comment> <validate>required-entry validate-zero-or-greater validate-digits</validate> </field> - <field id="expiration_period" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="expiration_period" translate="label comment" type="text" sortOrder="20" showInDefault="1" canRestore="1"> <label>Expiration Period</label> <comment>Cleanup entries older than X minutes.</comment> <validate>required-entry validate-zero-or-greater validate-number</validate> </field> </group> - <group id="consumer" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="consumer" translate="label" type="text" sortOrder="400" showInDefault="1"> <label>Consumer Settings</label> - <field id="expiration_period" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="expiration_period" translate="label comment" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>Expiration Period</label> <comment>Consumer key/secret will expire if not used within X seconds after Oauth token exchange starts.</comment> <validate>required-entry validate-zero-or-greater validate-number</validate> </field> - <field id="post_maxredirects" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="post_maxredirects" translate="label comment" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>OAuth consumer credentials HTTP Post maxredirects</label> <comment>Number of maximum redirects for OAuth consumer credentials Post request.</comment> <validate>required-entry validate-zero-or-greater validate-digits</validate> </field> - <field id="post_timeout" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="post_timeout" translate="label comment" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>OAuth consumer credentials HTTP Post timeout</label> <comment>Timeout for OAuth consumer credentials Post request within X seconds.</comment> <validate>required-entry validate-zero-or-greater validate-number</validate> </field> </group> - <group id="authentication_lock" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="authentication_lock" translate="label" type="text" sortOrder="400" showInDefault="1"> <label>Authentication Locks</label> - <field id="max_failures_count" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="max_failures_count" translate="label comment" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>Maximum Login Failures to Lock Out Account</label> <comment>Maximum Number of authentication failures to lock out account.</comment> <validate>required-entry validate-zero-or-greater validate-digits</validate> </field> - <field id="timeout" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="timeout" translate="label comment" type="text" sortOrder="30" showInDefault="1" canRestore="1"> <label>Lockout Time (seconds)</label> <comment>Period of time in seconds after which account will be unlocked.</comment> <validate>required-entry validate-zero-or-greater validate-number</validate> diff --git a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php index 4ed84829c2ad0..d592a004e111a 100644 --- a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php +++ b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php @@ -84,7 +84,11 @@ public function __construct( protected function configure() { $this->setName('catalog:images:resize') - ->setDescription('Creates resized product images') + ->setDescription( + 'Creates resized product images ' . + '(Not relevant when image resizing is offloaded from Magento. ' . + 'See https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options )' + ) ->setDefinition($this->getOptionsList()); } diff --git a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml index 2c7219fe8afaa..9c8c2c5b24398 100644 --- a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml @@ -10,11 +10,11 @@ <section id="system" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="media_storage_configuration" translate="label" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Storage Configuration for Media</label> - <field id="media_storage" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="media_storage" translate="label" type="select" sortOrder="100" showInDefault="1"> <label>Media Storage</label> <source_model>Magento\MediaStorage\Model\Config\Source\Storage\Media\Storage</source_model> </field> - <field id="media_database" translate="label" type="select" sortOrder="200" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="media_database" translate="label" type="select" sortOrder="200" showInDefault="1"> <label>Select Media Database</label> <source_model>Magento\MediaStorage\Model\Config\Source\Storage\Media\Database</source_model> <backend_model>Magento\MediaStorage\Model\Config\Backend\Storage\Media\Database</backend_model> @@ -22,11 +22,11 @@ <field id="media_storage">1</field> </depends> </field> - <field id="synchronize" translate="label comment" type="button" sortOrder="300" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="synchronize" translate="label comment" type="button" sortOrder="300" showInDefault="1"> <frontend_model>Magento\MediaStorage\Block\System\Config\System\Storage\Media\Synchronize</frontend_model> <comment>After selecting a new media storage location, press the Synchronize button to transfer all media to that location and then "Save Config". Media will not be available in the new location until the synchronization process is complete.</comment> </field> - <field id="configuration_update_time" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="configuration_update_time" translate="label" type="text" sortOrder="400" showInDefault="1" canRestore="1"> <label>Environment Update Time</label> <validate>validate-zero-or-greater validate-digits</validate> </field> diff --git a/app/code/Magento/Msrp/etc/adminhtml/system.xml b/app/code/Magento/Msrp/etc/adminhtml/system.xml index c20d753a2e794..8f6c3750c3835 100644 --- a/app/code/Magento/Msrp/etc/adminhtml/system.xml +++ b/app/code/Magento/Msrp/etc/adminhtml/system.xml @@ -8,16 +8,16 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="sales"> - <group id="msrp" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="msrp" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1"> <label>Minimum Advertised Price</label> - <field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable MAP</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment> <![CDATA[<strong style="color:red">Warning!</strong> Enabling MAP by default will hide all product prices on Storefront.]]> </comment> </field> - <field id="display_price_type" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="display_price_type" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Display Actual Price</label> <source_model>Magento\Msrp\Model\Product\Attribute\Source\Type</source_model> </field> diff --git a/app/code/Magento/Multishipping/etc/adminhtml/system.xml b/app/code/Magento/Multishipping/etc/adminhtml/system.xml index f30782b357c73..909db7b883904 100644 --- a/app/code/Magento/Multishipping/etc/adminhtml/system.xml +++ b/app/code/Magento/Multishipping/etc/adminhtml/system.xml @@ -7,19 +7,22 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="multishipping" translate="label" type="text" sortOrder="311" showInDefault="1" showInWebsite="1" showInStore="0"> + <section id="multishipping" translate="label" type="text" sortOrder="311" showInDefault="1" showInWebsite="1"> <label>Multishipping Settings</label> <tab>sales</tab> <resource>Magento_Multishipping::config_multishipping</resource> - <group id="options" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="options" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1"> <label>Options</label> - <field id="checkout_multiple" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="checkout_multiple" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allow Shipping to Multiple Addresses</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="checkout_multiple_maximum_qty" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="checkout_multiple_maximum_qty" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Maximum Qty Allowed for Shipping to Multiple Addresses</label> <validate>validate-number</validate> + <depends> + <field id="checkout_multiple">1</field> + </depends> </field> </group> </section> diff --git a/app/code/Magento/MysqlMq/etc/adminhtml/system.xml b/app/code/Magento/MysqlMq/etc/adminhtml/system.xml index 045a176a48e87..835283ee263c2 100644 --- a/app/code/Magento/MysqlMq/etc/adminhtml/system.xml +++ b/app/code/Magento/MysqlMq/etc/adminhtml/system.xml @@ -8,22 +8,22 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="system"> - <group id="mysqlmq" translate="label comment" type="text" sortOrder="15" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="mysqlmq" translate="label comment" type="text" sortOrder="15" showInDefault="1"> <label>MySQL Message Queue Cleanup</label> <comment>All the times are in minutes. Use "0" if you want to skip automatic clearance.</comment> - <field id="retry_inprogress_after" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="retry_inprogress_after" translate="label" type="text" sortOrder="60" showInDefault="1"> <label>Retry Messages In Progress After</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="successful_messages_lifetime" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="successful_messages_lifetime" translate="label" type="text" sortOrder="50" showInDefault="1"> <label>Successful Messages Lifetime</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="failed_messages_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="failed_messages_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1"> <label>Failed Messages Lifetime</label> <validate>validate-zero-or-greater validate-digits</validate> </field> - <field id="new_messages_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="new_messages_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1"> <label>New Messages Lifetime</label> <validate>validate-zero-or-greater validate-digits</validate> </field> diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php index b53ac39f0e4e2..eb55d365cafa2 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php @@ -268,7 +268,7 @@ class_exists(\Magento\Newsletter\Model\Template\Filter::class, true); ) ->disableOriginalConstructor() ->getMock(); - $filterTemplate->expects($this->once()) + $filterTemplate->expects($this->never()) ->method('setUseSessionInUrl') ->with(false) ->will($this->returnSelf()); diff --git a/app/code/Magento/Newsletter/etc/adminhtml/system.xml b/app/code/Magento/Newsletter/etc/adminhtml/system.xml index 16af7b2158dde..01dc4777f5d31 100644 --- a/app/code/Magento/Newsletter/etc/adminhtml/system.xml +++ b/app/code/Magento/Newsletter/etc/adminhtml/system.xml @@ -20,6 +20,9 @@ </group> <group id="subscription" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Subscription Options</label> + <depends> + <field id="*/general/active">1</field> + </depends> <field id="allow_guest_subscribe" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Allow Guest Subscription</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> diff --git a/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml b/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml index aedab33239f9f..23793f970a7d9 100644 --- a/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml +++ b/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml @@ -10,26 +10,26 @@ <section id="payment" type="text" sortOrder="400" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="checkmo" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Check / Money Order</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="order_status" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="order_status" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>New Order Status</label> <source_model>Magento\Sales\Model\Config\Source\Order\Status\NewStatus</source_model> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <frontend_class>validate-number</frontend_class> </field> <field id="title" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Payment from Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1"> <label>Payment from Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> @@ -40,11 +40,11 @@ <field id="mailing_address" translate="label" type="textarea" sortOrder="62" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Check to</label> </field> - <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1"> <label>Minimum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1"> <label>Maximum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> @@ -52,35 +52,35 @@ </group> <group id="purchaseorder" translate="label" type="text" sortOrder="32" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Purchase Order</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="order_status" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="order_status" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>New Order Status</label> <source_model>Magento\Sales\Model\Config\Source\Order\Status\NewStatus</source_model> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <frontend_class>validate-number</frontend_class> </field> <field id="title" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Payment from Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1"> <label>Payment from Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1"> <label>Minimum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1"> <label>Maximum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> @@ -88,22 +88,22 @@ </group> <group id="banktransfer" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Bank Transfer Payment</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="title" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="order_status" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="order_status" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>New Order Status</label> <source_model>Magento\Sales\Model\Config\Source\Order\Status\NewStatus</source_model> </field> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Payment from Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1"> <label>Payment from Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> @@ -111,37 +111,37 @@ <field id="instructions" translate="label" type="textarea" sortOrder="62" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Instructions</label> </field> - <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1"> <label>Minimum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1"> <label>Maximum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number</validate> </field> </group> <group id="cashondelivery" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Cash On Delivery Payment</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="title" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="order_status" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="order_status" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>New Order Status</label> <source_model>Magento\Sales\Model\Config\Source\Order\Status\NewStatus</source_model> </field> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Payment from Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1"> <label>Payment from Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> @@ -149,15 +149,15 @@ <field id="instructions" translate="label" type="textarea" sortOrder="62" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Instructions</label> </field> - <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1"> <label>Minimum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1"> <label>Maximum Order Total</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number</validate> </field> diff --git a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml index cb75bddf4d7bd..768aab0499046 100644 --- a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml +++ b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml @@ -10,47 +10,47 @@ <section id="carriers" type="text" sortOrder="320" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="flatrate" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Flat Rate</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="name" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Method Name</label> </field> - <field id="price" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="price" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Price</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="handling_type" translate="label" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_type" translate="label" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Calculate Handling Fee</label> <source_model>Magento\Shipping\Model\Source\HandlingType</source_model> </field> - <field id="handling_fee" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="0" > + <field id="handling_fee" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" > <label>Handling Fee</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="type" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="type" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Type</label> <source_model>Magento\OfflineShipping\Model\Config\Source\Flatrate</source_model> </field> - <field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ship to Applicable Countries</label> <frontend_class>shipping-applicable-country</frontend_class> <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1"> <label>Ship to Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1"> <label>Show Method if Not Applicable</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <frontend_class>shipping-skip-hide</frontend_class> @@ -61,54 +61,54 @@ </group> <group id="tablerate" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Table Rates</label> - <field id="handling_type" translate="label" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_type" translate="label" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Calculate Handling Fee</label> <source_model>Magento\Shipping\Model\Source\HandlingType</source_model> </field> - <field id="handling_fee" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="handling_fee" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1"> <label>Handling Fee</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="condition_name" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="condition_name" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Condition</label> <source_model>Magento\OfflineShipping\Model\Config\Source\Tablerate</source_model> </field> - <field id="include_virtual_price" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="include_virtual_price" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Include Virtual Products in Price Calculation</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="export" translate="label" type="Magento\OfflineShipping\Block\Adminhtml\Form\Field\Export" sortOrder="5" showInDefault="0" showInWebsite="1" showInStore="0"> + <field id="export" translate="label" type="Magento\OfflineShipping\Block\Adminhtml\Form\Field\Export" sortOrder="5" showInWebsite="1"> <label>Export</label> </field> - <field id="import" translate="label" type="Magento\OfflineShipping\Block\Adminhtml\Form\Field\Import" sortOrder="6" showInDefault="0" showInWebsite="1" showInStore="0"> + <field id="import" translate="label" type="Magento\OfflineShipping\Block\Adminhtml\Form\Field\Import" sortOrder="6" showInWebsite="1"> <label>Import</label> <backend_model>Magento\OfflineShipping\Model\Config\Backend\Tablerate</backend_model> </field> <field id="name" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Method Name</label> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ship to Applicable Countries</label> <frontend_class>shipping-applicable-country</frontend_class> <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1"> <label>Ship to Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1"> <label>Show Method if Not Applicable</label> <frontend_class>shipping-skip-hide</frontend_class> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -119,39 +119,39 @@ </group> <group id="freeshipping" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Free Shipping</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1"> <label>Minimum Order Amount</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="tax_including" translate="label" sortOrder="5" type="select" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="tax_including" translate="label" sortOrder="5" type="select" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Include Tax to Amount</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="name" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Method Name</label> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ship to Applicable Countries</label> <frontend_class>shipping-applicable-country</frontend_class> <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1"> <label>Ship to Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1"> <label>Show Method if Not Applicable</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <frontend_class>shipping-skip-hide</frontend_class> diff --git a/app/code/Magento/PageCache/etc/adminhtml/system.xml b/app/code/Magento/PageCache/etc/adminhtml/system.xml index 234e3e48a95d8..4ffc20958748d 100644 --- a/app/code/Magento/PageCache/etc/adminhtml/system.xml +++ b/app/code/Magento/PageCache/etc/adminhtml/system.xml @@ -8,15 +8,15 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="system"> - <group id="full_page_cache" translate="label" showInDefault="1" showInWebsite="0" showInStore="0" sortOrder="600"> + <group id="full_page_cache" translate="label" showInDefault="1" sortOrder="600"> <label>Full Page Cache</label> - <field id="caching_application" translate="label" type="select" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="caching_application" translate="label" type="select" sortOrder="0" showInDefault="1" canRestore="1"> <label>Caching Application</label> <source_model>Magento\PageCache\Model\System\Config\Source\Application</source_model> </field> - <group id="varnish" translate="label" showInDefault="1" showInWebsite="0" showInStore="0" sortOrder="605"> + <group id="varnish" translate="label" showInDefault="1" sortOrder="605"> <label>Varnish Configuration</label> - <field id="access_list" type="text" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="access_list" type="text" translate="label comment" sortOrder="15" showInDefault="1"> <label>Access list</label> <comment>IPs access list separated with ',' that can purge Varnish configuration for config file generation. If field is empty default value localhost will be saved.</comment> @@ -25,7 +25,7 @@ <field id="caching_application">1</field> </depends> </field> - <field id="backend_host" type="text" translate="label comment" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="backend_host" type="text" translate="label comment" sortOrder="20" showInDefault="1"> <label>Backend host</label> <comment>Specify backend host for config file generation. If field is empty default value localhost will be saved.</comment> <backend_model>Magento\PageCache\Model\System\Config\Backend\Varnish</backend_model> @@ -33,7 +33,7 @@ <field id="caching_application">1</field> </depends> </field> - <field id="backend_port" type="text" translate="label comment" sortOrder="25" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="backend_port" type="text" translate="label comment" sortOrder="25" showInDefault="1"> <label>Backend port</label> <comment>Specify backend port for config file generation. If field is empty default value 8080 will be saved.</comment> <backend_model>Magento\PageCache\Model\System\Config\Backend\Varnish</backend_model> @@ -41,7 +41,7 @@ <field id="caching_application">1</field> </depends> </field> - <field id="grace_period" type="text" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="grace_period" type="text" translate="label comment" sortOrder="30" showInDefault="1"> <label>Grace period</label> <comment>Specify grace period in seconds for config file generation. If field is empty default value 300 will be saved. This grace period will be used to serve cached content when the server is healthy. If the server is not healthy, cached content will be served for 3 days before failing.</comment> <backend_model>Magento\PageCache\Model\System\Config\Backend\Varnish</backend_model> @@ -49,20 +49,20 @@ <field id="caching_application">1</field> </depends> </field> - <field id="export_button_version4" translate="label" type="button" sortOrder="35" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="export_button_version4" translate="label" type="button" sortOrder="35" showInDefault="1"> <label>Export Configuration</label> <frontend_model>Magento\PageCache\Block\System\Config\Form\Field\Export\Varnish4</frontend_model> <depends> <field id="caching_application">1</field> </depends> </field> - <field id="export_button_version5" type="button" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="export_button_version5" type="button" sortOrder="40" showInDefault="1"> <frontend_model>Magento\PageCache\Block\System\Config\Form\Field\Export\Varnish5</frontend_model> <depends> <field id="caching_application">1</field> </depends> </field> - <field id="export_button_version6" type="button" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="export_button_version6" type="button" sortOrder="40" showInDefault="1"> <frontend_model>Magento\PageCache\Block\System\Config\Form\Field\Export\Varnish6</frontend_model> <depends> <field id="caching_application">1</field> @@ -72,7 +72,7 @@ <field id="caching_application">2</field> </depends> </group> - <field id="ttl" type="text" translate="label comment" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="ttl" type="text" translate="label comment" sortOrder="5" showInDefault="1" canRestore="1"> <label>TTL for public content</label> <validate>validate-zero-or-greater validate-digits</validate> <comment>Public content cache lifetime in seconds. If field is empty default value 86400 will be saved. </comment> diff --git a/app/code/Magento/Payment/etc/adminhtml/system.xml b/app/code/Magento/Payment/etc/adminhtml/system.xml index d168dc13a397b..1e8b617d31326 100644 --- a/app/code/Magento/Payment/etc/adminhtml/system.xml +++ b/app/code/Magento/Payment/etc/adminhtml/system.xml @@ -13,33 +13,33 @@ <resource>Magento_Payment::payment</resource> <group id="free" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Zero Subtotal Checkout</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="order_status" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="order_status" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>New Order Status</label> <source_model>Magento\Sales\Model\Config\Source\Order\Status\Newprocessing</source_model> </field> - <field id="payment_action" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="payment_action" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Automatically Invoice All Items</label> <source_model>Magento\Payment\Model\Source\Invoice</source_model> <depends> <field id="order_status" separator=",">processing</field> </depends> </field> - <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Sort Order</label> <frontend_class>validate-number</frontend_class> </field> <field id="title" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Payment from Applicable Countries</label> <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1"> <label>Payment from Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> diff --git a/app/code/Magento/Paypal/Model/Ipn.php b/app/code/Magento/Paypal/Model/Ipn.php index 9107762c54b69..0d7d5518f9c7d 100644 --- a/app/code/Magento/Paypal/Model/Ipn.php +++ b/app/code/Magento/Paypal/Model/Ipn.php @@ -106,6 +106,7 @@ protected function _getConfig() $parameters = ['params' => [$methodCode, $order->getStoreId()]]; $this->_config = $this->_configFactory->create($parameters); if (!$this->_config->isMethodActive($methodCode) || !$this->_config->isMethodAvailable()) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception(sprintf('The "%s" method isn\'t available.', $methodCode)); } /** @link https://cms.paypal.com/cgi-bin/marketingweb?cmd=_render-content&content_ID= @@ -117,6 +118,7 @@ protected function _getConfig() } $receiver = $this->getRequestData('business') ?: $this->getRequestData('receiver_email'); if (strtolower($merchantEmail) != strtolower($receiver)) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception( sprintf( 'The requested "%s" and the configured "%s" merchant emails don\'t match.', @@ -140,6 +142,7 @@ protected function _getOrder() $incrementId = $this->getRequestData('invoice'); $this->_order = $this->_orderFactory->create()->loadByIncrementId($incrementId); if (!$this->_order->getId()) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception(sprintf('The "%s" order ID is incorrect. Verify the ID and try again.', $incrementId)); } return $this->_order; @@ -245,8 +248,11 @@ protected function _registerTransaction() break; // customer attempted to pay via bank account, but failed case Info::PAYMENTSTATUS_FAILED: - // cancel order - $this->_registerPaymentFailure(); + if ($this->_order->getState() === \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW) { + $this->_registerPaymentDenial(); + } else { + $this->_registerPaymentFailure(); + } break; // payment was obtained, but money were not captured yet case Info::PAYMENTSTATUS_PENDING: @@ -270,6 +276,7 @@ protected function _registerTransaction() $this->_registerPaymentVoid(); break; default: + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception("The '{$paymentStatus}' payment status couldn't be handled."); } } @@ -322,11 +329,12 @@ protected function _registerPaymentDenial() { try { $this->_importPaymentInformation(); - $this->_order->getPayment() - ->setTransactionId($this->getRequestData('txn_id')) - ->setNotificationResult(true) - ->setIsTransactionClosed(true) - ->deny(false); + $payment = $this->_order->getPayment(); + $payment->setTransactionId($this->getRequestData('txn_id')); + $payment->setPreparedMessage($this->_createIpnComment('')); + $payment->setNotificationResult(true); + $payment->setIsTransactionClosed(true); + $payment->deny(false); $this->_order->save(); } catch (LocalizedException $e) { if ($e->getMessage() != __('We cannot cancel this order.')) { @@ -360,6 +368,7 @@ public function _registerPaymentPending() return; } if ('order' === $reason) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception('The "order" authorizations aren\'t implemented.'); } // case when was placed using PayPal standard @@ -501,6 +510,7 @@ protected function _registerPaymentVoid() /** * Map payment information from IPN to payment object + * * Returns true if there were changes in information * * @return bool @@ -537,8 +547,10 @@ protected function _importPaymentInformation() // collect fraud filters $fraudFilters = []; - for ($i = 1; $value = $this->getRequestData("fraud_management_pending_filters_{$i}"); $i++) { + $index = 1; + while ($value = $this->getRequestData("fraud_management_pending_filters_{$index}")) { $fraudFilters[] = $value; + $index++; } if ($fraudFilters) { $from[Info::FRAUD_FILTERS] = $fraudFilters; @@ -568,6 +580,7 @@ protected function _importPaymentInformation() /** * Generate an "IPN" comment with additional explanation. + * * Returns the generated comment or order status history object * * @param string $comment diff --git a/app/code/Magento/Paypal/etc/adminhtml/system.xml b/app/code/Magento/Paypal/etc/adminhtml/system.xml index 88bb61f2cdc99..80e9523c752e4 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system.xml @@ -8,10 +8,10 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="payment"> - <group id="account" translate="label" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="account" translate="label" sortOrder="1" showInDefault="1" showInWebsite="1"> <label>Merchant Location</label> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="merchant_country" type="select" translate="label comment" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="merchant_country" type="select" translate="label comment" sortOrder="5" showInDefault="1" showInWebsite="1"> <label>Merchant Country</label> <comment>If not specified, Default Country from General Config will be used.</comment> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Country</frontend_model> diff --git a/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml b/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml index 12afd8ceda60e..aa5eb371ba1a8 100644 --- a/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml +++ b/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml @@ -8,8 +8,8 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="customer"> - <group id="captcha" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> - <field id="forms" translate="label comment" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <group id="captcha" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1"> + <field id="forms" translate="label comment" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="1" canRestore="1"> <comment>CAPTCHA for "Create user", "Forgot password", "Payflow Pro" forms is always enabled if chosen.</comment> </field> </group> diff --git a/app/code/Magento/Persistent/etc/adminhtml/system.xml b/app/code/Magento/Persistent/etc/adminhtml/system.xml index 2db7cb0df0bd1..18882c0072877 100644 --- a/app/code/Magento/Persistent/etc/adminhtml/system.xml +++ b/app/code/Magento/Persistent/etc/adminhtml/system.xml @@ -7,46 +7,46 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="persistent" translate="label" type="text" sortOrder="500" showInDefault="1" showInWebsite="1" showInStore="0"> + <section id="persistent" translate="label" type="text" sortOrder="500" showInDefault="1" showInWebsite="1"> <class>separator-top</class> <label>Persistent Shopping Cart</label> <tab>customer</tab> <resource>Magento_Persistent::persistent</resource> - <group id="options" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="options" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1"> <label>General Options</label> - <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable Persistence</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="lifetime" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="lifetime" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Persistence Lifetime (seconds)</label> <validate>validate-digits validate-digits-range digits-range-0-3153600000</validate> <depends> <field id="enabled">1</field> </depends> </field> - <field id="remember_enabled" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="remember_enabled" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable "Remember Me"</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> <field id="enabled">1</field> </depends> </field> - <field id="remember_default" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="remember_default" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" canRestore="1"> <label>"Remember Me" Default Value</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> <field id="enabled">1</field> </depends> </field> - <field id="logout_clear" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="logout_clear" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Clear Persistence on Sign Out</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> <field id="enabled">1</field> </depends> </field> - <field id="shopping_cart" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="shopping_cart" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Persist Shopping Cart</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <depends> diff --git a/app/code/Magento/ProductAlert/Block/Email/AbstractEmail.php b/app/code/Magento/ProductAlert/Block/Email/AbstractEmail.php index ca989c9f5235f..57081804fca80 100644 --- a/app/code/Magento/ProductAlert/Block/Email/AbstractEmail.php +++ b/app/code/Magento/ProductAlert/Block/Email/AbstractEmail.php @@ -6,8 +6,6 @@ namespace Magento\ProductAlert\Block\Email; use Magento\Framework\Pricing\PriceCurrencyInterface; -use Magento\Framework\App\ObjectManager; -use Magento\ProductAlert\Block\Product\ImageProvider; /** * Product Alert Abstract Email Block @@ -43,32 +41,23 @@ abstract class AbstractEmail extends \Magento\Framework\View\Element\Template */ protected $imageBuilder; - /** - * @var ImageProvider - */ - private $imageProvider; - /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Framework\Filter\Input\MaliciousCode $maliciousCode * @param PriceCurrencyInterface $priceCurrency * @param \Magento\Catalog\Block\Product\ImageBuilder $imageBuilder * @param array $data - * @param ImageProvider $imageProvider */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Framework\Filter\Input\MaliciousCode $maliciousCode, PriceCurrencyInterface $priceCurrency, \Magento\Catalog\Block\Product\ImageBuilder $imageBuilder, - array $data = [], - ImageProvider $imageProvider = null + array $data = [] ) { $this->imageBuilder = $imageBuilder; $this->priceCurrency = $priceCurrency; $this->_maliciousCode = $maliciousCode; - $this->imageProvider = $imageProvider ?: ObjectManager::getInstance()->get(ImageProvider::class); - parent::__construct($context, $data); } @@ -173,6 +162,8 @@ protected function _getUrlParams() } /** + * Get Price Render + * * @return \Magento\Framework\Pricing\Render */ protected function getPriceRender() @@ -227,6 +218,6 @@ public function getProductPriceHtml( */ public function getImage($product, $imageId, $attributes = []) { - return $this->imageProvider->getImage($product, $imageId, $attributes); + return $this->imageBuilder->create($product, $imageId, $attributes); } } diff --git a/app/code/Magento/ProductAlert/Block/Product/ImageProvider.php b/app/code/Magento/ProductAlert/Block/Product/ImageProvider.php deleted file mode 100644 index 61d8d1987c2d7..0000000000000 --- a/app/code/Magento/ProductAlert/Block/Product/ImageProvider.php +++ /dev/null @@ -1,72 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ProductAlert\Block\Product; - -use Magento\Store\Model\App\Emulation; -use Magento\Catalog\Block\Product\ImageBuilder; -use Magento\Catalog\Model\Product; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\App\Area; -use Magento\Catalog\Block\Product\Image; - -/** - * Provides product image to be used in the Product Alert Email. - */ -class ImageProvider -{ - /** - * @var ImageBuilder - */ - private $imageBuilder; - - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * @var Emulation - */ - private $appEmulation; - - /** - * @param ImageBuilder $imageBuilder - * @param StoreManagerInterface $storeManager - * @param Emulation $appEmulation - */ - public function __construct( - ImageBuilder $imageBuilder, - StoreManagerInterface $storeManager, - Emulation $appEmulation - ) { - $this->imageBuilder = $imageBuilder; - $this->storeManager = $storeManager; - $this->appEmulation = $appEmulation; - } - - /** - * @param Product $product - * @param string $imageId - * @param array $attributes - * @return Image - * @throws \Exception - */ - public function getImage(Product $product, $imageId, $attributes = []) - { - $storeId = $this->storeManager->getStore()->getId(); - $this->appEmulation->startEnvironmentEmulation($storeId, Area::AREA_FRONTEND, true); - - try { - $image = $this->imageBuilder->create($product, $imageId, $attributes); - } catch (\Exception $exception) { - $this->appEmulation->stopEnvironmentEmulation(); - throw $exception; - } - - $this->appEmulation->stopEnvironmentEmulation(); - return $image; - } -} diff --git a/app/code/Magento/ProductAlert/Test/Unit/Block/Email/StockTest.php b/app/code/Magento/ProductAlert/Test/Unit/Block/Email/StockTest.php index c5872701ef9c8..48907197ab7b3 100644 --- a/app/code/Magento/ProductAlert/Test/Unit/Block/Email/StockTest.php +++ b/app/code/Magento/ProductAlert/Test/Unit/Block/Email/StockTest.php @@ -25,11 +25,6 @@ class StockTest extends \PHPUnit\Framework\TestCase */ protected $imageBuilder; - /** - * @var \Magento\ProductAlert\Block\Product\ImageProvider|\PHPUnit_Framework_MockObject_MockObject - */ - private $imageProviderMock; - protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -39,16 +34,11 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->imageProviderMock = $this->getMockBuilder(\Magento\ProductAlert\Block\Product\ImageProvider::class) - ->disableOriginalConstructor() - ->getMock(); - $this->_block = $objectManager->getObject( \Magento\ProductAlert\Block\Email\Stock::class, [ 'maliciousCode' => $this->_filter, - 'imageBuilder' => $this->imageBuilder, - 'imageProvider' => $this->imageProviderMock + 'imageBuilder' => $this->imageBuilder ] ); } @@ -88,8 +78,7 @@ public function testGetImage() ->disableOriginalConstructor() ->getMock(); - $this->imageProviderMock->expects($this->atLeastOnce())->method('getImage')->willReturn($productImageMock); - + $this->imageBuilder->expects($this->atLeastOnce())->method('create')->willReturn($productImageMock); $this->assertInstanceOf( \Magento\Catalog\Block\Product\Image::class, $this->_block->getImage($productMock, $imageId, $attributes) diff --git a/app/code/Magento/ProductAlert/Test/Unit/Block/Product/ImageProviderTest.php b/app/code/Magento/ProductAlert/Test/Unit/Block/Product/ImageProviderTest.php deleted file mode 100644 index 172e5f486f30b..0000000000000 --- a/app/code/Magento/ProductAlert/Test/Unit/Block/Product/ImageProviderTest.php +++ /dev/null @@ -1,99 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ProductAlert\Test\Unit\Block\Product; - -class ImageProviderTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Catalog\Block\Product\ImageBuilder|\PHPUnit_Framework_MockObject_MockObject - */ - private $imageBuilderMock; - - /** - * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $storeManagerMock; - - /** - * @var \Magento\Store\Model\App\Emulation|\PHPUnit_Framework_MockObject_MockObject - */ - private $emulationMock; - - /** - * @var \Magento\ProductAlert\Block\Product\ImageProvider - */ - private $model; - - protected function setUp() - { - $this->imageBuilderMock = $this->getMockBuilder(\Magento\Catalog\Block\Product\ImageBuilder::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->emulationMock = $this->getMockBuilder(\Magento\Store\Model\App\Emulation::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->model = new \Magento\ProductAlert\Block\Product\ImageProvider( - $this->imageBuilderMock, - $this->storeManagerMock, - $this->emulationMock - ); - } - - /** - * Test that image is created successfully with app emulation enabled. - */ - public function testGetImage() - { - $imageId = 'test_image_id'; - $attributes = []; - - $productMock = $this->createMock(\Magento\Catalog\Model\Product::class); - $imageMock = $this->createMock(\Magento\Catalog\Block\Product\Image::class); - $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); - - $this->storeManagerMock->expects($this->atLeastOnce())->method('getStore')->willReturn($storeMock); - $this->emulationMock->expects($this->once())->method('startEnvironmentEmulation'); - $this->imageBuilderMock->expects($this->once()) - ->method('create') - ->with($productMock, $imageId, $attributes) - ->willReturn($imageMock); - $this->emulationMock->expects($this->once())->method('stopEnvironmentEmulation'); - - $this->assertEquals($imageMock, $this->model->getImage($productMock, $imageId, $attributes)); - } - - /** - * Test that app emulation stops when exception occurs. - * - * @expectedException \Exception - * @expectedExceptionMessage Image Builder Exception - */ - public function testGetImageThrowsAnException() - { - $imageId = 1; - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->emulationMock->expects($this->once())->method('startEnvironmentEmulation'); - $this->storeManagerMock->expects($this->atLeastOnce())->method('getStore')->willReturn($storeMock); - - $this->imageBuilderMock->expects($this->once()) - ->method('create') - ->with($productMock, $imageId) - ->willThrowException(new \Exception("Image Builder Exception")); - - $this->emulationMock->expects($this->once())->method('stopEnvironmentEmulation'); - $this->model->getImage($productMock, $imageId); - } -} diff --git a/app/code/Magento/ProductAlert/Test/Unit/Model/ObserverTest.php b/app/code/Magento/ProductAlert/Test/Unit/Model/ObserverTest.php index e3a2056a89ec0..9a5381c094243 100644 --- a/app/code/Magento/ProductAlert/Test/Unit/Model/ObserverTest.php +++ b/app/code/Magento/ProductAlert/Test/Unit/Model/ObserverTest.php @@ -11,6 +11,9 @@ /** * Class ObserverTest + * + * Is used to test Product Alert Observer + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) */ @@ -168,7 +171,7 @@ protected function setUp() ); $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) ->disableOriginalConstructor() - ->setMethods(['getDefaultStore', 'getId']) + ->setMethods(['getDefaultStore', 'getId', 'setWebsiteId']) ->getMock(); $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) ->getMock(); @@ -285,12 +288,13 @@ public function testProcessPriceEmailThrowsException() $this->storeMock->expects($this->any())->method('getDefaultStore')->willReturnSelf(); $this->websiteMock->expects($this->once())->method('getDefaultStore')->willReturn($this->storeMock); $this->storeMock->expects($this->any())->method('getId')->willReturn(2); + $this->storeMock->expects($this->any())->method('setWebsiteId')->willReturnSelf(); $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(true); $this->priceColFactoryMock->expects($this->once())->method('create')->willReturnSelf(); $this->priceColFactoryMock->expects($this->once())->method('addWebsiteFilter')->willReturnSelf(); - + $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($this->storeMock); $items = [ new \Magento\Framework\DataObject([ 'customer_id' => $id diff --git a/app/code/Magento/ProductAlert/etc/adminhtml/system.xml b/app/code/Magento/ProductAlert/etc/adminhtml/system.xml index edf4940ff504a..2af3b905d2faf 100644 --- a/app/code/Magento/ProductAlert/etc/adminhtml/system.xml +++ b/app/code/Magento/ProductAlert/etc/adminhtml/system.xml @@ -14,7 +14,7 @@ <label>Allow Alert When Product Price Changes</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="allow_stock" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allow_stock" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allow Alert When Product Comes Back in Stock</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -33,25 +33,25 @@ <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> </field> </group> - <group id="productalert_cron" translate="label" type="text" sortOrder="260" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="productalert_cron" translate="label" type="text" sortOrder="260" showInDefault="1"> <label>Product Alerts Run Settings</label> - <field id="frequency" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="frequency" translate="label" type="select" sortOrder="1" showInDefault="1"> <label>Frequency</label> <source_model>Magento\Cron\Model\Config\Source\Frequency</source_model> <backend_model>Magento\Cron\Model\Config\Backend\Product\Alert</backend_model> </field> - <field id="time" translate="label" type="time" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="time" translate="label" type="time" sortOrder="2" showInDefault="1"> <label>Start Time</label> </field> - <field id="error_email" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="error_email" translate="label" type="text" sortOrder="3" showInDefault="1"> <label>Error Email Recipient</label> <validate>validate-email</validate> </field> - <field id="error_email_identity" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="error_email_identity" translate="label" type="select" sortOrder="4" showInDefault="1" canRestore="1"> <label>Error Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> </field> - <field id="error_email_template" translate="label comment" type="select" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="error_email_template" translate="label comment" type="select" sortOrder="5" showInDefault="1" canRestore="1"> <label>Error Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> diff --git a/app/code/Magento/ProductVideo/etc/adminhtml/system.xml b/app/code/Magento/ProductVideo/etc/adminhtml/system.xml index aa8749f69b409..aa70f3a5096c5 100644 --- a/app/code/Magento/ProductVideo/etc/adminhtml/system.xml +++ b/app/code/Magento/ProductVideo/etc/adminhtml/system.xml @@ -8,9 +8,9 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="catalog"> - <group id="product_video" translate="label" type="text" sortOrder="350" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="product_video" translate="label" type="text" sortOrder="350" showInDefault="1" showInWebsite="1"> <label>Product Video</label> - <field id="youtube_api_key" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="youtube_api_key" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1"> <label>YouTube API Key</label> </field> <field id="play_if_base" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php index db8ac0d1fe179..39148f990b714 100644 --- a/app/code/Magento/Quote/Model/Quote/Address.php +++ b/app/code/Magento/Quote/Model/Quote/Address.php @@ -6,10 +6,42 @@ namespace Magento\Quote\Model\Quote; use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Customer\Api\Data\AddressInterface; use Magento\Customer\Api\Data\AddressInterfaceFactory; use Magento\Customer\Api\Data\RegionInterfaceFactory; +use Magento\Customer\Model\Address\AbstractAddress; +use Magento\Customer\Model\Address\Mapper; +use Magento\Directory\Helper\Data; +use Magento\Directory\Model\CountryFactory; +use Magento\Directory\Model\RegionFactory; +use Magento\Framework\Api\AttributeValueFactory; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\ExtensionAttributesFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\DataObject\Copy; +use Magento\Framework\Model\Context; +use Magento\Framework\Model\ResourceModel\AbstractResource; +use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; +use Magento\Framework\Registry; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Quote\Api\Data\AddressExtensionInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address\Rate; +use Magento\Quote\Model\Quote\Address\RateCollectorInterfaceFactory; +use Magento\Quote\Model\Quote\Address\RateFactory; +use Magento\Quote\Model\Quote\Address\RateRequest; +use Magento\Quote\Model\Quote\Address\RateRequestFactory; +use Magento\Quote\Model\Quote\Address\Total; +use Magento\Quote\Model\Quote\Address\Total\Collector; +use Magento\Quote\Model\Quote\Address\Total\CollectorFactory; +use Magento\Quote\Model\Quote\Address\TotalFactory; +use Magento\Quote\Model\Quote\Item\AbstractItem; +use Magento\Quote\Model\ResourceModel\Quote\Address\Rate\CollectionFactory; +use Magento\Shipping\Model\CarrierFactoryInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; /** @@ -22,8 +54,8 @@ * @method Address setCreatedAt(string $value) * @method string getUpdatedAt() * @method Address setUpdatedAt(string $value) - * @method \Magento\Customer\Api\Data\AddressInterface getCustomerAddress() - * @method Address setCustomerAddressData(\Magento\Customer\Api\Data\AddressInterface $value) + * @method AddressInterface getCustomerAddress() + * @method Address setCustomerAddressData(AddressInterface $value) * @method string getAddressType() * @method Address setAddressType(string $value) * @method int getFreeShipping() @@ -90,14 +122,14 @@ * @method int[] getAppliedRuleIds() * @method Address setBaseShippingInclTax(float $value) * - * @property $_objectCopyService \Magento\Framework\DataObject\Copy + * @property $objectCopyService \Magento\Framework\DataObject\Copy * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ -class Address extends \Magento\Customer\Model\Address\AbstractAddress implements +class Address extends AbstractAddress implements \Magento\Quote\Api\Data\AddressInterface { const RATES_FETCH = 1; @@ -125,28 +157,28 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements /** * Quote object * - * @var \Magento\Quote\Model\Quote + * @var Quote */ protected $_items; /** * Quote object * - * @var \Magento\Quote\Model\Quote + * @var Quote */ protected $_quote; /** * Sales Quote address rates * - * @var \Magento\Quote\Model\Quote\Address\Rate + * @var Rate */ protected $_rates; /** * Total models collector * - * @var \Magento\Quote\Model\Quote\Address\Total\Collector + * @var Collector */ protected $_totalCollector; @@ -170,7 +202,7 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements /** * Core store config * - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ protected $_scopeConfig; @@ -185,33 +217,33 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements protected $_itemCollectionFactory; /** - * @var \Magento\Quote\Model\Quote\Address\RateCollectorInterfaceFactory + * @var RateCollectorInterfaceFactory */ protected $_rateCollector; /** - * @var \Magento\Quote\Model\ResourceModel\Quote\Address\Rate\CollectionFactory + * @var CollectionFactory */ protected $_rateCollectionFactory; /** - * @var \Magento\Quote\Model\Quote\Address\Total\CollectorFactory + * @var CollectorFactory */ protected $_totalCollectorFactory; /** - * @var \Magento\Quote\Model\Quote\Address\TotalFactory + * @var TotalFactory */ protected $_addressTotalFactory; /** - * @var \Magento\Quote\Model\Quote\Address\RateFactory + * @var RateFactory * @since 100.2.0 */ protected $_addressRateFactory; /** - * @var \Magento\Customer\Api\Data\AddressInterfaceFactory + * @var AddressInterfaceFactory */ protected $addressDataFactory; @@ -221,7 +253,7 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements protected $validator; /** - * @var \Magento\Customer\Model\Address\Mapper + * @var Mapper */ protected $addressMapper; @@ -241,7 +273,7 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements protected $totalsCollector; /** - * @var \Magento\Quote\Model\Quote\TotalsReader + * @var TotalsReader */ protected $totalsReader; @@ -256,37 +288,47 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements private $storeManager; /** - * @param \Magento\Framework\Model\Context $context - * @param \Magento\Framework\Registry $registry - * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory - * @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory - * @param \Magento\Directory\Helper\Data $directoryData + * @var Copy + */ + private $objectCopyService; + + /** + * @var /Magento\Shipping\Model\CarrierFactoryInterface + */ + private $carrierFactory; + + /** + * @param Context $context + * @param Registry $registry + * @param ExtensionAttributesFactory $extensionFactory + * @param AttributeValueFactory $customAttributeFactory + * @param Data $directoryData * @param \Magento\Eav\Model\Config $eavConfig * @param \Magento\Customer\Model\Address\Config $addressConfig - * @param \Magento\Directory\Model\RegionFactory $regionFactory - * @param \Magento\Directory\Model\CountryFactory $countryFactory + * @param RegionFactory $regionFactory + * @param CountryFactory $countryFactory * @param AddressMetadataInterface $metadataService * @param AddressInterfaceFactory $addressDataFactory * @param RegionInterfaceFactory $regionDataFactory - * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param DataObjectHelper $dataObjectHelper + * @param ScopeConfigInterface $scopeConfig * @param Address\ItemFactory $addressItemFactory * @param \Magento\Quote\Model\ResourceModel\Quote\Address\Item\CollectionFactory $itemCollectionFactory - * @param \Magento\Quote\Model\Quote\Address\RateFactory $addressRateFactory + * @param RateFactory $addressRateFactory * @param Address\RateCollectorInterfaceFactory $rateCollector - * @param \Magento\Quote\Model\ResourceModel\Quote\Address\Rate\CollectionFactory $rateCollectionFactory + * @param CollectionFactory $rateCollectionFactory * @param Address\RateRequestFactory $rateRequestFactory * @param Address\Total\CollectorFactory $totalCollectorFactory * @param Address\TotalFactory $addressTotalFactory - * @param \Magento\Framework\DataObject\Copy $objectCopyService - * @param \Magento\Shipping\Model\CarrierFactoryInterface $carrierFactory + * @param Copy $objectCopyService + * @param CarrierFactoryInterface $carrierFactory * @param Address\Validator $validator - * @param \Magento\Customer\Model\Address\Mapper $addressMapper + * @param Mapper $addressMapper * @param Address\CustomAttributeListInterface $attributeList * @param TotalsCollector $totalsCollector - * @param \Magento\Quote\Model\Quote\TotalsReader $totalsReader - * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource - * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection + * @param TotalsReader $totalsReader + * @param AbstractResource|null $resource + * @param AbstractDb|null $resourceCollection * @param array $data * @param Json $serializer * @param StoreManagerInterface $storeManager @@ -294,37 +336,37 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Framework\Model\Context $context, - \Magento\Framework\Registry $registry, - \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory, - \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory, - \Magento\Directory\Helper\Data $directoryData, + Context $context, + Registry $registry, + ExtensionAttributesFactory $extensionFactory, + AttributeValueFactory $customAttributeFactory, + Data $directoryData, \Magento\Eav\Model\Config $eavConfig, \Magento\Customer\Model\Address\Config $addressConfig, - \Magento\Directory\Model\RegionFactory $regionFactory, - \Magento\Directory\Model\CountryFactory $countryFactory, + RegionFactory $regionFactory, + CountryFactory $countryFactory, AddressMetadataInterface $metadataService, AddressInterfaceFactory $addressDataFactory, RegionInterfaceFactory $regionDataFactory, - \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + DataObjectHelper $dataObjectHelper, + ScopeConfigInterface $scopeConfig, \Magento\Quote\Model\Quote\Address\ItemFactory $addressItemFactory, \Magento\Quote\Model\ResourceModel\Quote\Address\Item\CollectionFactory $itemCollectionFactory, - \Magento\Quote\Model\Quote\Address\RateFactory $addressRateFactory, - \Magento\Quote\Model\Quote\Address\RateCollectorInterfaceFactory $rateCollector, - \Magento\Quote\Model\ResourceModel\Quote\Address\Rate\CollectionFactory $rateCollectionFactory, - \Magento\Quote\Model\Quote\Address\RateRequestFactory $rateRequestFactory, - \Magento\Quote\Model\Quote\Address\Total\CollectorFactory $totalCollectorFactory, - \Magento\Quote\Model\Quote\Address\TotalFactory $addressTotalFactory, - \Magento\Framework\DataObject\Copy $objectCopyService, - \Magento\Shipping\Model\CarrierFactoryInterface $carrierFactory, + RateFactory $addressRateFactory, + RateCollectorInterfaceFactory $rateCollector, + CollectionFactory $rateCollectionFactory, + RateRequestFactory $rateRequestFactory, + CollectorFactory $totalCollectorFactory, + TotalFactory $addressTotalFactory, + Copy $objectCopyService, + CarrierFactoryInterface $carrierFactory, Address\Validator $validator, - \Magento\Customer\Model\Address\Mapper $addressMapper, + Mapper $addressMapper, Address\CustomAttributeListInterface $attributeList, - \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector, - \Magento\Quote\Model\Quote\TotalsReader $totalsReader, - \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, - \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + TotalsCollector $totalsCollector, + TotalsReader $totalsReader, + AbstractResource $resource = null, + AbstractDb $resourceCollection = null, array $data = [], Json $serializer = null, StoreManagerInterface $storeManager = null @@ -338,8 +380,8 @@ public function __construct( $this->_rateRequestFactory = $rateRequestFactory; $this->_totalCollectorFactory = $totalCollectorFactory; $this->_addressTotalFactory = $addressTotalFactory; - $this->_objectCopyService = $objectCopyService; - $this->_carrierFactory = $carrierFactory; + $this->objectCopyService = $objectCopyService; + $this->carrierFactory = $carrierFactory; $this->addressDataFactory = $addressDataFactory; $this->validator = $validator; $this->addressMapper = $addressMapper; @@ -412,7 +454,7 @@ protected function _populateBeforeSaveData() $this->setCustomerAddressId($this->getCustomerAddressData()->getId()); } - if (!$this->getId()) { + if (!$this->getId() || $this->getQuote()->dataHasChangedFor('customer_id')) { $this->setSameAsBilling((int)$this->_isSameAsBilling()); } } @@ -427,7 +469,7 @@ protected function _isSameAsBilling() { $quoteSameAsBilling = $this->getSameAsBilling(); - return $this->getAddressType() == \Magento\Quote\Model\Quote\Address::TYPE_SHIPPING && + return $this->getAddressType() == Address::TYPE_SHIPPING && ($this->_isNotRegisteredCustomer() || $this->_isDefaultShippingNullOrSameAsBillingAddress()) && ($quoteSameAsBilling || $quoteSameAsBilling === 0 || $quoteSameAsBilling === null); } @@ -473,10 +515,10 @@ protected function _isDefaultShippingNullOrSameAsBillingAddress() /** * Declare address quote model object * - * @param \Magento\Quote\Model\Quote $quote + * @param Quote $quote * @return $this */ - public function setQuote(\Magento\Quote\Model\Quote $quote) + public function setQuote(Quote $quote) { $this->_quote = $quote; $this->setQuoteId($quote->getId()); @@ -486,7 +528,7 @@ public function setQuote(\Magento\Quote\Model\Quote $quote) /** * Retrieve quote object * - * @return \Magento\Quote\Model\Quote + * @return Quote */ public function getQuote() { @@ -496,12 +538,12 @@ public function getQuote() /** * Import quote address data from customer address Data Object. * - * @param \Magento\Customer\Api\Data\AddressInterface $address + * @param AddressInterface $address * @return $this */ - public function importCustomerAddressData(\Magento\Customer\Api\Data\AddressInterface $address) + public function importCustomerAddressData(AddressInterface $address) { - $this->_objectCopyService->copyFieldsetToTarget( + $this->objectCopyService->copyFieldsetToTarget( 'customer_address', 'to_quote_address', $this->addressMapper->toFlatArray($address), @@ -519,11 +561,11 @@ public function importCustomerAddressData(\Magento\Customer\Api\Data\AddressInte /** * Export data to customer address Data Object. * - * @return \Magento\Customer\Api\Data\AddressInterface + * @return AddressInterface */ public function exportCustomerAddress() { - $customerAddressData = $this->_objectCopyService->getDataFromFieldset( + $customerAddressData = $this->objectCopyService->getDataFromFieldset( 'sales_convert_quote_address', 'to_customer_address', $this @@ -542,7 +584,7 @@ public function exportCustomerAddress() $this->dataObjectHelper->populateWithArray( $addressDataObject, $customerAddressData, - \Magento\Customer\Api\Data\AddressInterface::class + AddressInterface::class ); return $addressDataObject; } @@ -568,7 +610,7 @@ public function toArray(array $arrAttributes = []) /** * Retrieve address items collection * - * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection + * @return AbstractCollection */ public function getItemsCollection() { @@ -765,13 +807,13 @@ public function removeItem($itemId) /** * Add item to address * - * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item + * @param AbstractItem $item * @param int $qty * @return $this */ - public function addItem(\Magento\Quote\Model\Quote\Item\AbstractItem $item, $qty = null) + public function addItem(AbstractItem $item, $qty = null) { - if ($item instanceof \Magento\Quote\Model\Quote\Item) { + if ($item instanceof Item) { if ($item->getParentItemId()) { return $this; } @@ -808,7 +850,7 @@ public function addItem(\Magento\Quote\Model\Quote\Item\AbstractItem $item, $qty /** * Retrieve collection of quote shipping rates * - * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection + * @return AbstractCollection */ public function getShippingRatesCollection() { @@ -849,13 +891,13 @@ public function getGroupedAllShippingRates() { $rates = []; foreach ($this->getShippingRatesCollection() as $rate) { - if (!$rate->isDeleted() && $this->_carrierFactory->get($rate->getCarrier())) { + if (!$rate->isDeleted() && $this->carrierFactory->get($rate->getCarrier())) { if (!isset($rates[$rate->getCarrier()])) { $rates[$rate->getCarrier()] = []; } $rates[$rate->getCarrier()][] = $rate; - $rates[$rate->getCarrier()][0]->carrier_sort_order = $this->_carrierFactory->get( + $rates[$rate->getCarrier()][0]->carrier_sort_order = $this->carrierFactory->get( $rate->getCarrier() )->getSortOrder(); } @@ -881,7 +923,7 @@ protected function _sortRates($firstItem, $secondItem) * Retrieve shipping rate by identifier * * @param int $rateId - * @return \Magento\Quote\Model\Quote\Address\Rate|false + * @return Rate|false */ public function getShippingRateById($rateId) { @@ -898,7 +940,7 @@ public function getShippingRateById($rateId) * Retrieve shipping rate by code * * @param string $code - * @return \Magento\Quote\Model\Quote\Address\Rate|false + * @return Rate|false */ public function getShippingRateByCode($code) { @@ -927,10 +969,10 @@ public function removeAllShippingRates() /** * Add shipping rate * - * @param \Magento\Quote\Model\Quote\Address\Rate $rate + * @param Rate $rate * @return $this */ - public function addShippingRate(\Magento\Quote\Model\Quote\Address\Rate $rate) + public function addShippingRate(Rate $rate) { $rate->setAddress($this); $this->getShippingRatesCollection()->addItem($rate); @@ -970,14 +1012,14 @@ public function collectShippingRates() * * Returns true if current selected shipping method code corresponds to one of the found rates * - * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item + * @param AbstractItem $item * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function requestShippingRates(\Magento\Quote\Model\Quote\Item\AbstractItem $item = null) + public function requestShippingRates(AbstractItem $item = null) { - /** @var $request \Magento\Quote\Model\Quote\Address\RateRequest */ + /** @var $request RateRequest */ $request = $this->_rateRequestFactory->create(); $request->setAllItems($item ? [$item] : $this->getAllItems()); $request->setDestCountryId($this->getCountryId()); @@ -1041,7 +1083,7 @@ public function requestShippingRates(\Magento\Quote\Model\Quote\Item\AbstractIte $item->setBaseShippingAmount($rate->getPrice()); } else { - /** @var \Magento\Store\Api\Data\StoreInterface */ + /** @var StoreInterface */ $store = $this->storeManager->getStore(); $amountPrice = $store->getBaseCurrency() ->convert($rate->getPrice(), $store->getCurrentCurrencyCode()); @@ -1078,17 +1120,17 @@ public function getTotals() /** * Add total data or model * - * @param \Magento\Quote\Model\Quote\Address\Total|array $total + * @param Total|array $total * @return $this */ public function addTotal($total) { $addressTotal = null; if (is_array($total)) { - /** @var \Magento\Quote\Model\Quote\Address\Total $addressTotal */ - $addressTotal = $this->_addressTotalFactory->create(\Magento\Quote\Model\Quote\Address\Total::class); + /** @var Total $addressTotal */ + $addressTotal = $this->_addressTotalFactory->create(Total::class); $addressTotal->setData($total); - } elseif ($total instanceof \Magento\Quote\Model\Quote\Address\Total) { + } elseif ($total instanceof Total) { $addressTotal = $total; } @@ -1104,7 +1146,7 @@ public function addTotal($total) /** * Rewrite clone method * - * @return \Magento\Quote\Model\Quote\Address + * @return Address */ public function __clone() { @@ -1141,7 +1183,7 @@ public function validateMinimumAmount() $storeId = $this->getQuote()->getStoreId(); $validateEnabled = $this->_scopeConfig->isSetFlag( 'sales/minimum_order/active', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $storeId ); if (!$validateEnabled) { @@ -1154,17 +1196,17 @@ public function validateMinimumAmount() $includeDiscount = $this->_scopeConfig->getValue( 'sales/minimum_order/include_discount_amount', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $storeId ); $amount = $this->_scopeConfig->getValue( 'sales/minimum_order/amount', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $storeId ); $taxInclude = $this->_scopeConfig->getValue( 'sales/minimum_order/tax_including', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $storeId ); @@ -1701,7 +1743,7 @@ public function setSaveInAddressBook($saveInAddressBook) /** * @inheritdoc * - * @return \Magento\Quote\Api\Data\AddressExtensionInterface|null + * @return AddressExtensionInterface|null */ public function getExtensionAttributes() { @@ -1711,10 +1753,10 @@ public function getExtensionAttributes() /** * @inheritdoc * - * @param \Magento\Quote\Api\Data\AddressExtensionInterface $extensionAttributes + * @param AddressExtensionInterface $extensionAttributes * @return $this */ - public function setExtensionAttributes(\Magento\Quote\Api\Data\AddressExtensionInterface $extensionAttributes) + public function setExtensionAttributes(AddressExtensionInterface $extensionAttributes) { return $this->_setExtensionAttributes($extensionAttributes); } diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote.php b/app/code/Magento/Quote/Model/ResourceModel/Quote.php index ae26407c74522..48945dacd1738 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote.php @@ -102,6 +102,7 @@ public function loadByCustomerId($quote, $customerId) if ($data) { $quote->setData($data); + $quote->setOrigData(); } $this->_afterLoad($quote); @@ -124,6 +125,7 @@ public function loadActive($quote, $quoteId) $data = $connection->fetchRow($select); if ($data) { $quote->setData($data); + $quote->setOrigData(); } $this->_afterLoad($quote); @@ -148,6 +150,7 @@ public function loadByIdWithoutStore($quote, $quoteId) if ($data) { $quote->setData($data); + $quote->setOrigData(); } } @@ -303,5 +306,7 @@ public function save(\Magento\Framework\Model\AbstractModel $object) if (!$object->isPreventSaving()) { return parent::save($object); } + + return $this; } } diff --git a/app/code/Magento/Quote/Model/ShippingMethodManagement.php b/app/code/Magento/Quote/Model/ShippingMethodManagement.php index f62866539c6cc..73a2a43b2581f 100644 --- a/app/code/Magento/Quote/Model/ShippingMethodManagement.php +++ b/app/code/Magento/Quote/Model/ShippingMethodManagement.php @@ -7,6 +7,7 @@ namespace Magento\Quote\Model; use Magento\Customer\Api\Data\AddressInterfaceFactory; +use Magento\Customer\Model\Session as CustomerSession; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\InputException; @@ -22,6 +23,7 @@ * Shipping method read service * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class ShippingMethodManagement implements \Magento\Quote\Api\ShippingMethodManagementInterface, @@ -69,6 +71,11 @@ class ShippingMethodManagement implements */ private $quoteAddressResource; + /** + * @var CustomerSession + */ + private $customerSession; + /** * Constructor * @@ -78,6 +85,7 @@ class ShippingMethodManagement implements * @param Quote\TotalsCollector $totalsCollector * @param AddressInterfaceFactory|null $addressFactory * @param QuoteAddressResource|null $quoteAddressResource + * @param CustomerSession|null $customerSession */ public function __construct( \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, @@ -85,7 +93,8 @@ public function __construct( \Magento\Customer\Api\AddressRepositoryInterface $addressRepository, \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector, AddressInterfaceFactory $addressFactory = null, - QuoteAddressResource $quoteAddressResource = null + QuoteAddressResource $quoteAddressResource = null, + CustomerSession $customerSession = null ) { $this->quoteRepository = $quoteRepository; $this->converter = $converter; @@ -95,10 +104,11 @@ public function __construct( ->get(AddressInterfaceFactory::class); $this->quoteAddressResource = $quoteAddressResource ?: ObjectManager::getInstance() ->get(QuoteAddressResource::class); + $this->customerSession = $customerSession ?? ObjectManager::getInstance()->get(CustomerSession::class); } /** - * {@inheritDoc} + * @inheritDoc */ public function get($cartId) { @@ -126,7 +136,7 @@ public function get($cartId) } /** - * {@inheritDoc} + * @inheritDoc */ public function getList($cartId) { @@ -155,7 +165,7 @@ public function getList($cartId) } /** - * {@inheritDoc} + * @inheritDoc */ public function set($cartId, $carrierCode, $methodCode) { @@ -176,6 +186,8 @@ public function set($cartId, $carrierCode, $methodCode) } /** + * Apply carrier code. + * * @param int $cartId The shopping cart ID. * @param string $carrierCode The carrier code. * @param string $methodCode The shipping method code. @@ -209,7 +221,7 @@ public function apply($cartId, $carrierCode, $methodCode) } /** - * {@inheritDoc} + * @inheritDoc */ public function estimateByAddress($cartId, \Magento\Quote\Api\Data\EstimateAddressInterface $address) { @@ -240,7 +252,7 @@ public function estimateByExtendedAddress($cartId, AddressInterface $address) } /** - * {@inheritDoc} + * @inheritDoc */ public function estimateByAddressId($cartId, $addressId) { @@ -266,6 +278,7 @@ public function estimateByAddressId($cartId, $addressId) * @param string $region * @param \Magento\Framework\Api\ExtensibleDataInterface|null $address * @return \Magento\Quote\Api\Data\ShippingMethodInterface[] An array of shipping methods. + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @deprecated 100.2.0 */ protected function getEstimatedRates( @@ -277,11 +290,10 @@ protected function getEstimatedRates( $address = null ) { if (!$address) { - $address = $this->getAddressFactory()->create() + $address = $this->addressFactory->create() ->setCountryId($country) ->setPostcode($postcode) - ->setRegionId($regionId) - ->setRegion($region); + ->setRegionId($regionId); } return $this->getShippingMethods($quote, $address); } @@ -301,12 +313,21 @@ private function getShippingMethods(Quote $quote, $address) $shippingAddress->setCollectShippingRates(true); $this->totalsCollector->collectAddressTotals($quote, $shippingAddress); + $quoteCustomerGroupId = $quote->getCustomerGroupId(); + $customerGroupId = $this->customerSession->getCustomerGroupId(); + $isCustomerGroupChanged = $quoteCustomerGroupId !== $customerGroupId; + if ($isCustomerGroupChanged) { + $quote->setCustomerGroupId($customerGroupId); + } $shippingRates = $shippingAddress->getGroupedAllShippingRates(); foreach ($shippingRates as $carrierRates) { foreach ($carrierRates as $rate) { $output[] = $this->converter->modelToDataObject($rate, $quote->getQuoteCurrencyCode()); } } + if ($isCustomerGroupChanged) { + $quote->setCustomerGroupId($quoteCustomerGroupId); + } return $output; } diff --git a/app/code/Magento/Reports/etc/adminhtml/system.xml b/app/code/Magento/Reports/etc/adminhtml/system.xml index 40eba9f3a6bb2..66507afda5b3b 100644 --- a/app/code/Magento/Reports/etc/adminhtml/system.xml +++ b/app/code/Magento/Reports/etc/adminhtml/system.xml @@ -8,9 +8,9 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="catalog"> - <group id="recently_products" translate="label" type="text" sortOrder="350" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="recently_products" translate="label" type="text" sortOrder="350" showInDefault="1" showInWebsite="1"> <label>Recently Viewed/Compared Products</label> - <field id="scope" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="scope" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Show for Current</label> <source_model>Magento\Config\Model\Config\Source\Reports\Scope</source_model> <hide_in_single_store_mode>1</hide_in_single_store_mode> @@ -25,30 +25,30 @@ </field> </group> </section> - <section id="reports" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="0" showInStore="0"> + <section id="reports" translate="label" type="text" sortOrder="1000" showInDefault="1"> <label>Reports</label> <tab>general</tab> <resource>Magento_Reports::reports</resource> - <group id="dashboard" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="dashboard" translate="label" type="text" sortOrder="1" showInDefault="1"> <label>Dashboard</label> - <field id="ytd_start" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="ytd_start" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Year-To-Date Starts</label> <frontend_model>Magento\Reports\Block\Adminhtml\Config\Form\Field\YtdStart</frontend_model> </field> - <field id="mtd_start" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="mtd_start" translate="label comment" type="select" sortOrder="2" showInDefault="1" canRestore="1"> <label>Current Month Starts</label> <frontend_model>Magento\Reports\Block\Adminhtml\Config\Form\Field\MtdStart</frontend_model> <comment>Select day of the month.</comment> </field> </group> - <group id="options" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="options" translate="label" type="text" sortOrder="1" showInDefault="1"> <label>General Options</label> - <field id="enabled" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="enabled" translate="label comment" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Enable Reports</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>If disabled, all report events will be disabled</comment> </field> - <field id="product_view_enabled" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="product_view_enabled" translate="label comment" type="select" sortOrder="1" showInDefault="1"> <label>Enable "Product View" Report</label> <comment>If enabled, will collect statistic of viewed product pages</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -56,7 +56,7 @@ <field id="enabled">1</field> </depends> </field> - <field id="product_send_enabled" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="product_send_enabled" translate="label comment" type="select" sortOrder="2" showInDefault="1"> <label>Enable "Send Product Link To Friend" Report</label> <comment>If enabled, will collect statistic of product links sent to friend</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -64,7 +64,7 @@ <field id="enabled">1</field> </depends> </field> - <field id="product_compare_enabled" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="product_compare_enabled" translate="label comment" type="select" sortOrder="3" showInDefault="1"> <label>Enable "Add Product To Compare List" Report</label> <comment>If enabled, will collect statistic of products added to Compare List</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -72,7 +72,7 @@ <field id="enabled">1</field> </depends> </field> - <field id="product_to_cart_enabled" translate="label comment" type="select" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="product_to_cart_enabled" translate="label comment" type="select" sortOrder="4" showInDefault="1"> <label>Enable "Product Added To Cart" Report</label> <comment>If enabled, will collect statistic of products added to Cart</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -80,7 +80,7 @@ <field id="enabled">1</field> </depends> </field> - <field id="product_to_wishlist_enabled" translate="label comment" type="select" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="product_to_wishlist_enabled" translate="label comment" type="select" sortOrder="5" showInDefault="1"> <label>Enable "Product Added To WishList" Report</label> <comment>If enabled, will collect statistic of products added to WishList</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -88,7 +88,7 @@ <field id="enabled">1</field> </depends> </field> - <field id="wishlist_share_enabled" translate="label comment" type="select" sortOrder="6" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="wishlist_share_enabled" translate="label comment" type="select" sortOrder="6" showInDefault="1"> <label>Enable "Share WishList" Report</label> <comment>If enabled, will collect statistic of shared WishLists</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Reviews.php b/app/code/Magento/Review/Block/Adminhtml/Edit/Tab/Reviews.php similarity index 52% rename from app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Reviews.php rename to app/code/Magento/Review/Block/Adminhtml/Edit/Tab/Reviews.php index a10842fc0c6de..15d41fad0a595 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Reviews.php +++ b/app/code/Magento/Review/Block/Adminhtml/Edit/Tab/Reviews.php @@ -3,18 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Customer\Block\Adminhtml\Edit\Tab; +declare(strict_types=1); + +namespace Magento\Review\Block\Adminhtml\Edit\Tab; + +use Magento\Review\Block\Adminhtml\Grid; /** + * Review tab in adminhtml area. + * * @api - * @since 100.0.2 */ -class Reviews extends \Magento\Review\Block\Adminhtml\Grid +class Reviews extends Grid { /** - * Hide grid mass action elements + * Hide grid mass action elements. * - * @return \Magento\Customer\Block\Adminhtml\Edit\Tab\Reviews + * @return Reviews */ protected function _prepareMassaction() { @@ -28,6 +33,6 @@ protected function _prepareMassaction() */ public function getGridUrl() { - return $this->getUrl('customer/*/productReviews', ['_current' => true]); + return $this->getUrl('review/customer/productReviews', ['_current' => true]); } } diff --git a/app/code/Magento/Review/Block/Adminhtml/ReviewTab.php b/app/code/Magento/Review/Block/Adminhtml/ReviewTab.php index f62f8e2aa0d4c..9533be4acda54 100644 --- a/app/code/Magento/Review/Block/Adminhtml/ReviewTab.php +++ b/app/code/Magento/Review/Block/Adminhtml/ReviewTab.php @@ -11,9 +11,7 @@ use Magento\Ui\Component\Layout\Tabs\TabWrapper; /** - * Class ReviewTab - * - * @package Magento\Review\Block\Adminhtml + * Review tab block. */ class ReviewTab extends TabWrapper { @@ -30,8 +28,6 @@ class ReviewTab extends TabWrapper protected $isAjaxLoaded = true; /** - * Constructor - * * @param Context $context * @param Registry $registry * @param array $data @@ -67,6 +63,6 @@ public function getTabLabel() */ public function getTabUrl() { - return $this->getUrl('customer/*/productReviews', ['_current' => true]); + return $this->getUrl('review/customer/productReviews', ['_current' => true]); } } diff --git a/app/code/Magento/Review/Controller/Adminhtml/Customer/ProductReviews.php b/app/code/Magento/Review/Controller/Adminhtml/Customer/ProductReviews.php new file mode 100644 index 0000000000000..3ab49154a47a7 --- /dev/null +++ b/app/code/Magento/Review/Controller/Adminhtml/Customer/ProductReviews.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Review\Controller\Adminhtml\Customer; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Customer\Model\CustomerIdProvider; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\View\Result\Layout; + +/** + * Customer product reviews page. + */ +class ProductReviews extends Action implements HttpPostActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Review::reviews_all'; + + /** @var CustomerIdProvider */ + private $customerIdProvider; + + /** + * @param Context $context + * @param CustomerIdProvider $customerIdProvider + */ + public function __construct(Context $context, CustomerIdProvider $customerIdProvider) + { + $this->customerIdProvider = $customerIdProvider; + parent::__construct($context); + } + + /** + * Get customer's product reviews list. + * + * @return Layout + */ + public function execute() + { + $customerId = $this->customerIdProvider->getCustomerId(); + /** @var \Magento\Framework\View\Result\Layout $resultLayout */ + $resultLayout = $this->resultFactory->create(ResultFactory::TYPE_LAYOUT); + $block = $resultLayout->getLayout()->getBlock('admin.customer.reviews'); + $block->setCustomerId($customerId)->setUseAjax(true); + + return $resultLayout; + } +} diff --git a/app/code/Magento/Review/Test/Mftf/Test/StorefrontNoJavascriptErrorOnAddYourReviewClickTest.xml b/app/code/Magento/Review/Test/Mftf/Test/StorefrontNoJavascriptErrorOnAddYourReviewClickTest.xml index 99e418a950c69..c981d70938e11 100644 --- a/app/code/Magento/Review/Test/Mftf/Test/StorefrontNoJavascriptErrorOnAddYourReviewClickTest.xml +++ b/app/code/Magento/Review/Test/Mftf/Test/StorefrontNoJavascriptErrorOnAddYourReviewClickTest.xml @@ -11,6 +11,7 @@ <test name="StorefrontNoJavascriptErrorOnAddYourReviewClickTest"> <annotations> <features value="Review"/> + <stories value="Storefront Add Review"/> <title value="Storefront no javascript error on 'Add Your Review' click test"/> <description value="Verify no javascript error occurs when customer clicks 'Add Your Review' link"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Review/Test/Unit/Observer/CatalogProductListCollectionAppendSummaryFieldsObserverTest.php b/app/code/Magento/Review/Test/Unit/Observer/CatalogProductListCollectionAppendSummaryFieldsObserverTest.php new file mode 100644 index 0000000000000..894463de93227 --- /dev/null +++ b/app/code/Magento/Review/Test/Unit/Observer/CatalogProductListCollectionAppendSummaryFieldsObserverTest.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Review\Test\Unit\Observer; + +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Review\Model\ResourceModel\Review\Summary; +use Magento\Review\Model\ResourceModel\Review\SummaryFactory; +use Magento\Review\Observer\CatalogProductListCollectionAppendSummaryFieldsObserver; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test class for \Magento\Review\Observer\CatalogProductListCollectionAppendSummaryFieldsObserver + */ +class CatalogProductListCollectionAppendSummaryFieldsObserverTest extends TestCase +{ + private const STORE_ID = '1'; + + /** + * @var Event|MockObject + */ + private $eventMock; + + /** + * Testable Object + * + * @var CatalogProductListCollectionAppendSummaryFieldsObserver + */ + private $observer; + + /** + * @var Observer|MockObject + */ + private $observerMock; + + /** + * @var Collection|MockObject + */ + private $productCollectionMock; + + /** + * @var StoreInterface|MockObject + */ + private $storeMock; + + /** + * @var StoreManagerInterface|MockObject + */ + private $storeManagerMock; + + /** + * @var Summary|MockObject + */ + private $sumResourceMock; + + /** + * @var SummaryFactory|MockObject + */ + private $sumResourceFactoryMock; + + /** + * @inheritdoc + */ + protected function setUp() : void + { + $this->eventMock = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->setMethods(['getCollection']) + ->getMock(); + + $this->observerMock = $this->createMock(Observer::class); + + $this->productCollectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getStore']) + ->getMockForAbstractClass(); + + $this->storeMock = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMockForAbstractClass(); + + $this->sumResourceMock = $this->createPartialMock( + Summary::class, + ['appendSummaryFieldsToCollection'] + ); + + $this->sumResourceFactoryMock = $this->getMockBuilder(SummaryFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->observer = new CatalogProductListCollectionAppendSummaryFieldsObserver( + $this->sumResourceFactoryMock, + $this->storeManagerMock + ); + } + + /** + * Product listing test + */ + public function testAddSummaryFieldToProductsCollection() : void + { + $this->eventMock + ->expects($this->once()) + ->method('getCollection') + ->willReturn($this->productCollectionMock); + + $this->observerMock + ->expects($this->once()) + ->method('getEvent') + ->willReturn($this->eventMock); + + $this->storeManagerMock + ->expects($this->once()) + ->method('getStore') + ->willReturn($this->storeMock); + + $this->storeMock + ->expects($this->once()) + ->method('getId') + ->willReturn(self::STORE_ID); + + $this->sumResourceFactoryMock + ->expects($this->once()) + ->method('create') + ->willReturn($this->sumResourceMock); + + $this->sumResourceMock + ->expects($this->once()) + ->method('appendSummaryFieldsToCollection') + ->willReturn($this->sumResourceMock); + + $this->observer->execute($this->observerMock); + } +} diff --git a/app/code/Magento/Review/etc/adminhtml/system.xml b/app/code/Magento/Review/etc/adminhtml/system.xml index a24ed29dc2c23..1ddae2118c5a7 100644 --- a/app/code/Magento/Review/etc/adminhtml/system.xml +++ b/app/code/Magento/Review/etc/adminhtml/system.xml @@ -8,15 +8,18 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="catalog"> - <group id="review" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="review" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> <label>Product Reviews</label> <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="allow_guest" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allow_guest" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allow Guests to Write Reviews</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="active">1</field> + </depends> </field> </group> </section> diff --git a/app/code/Magento/Customer/view/adminhtml/layout/customer_index_productreviews.xml b/app/code/Magento/Review/view/adminhtml/layout/review_customer_productreviews.xml similarity index 76% rename from app/code/Magento/Customer/view/adminhtml/layout/customer_index_productreviews.xml rename to app/code/Magento/Review/view/adminhtml/layout/review_customer_productreviews.xml index dfbfcac04ac67..ee1cc4bfb0871 100644 --- a/app/code/Magento/Customer/view/adminhtml/layout/customer_index_productreviews.xml +++ b/app/code/Magento/Review/view/adminhtml/layout/review_customer_productreviews.xml @@ -7,6 +7,6 @@ --> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> <container name="root" label="Root"> - <block class="Magento\Customer\Block\Adminhtml\Edit\Tab\Reviews" name="admin.customer.reviews"/> + <block class="Magento\Review\Block\Adminhtml\Edit\Tab\Reviews" name="admin.customer.reviews"/> </container> </layout> diff --git a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php index e216e2ae658ba..2206dbef38640 100644 --- a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php +++ b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php @@ -625,6 +625,8 @@ public function getMappedSqlField() $mappedSqlField = $this->getEavAttributeTableAlias() . '.value'; } elseif ($this->getAttribute() == 'category_ids') { $mappedSqlField = 'e.entity_id'; + } elseif ($this->getAttribute() == 'attribute_set_id') { + $mappedSqlField = 'e.attribute_set_id'; } else { $mappedSqlField = parent::getMappedSqlField(); } diff --git a/app/code/Magento/Rule/Test/Unit/Model/Condition/Product/AbstractProductTest.php b/app/code/Magento/Rule/Test/Unit/Model/Condition/Product/AbstractProductTest.php index 80f07c9d6550d..62d91de3ca8e2 100644 --- a/app/code/Magento/Rule/Test/Unit/Model/Condition/Product/AbstractProductTest.php +++ b/app/code/Magento/Rule/Test/Unit/Model/Condition/Product/AbstractProductTest.php @@ -3,85 +3,103 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Rule\Test\Unit\Model\Condition\Product; -use ReflectionMethod; -use ReflectionProperty; +use Magento\Catalog\Model\ProductCategoryList; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Catalog\Model\ResourceModel\Product; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; +use Magento\Eav\Model\Entity\Type; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection; +use Magento\Framework\DataObject; +use Magento\Framework\Model\AbstractModel; +use Magento\Rule\Model\Condition\Product\AbstractProduct; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class AbstractProductTest extends \PHPUnit\Framework\TestCase +/** + * Class to test Abstract Rule product condition data model + */ +class AbstractProductTest extends TestCase { /** * Tested condition * - * @var \Magento\Rule\Model\Condition\Product\AbstractProduct|\PHPUnit_Framework_MockObject_MockObject + * @var AbstractProduct|MockObject */ protected $_condition; /** * Framework object * - * @var \Magento\Framework\DataObject|\PHPUnit_Framework_MockObject_MockObject + * @var DataObject|MockObject */ protected $_object; /** - * Reflection for Magento\Rule\Model\Condition\Product\AbstractProduct::$_entityAttributeValues + * Reflection for AbstractProduct::$_entityAttributeValues * * @var \ReflectionProperty */ protected $_entityAttributeValuesProperty; /** - * Reflection for Magento\Rule\Model\Condition\Product\AbstractProduct::$_config + * Reflection for AbstractProduct::$_config * * @var \ReflectionProperty */ protected $_configProperty; /** - * Reflection for Magento\Rule\Model\Condition\Product\AbstractProduct::$productCategoryListProperty + * Reflection for AbstractProduct::$productCategoryListProperty * * @var \ReflectionProperty */ private $productCategoryListProperty; + /** + * @inheritdoc + */ protected function setUp() { $this->_condition = $this->getMockForAbstractClass( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + AbstractProduct::class, [], '', false ); $this->productCategoryListProperty = new \ReflectionProperty( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + AbstractProduct::class, 'productCategoryList' ); $this->productCategoryListProperty->setAccessible(true); $this->_entityAttributeValuesProperty = new \ReflectionProperty( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + AbstractProduct::class, '_entityAttributeValues' ); $this->_entityAttributeValuesProperty->setAccessible(true); $this->_configProperty = new \ReflectionProperty( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + AbstractProduct::class, '_config' ); $this->_configProperty->setAccessible(true); } + /** + * Test to validate equal category id condition + */ public function testValidateAttributeEqualCategoryId() { - $product = $this->createPartialMock(\Magento\Framework\Model\AbstractModel::class, ["getAttribute"]); + $product = $this->createPartialMock(AbstractModel::class, ["getAttribute"]); $this->_condition->setAttribute('category_ids'); $this->_condition->setValueParsed('1'); $this->_condition->setOperator('{}'); - $productCategoryList = $this->getMockBuilder(\Magento\Catalog\Model\ProductCategoryList::class) + $productCategoryList = $this->getMockBuilder(ProductCategoryList::class) ->disableOriginalConstructor() ->getMock(); $productCategoryList->method('getCategoryIds')->willReturn([1, 2]); @@ -91,7 +109,7 @@ public function testValidateAttributeEqualCategoryId() ); $this->_configProperty->setValue( $this->_condition, - $this->getMockBuilder(\Magento\Eav\Model\Config::class) + $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock() ); @@ -99,10 +117,13 @@ public function testValidateAttributeEqualCategoryId() $this->assertTrue($this->_condition->validate($product)); } + /** + * Test to validate empty attribute condition + */ public function testValidateEmptyEntityAttributeValues() { $product = $this->createPartialMock( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, ["getAttribute", 'getResource'] ); $product->expects($this->once()) @@ -110,7 +131,7 @@ public function testValidateEmptyEntityAttributeValues() ->willReturn(null); $product->setId(1); $configProperty = new \ReflectionProperty( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + AbstractProduct::class, '_entityAttributeValues' ); $configProperty->setAccessible(true); @@ -118,10 +139,13 @@ public function testValidateEmptyEntityAttributeValues() $this->assertFalse($this->_condition->validate($product)); } + /** + * Test to validate empty attribute value condition + */ public function testValidateEmptyEntityAttributeValuesWithResource() { $product = $this->createPartialMock( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, ["getAttribute", 'getResource'] ); $product->setId(1); @@ -132,18 +156,18 @@ public function testValidateEmptyEntityAttributeValuesWithResource() $this->_configProperty->setValue( $this->_condition, - $this->createMock(\Magento\Eav\Model\Config::class) + $this->createMock(Config::class) ); - $attribute = new \Magento\Framework\DataObject(); + $attribute = new DataObject(); $attribute->setBackendType('datetime'); - $newResource = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\Product::class, ['getAttribute']); + $newResource = $this->createPartialMock(Product::class, ['getAttribute']); $newResource->expects($this->any()) ->method('getAttribute') ->with('someAttribute') ->will($this->returnValue($attribute)); - $newResource->_config = $this->createMock(\Magento\Eav\Model\Config::class); + $newResource->_config = $this->createMock(Config::class); $product->expects($this->atLeastOnce()) ->method('getResource') ->willReturn($newResource); @@ -154,22 +178,25 @@ public function testValidateEmptyEntityAttributeValuesWithResource() $attribute->setBackendType('null'); $attribute->setFrontendInput('multiselect'); - $newResource = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\Product::class, ['getAttribute']); + $newResource = $this->createPartialMock(Product::class, ['getAttribute']); $newResource->expects($this->any()) ->method('getAttribute') ->with('someAttribute') ->will($this->returnValue($attribute)); - $newResource->_config = $this->createMock(\Magento\Eav\Model\Config::class); + $newResource->_config = $this->createMock(Config::class); $product->setResource($newResource); $this->assertFalse($this->_condition->validate($product)); } + /** + * Test to validate set entity attribute value with resource condition + */ public function testValidateSetEntityAttributeValuesWithResource() { $this->_condition->setAttribute('someAttribute'); $product = $this->createPartialMock( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, ['getAttribute', 'getResource'] ); $product->setAtribute('attribute'); @@ -177,22 +204,22 @@ public function testValidateSetEntityAttributeValuesWithResource() $this->_configProperty->setValue( $this->_condition, - $this->createMock(\Magento\Eav\Model\Config::class) + $this->createMock(Config::class) ); $this->_entityAttributeValuesProperty->setValue( $this->_condition, - $this->createMock(\Magento\Eav\Model\Config::class) + $this->createMock(Config::class) ); - $attribute = new \Magento\Framework\DataObject(); + $attribute = new DataObject(); $attribute->setBackendType('datetime'); - $newResource = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\Product::class, ['getAttribute']); + $newResource = $this->createPartialMock(Product::class, ['getAttribute']); $newResource->expects($this->any()) ->method('getAttribute') ->with('someAttribute') ->will($this->returnValue($attribute)); - $newResource->_config = $this->createMock(\Magento\Eav\Model\Config::class); + $newResource->_config = $this->createMock(Config::class); $product->expects($this->atLeastOnce()) ->method('getResource') @@ -209,10 +236,13 @@ public function testValidateSetEntityAttributeValuesWithResource() $this->assertFalse($this->_condition->validate($product)); } + /** + * Test to validate set entity attribute value without resource condition + */ public function testValidateSetEntityAttributeValuesWithoutResource() { $product = $this->createPartialMock( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, ['someMethod', 'getResource', 'load'] ); $this->_condition->setAttribute('someAttribute'); @@ -221,23 +251,23 @@ public function testValidateSetEntityAttributeValuesWithoutResource() $this->_configProperty->setValue( $this->_condition, - $this->createMock(\Magento\Eav\Model\Config::class) + $this->createMock(Config::class) ); $this->_entityAttributeValuesProperty->setValue( $this->_condition, - $this->createMock(\Magento\Eav\Model\Config::class) + $this->createMock(Config::class) ); - $attribute = new \Magento\Framework\DataObject(); + $attribute = new DataObject(); $attribute->setBackendType('multiselect'); - $newResource = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\Product::class, ['getAttribute']); + $newResource = $this->createPartialMock(Product::class, ['getAttribute']); $newResource->expects($this->any()) ->method('getAttribute') ->with('someAttribute') ->will($this->returnValue($attribute)); - $newResource->_config = $this->createMock(\Magento\Eav\Model\Config::class); + $newResource->_config = $this->createMock(Config::class); $product->expects($this->atLeastOnce()) ->method('getResource') @@ -254,16 +284,16 @@ public function testValidateSetEntityAttributeValuesWithoutResource() $this->assertFalse($this->_condition->validate($product)); - $attribute = new \Magento\Framework\DataObject(); + $attribute = new DataObject(); $attribute->setBackendType(null); $attribute->setFrontendInput('multiselect'); - $newResource = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\Product::class, ['getAttribute']); + $newResource = $this->createPartialMock(Product::class, ['getAttribute']); $newResource->expects($this->any()) ->method('getAttribute') ->with('someAttribute') ->will($this->returnValue($attribute)); - $newResource->_config = $this->createMock(\Magento\Eav\Model\Config::class); + $newResource->_config = $this->createMock(Config::class); $product->setResource($newResource); $product->setId(1); @@ -272,19 +302,29 @@ public function testValidateSetEntityAttributeValuesWithoutResource() $this->assertFalse($this->_condition->validate($product)); } + /** + * Test to get tables to join + */ public function testGetjointTables() { $this->_condition->setAttribute('category_ids'); $this->assertEquals([], $this->_condition->getTablesToJoin()); } + /** + * Test to get mapped sql field + */ public function testGetMappedSqlField() { $this->_condition->setAttribute('category_ids'); $this->assertEquals('e.entity_id', $this->_condition->getMappedSqlField()); + $this->_condition->setAttribute('attribute_set_id'); + $this->assertEquals('e.attribute_set_id', $this->_condition->getMappedSqlField()); } /** + * Test to prepare value options + * * @param array $setData * @param string $attributeObjectFrontendInput * @param array $attrObjectSourceAllOptionsValue @@ -308,7 +348,7 @@ public function testPrepareValueOptions( $this->_condition->setData($key, $value); } - $attrObjectSourceMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource::class) + $attrObjectSourceMock = $this->getMockBuilder(AbstractSource::class) ->setMethods(['getAllOptions']) ->disableOriginalConstructor() ->getMock(); @@ -318,7 +358,7 @@ public function testPrepareValueOptions( ->with($expectedAttrObjSourceAllOptionsParam) ->willReturn($attrObjectSourceAllOptionsValue); - $attributeObjectMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + $attributeObjectMock = $this->getMockBuilder(Attribute::class) ->setMethods(['usesSource', 'getFrontendInput', 'getSource', 'getAllOptions']) ->disableOriginalConstructor() ->getMock(); @@ -329,28 +369,28 @@ public function testPrepareValueOptions( ->willReturn($attributeObjectFrontendInput); $attributeObjectMock->method('getSource')->willReturn($attrObjectSourceMock); - $entityTypeMock = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel\Type::class) + $entityTypeMock = $this->getMockBuilder(Type::class) ->setMethods(['getId']) ->disableOriginalConstructor() ->getMock(); $entityTypeMock->method('getId')->willReturn('SomeEntityType'); $configValueMock = $this->createPartialMock( - \Magento\Eav\Model\Config::class, + Config::class, ['getAttribute', 'getEntityType'] ); $configValueMock->method('getAttribute')->willReturn($attributeObjectMock); $configValueMock->method('getEntityType')->willReturn($entityTypeMock); - $configProperty = new ReflectionProperty( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + $configProperty = new \ReflectionProperty( + AbstractProduct::class, '_config' ); $configProperty->setAccessible(true); $configProperty->setValue($this->_condition, $configValueMock); $attrSetCollectionValueMock = $this - ->getMockBuilder(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection::class) + ->getMockBuilder(Collection::class) ->setMethods(['setEntityTypeFilter', 'load', 'toOptionArray']) ->disableOriginalConstructor() ->getMock(); @@ -362,15 +402,15 @@ public function testPrepareValueOptions( ->method('toOptionArray') ->willReturn($attrSetCollectionOptionsArray); - $attrSetCollectionProperty = new ReflectionProperty( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + $attrSetCollectionProperty = new \ReflectionProperty( + AbstractProduct::class, '_attrSetCollection' ); $attrSetCollectionProperty->setAccessible(true); $attrSetCollectionProperty->setValue($this->_condition, $attrSetCollectionValueMock); - $testedMethod = new ReflectionMethod( - \Magento\Rule\Model\Condition\Product\AbstractProduct::class, + $testedMethod = new \ReflectionMethod( + AbstractProduct::class, '_prepareValueOptions' ); $testedMethod->setAccessible(true); @@ -395,7 +435,6 @@ public function prepareValueOptionsDataProvider() 'value_option' => ['k' => 'v'], ], null, null, null, null, ['key' => 'value'], ['k' => 'v'], ], - [ ['attribute' => 'attribute_set_id'], null, @@ -414,7 +453,6 @@ public function prepareValueOptionsDataProvider() 'value2' => 'Label for value 2' ] ], - [ [ 'value_select_options' => [ @@ -436,7 +474,6 @@ public function prepareValueOptionsDataProvider() 'value4' => 'Label for value 4' ] ], - [ [ 'value_select_options' => [ @@ -458,7 +495,6 @@ public function prepareValueOptionsDataProvider() 'value6' => 'Label for value 6' ] ], - [ [], 'multiselect', @@ -477,7 +513,6 @@ public function prepareValueOptionsDataProvider() 'value8' => 'Label for value 8', ], ], - [ [], 'multiselect', @@ -495,7 +530,6 @@ public function prepareValueOptionsDataProvider() 'valueA' => 'Label for value A', ], ], - [ [], 'select', diff --git a/app/code/Magento/Sales/Controller/AbstractController/Reorder.php b/app/code/Magento/Sales/Controller/AbstractController/Reorder.php index 65cb537e89fec..f53ecaa625bf5 100644 --- a/app/code/Magento/Sales/Controller/AbstractController/Reorder.php +++ b/app/code/Magento/Sales/Controller/AbstractController/Reorder.php @@ -4,16 +4,18 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Controller\AbstractController; use Magento\Framework\App\Action; use Magento\Framework\Registry; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Sales\Helper\Reorder as ReorderHelper; /** * Abstract class for controllers Reorder(Customer) and Reorder(Guest) - * - * @package Magento\Sales\Controller\AbstractController */ abstract class Reorder extends Action\Action implements HttpPostActionInterface { @@ -28,17 +30,27 @@ abstract class Reorder extends Action\Action implements HttpPostActionInterface protected $_coreRegistry; /** + * @var ReorderHelper + */ + private $reorderHelper; + + /** + * Constructor + * * @param Action\Context $context * @param OrderLoaderInterface $orderLoader * @param Registry $registry + * @param ReorderHelper|null $reorderHelper */ public function __construct( Action\Context $context, OrderLoaderInterface $orderLoader, - Registry $registry + Registry $registry, + ReorderHelper $reorderHelper = null ) { $this->orderLoader = $orderLoader; $this->_coreRegistry = $registry; + $this->reorderHelper = $reorderHelper ?: ObjectManager::getInstance()->get(ReorderHelper::class); parent::__construct($context); } @@ -57,6 +69,11 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); + if (!$this->reorderHelper->canReorder($order->getId())) { + $this->messageManager->addErrorMessage(__("Reorder is not available.")); + return $resultRedirect->setPath('checkout/cart'); + } + /* @var $cart \Magento\Checkout\Model\Cart */ $cart = $this->_objectManager->get(\Magento\Checkout\Model\Cart::class); $items = $order->getItemsCollection(); diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Discount.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Discount.php index 06bfbcf24daac..43a66382aa1c2 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Discount.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Discount.php @@ -5,17 +5,38 @@ */ namespace Magento\Sales\Model\Order\Creditmemo\Total; +use Magento\Tax\Model\Config; + /** * Discount total calculator */ class Discount extends AbstractTotal { + /** + * @var Config + */ + private $taxConfig; + + /** + * @param Config $taxConfig + * @param array $data + */ + public function __construct( + Config $taxConfig, + array $data = [] + ) { + $this->taxConfig = $taxConfig; + + parent::__construct($data); + } + /** * Collect discount * * @param \Magento\Sales\Model\Order\Creditmemo $creditmemo * @return $this * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo) { @@ -31,7 +52,7 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo) * Calculate how much shipping discount should be applied * basing on how much shipping should be refunded. */ - $baseShippingAmount = $this->getBaseShippingAmount($creditmemo); + $baseShippingAmount = $this->getBaseShippingAmount($creditmemo, $order); /** * If credit memo's shipping amount is set and Order's shipping amount is 0, @@ -43,10 +64,14 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo) ); } if ($baseShippingAmount) { + $orderBaseShippingAmount = $this->isShippingInclTax((int)$order->getStoreId()) ? + $order->getBaseShippingInclTax() : $order->getBaseShippingAmount(); + $orderShippingAmount = $this->isShippingInclTax((int)$order->getStoreId()) ? + $order->getShippingInclTax() : $order->getShippingAmount(); $baseShippingDiscount = $baseShippingAmount * $order->getBaseShippingDiscountAmount() / - $order->getBaseShippingAmount(); - $shippingDiscount = $order->getShippingAmount() * $baseShippingDiscount / $order->getBaseShippingAmount(); + $orderBaseShippingAmount; + $shippingDiscount = $orderShippingAmount * $baseShippingDiscount / $orderBaseShippingAmount; $totalDiscountAmount = $totalDiscountAmount + $shippingDiscount; $baseTotalDiscountAmount = $baseTotalDiscountAmount + $baseShippingDiscount; } @@ -104,8 +129,20 @@ private function getBaseShippingAmount(\Magento\Sales\Model\Order\Creditmemo $cr if (!$baseShippingAmount) { $baseShippingInclTax = (float)$creditmemo->getBaseShippingInclTax(); $baseShippingTaxAmount = (float)$creditmemo->getBaseShippingTaxAmount(); - $baseShippingAmount = $baseShippingInclTax - $baseShippingTaxAmount; + $baseShippingAmount = $this->isShippingInclTax((int)$creditmemo->getStoreId()) ? + $baseShippingInclTax : $baseShippingInclTax - $baseShippingTaxAmount; } return $baseShippingAmount; } + + /** + * Returns whether the user specified a shipping amount that already includes tax + * + * @param int $storeId + * @return bool + */ + private function isShippingInclTax(int $storeId): bool + { + return (bool)$this->taxConfig->displaySalesShippingInclTax($storeId); + } } diff --git a/app/code/Magento/Sales/Model/Order/Item.php b/app/code/Magento/Sales/Model/Order/Item.php index a0eff45179ac8..c133d3aea267d 100644 --- a/app/code/Magento/Sales/Model/Order/Item.php +++ b/app/code/Magento/Sales/Model/Order/Item.php @@ -385,6 +385,8 @@ public function getStatus() * * @param string $statusId * @return \Magento\Framework\Phrase + * + * phpcs:disable Magento2.Functions.StaticFunction */ public static function getStatusName($statusId) { @@ -422,6 +424,8 @@ public function cancel() * Retrieve order item statuses array * * @return array + * + * phpcs:disable Magento2.Functions.StaticFunction */ public static function getStatuses() { @@ -1319,7 +1323,13 @@ public function getParentItemId() */ public function getPrice() { - return $this->getData(OrderItemInterface::PRICE); + $price = $this->getData(OrderItemInterface::PRICE); + + if ($price === null) { + return $price; + } + + return (float) $price; } /** diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionOnGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionOnGridActionGroup.xml index a9091deb039fc..20f475d4c0d78 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionOnGridActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionOnGridActionGroup.xml @@ -19,11 +19,4 @@ <click selector="{{AdminOrdersGridSection.dropdownActionItem(action)}}" stepKey="selectAction"/> <waitForPageLoad stepKey="waitForResults"/> </actionGroup> - <actionGroup name="AdminTwoOrderActionOnGridActionGroup" extends="AdminOrderActionOnGridActionGroup"> - <arguments> - <argument name="secondOrderId" type="string"/> - </arguments> - <checkOption selector="{{AdminOrdersGridSection.selectOrderID(secondOrderId)}}" stepKey="selectSecondOrder" after="waitForCheck"/> - <waitForLoadingMaskToDisappear stepKey="waitForSecondCheck" after="selectSecondOrder"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminTwoOrderActionOnGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminTwoOrderActionOnGridActionGroup.xml new file mode 100644 index 0000000000000..4cb0a45078125 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminTwoOrderActionOnGridActionGroup.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="AdminTwoOrderActionOnGridActionGroup" extends="AdminOrderActionOnGridActionGroup"> + <arguments> + <argument name="secondOrderId" type="string"/> + </arguments> + <checkOption selector="{{AdminOrdersGridSection.selectOrderID(secondOrderId)}}" stepKey="selectSecondOrder" after="waitForCheck"/> + <waitForLoadingMaskToDisappear stepKey="waitForSecondCheck" after="selectSecondOrder"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml index 6d9f35efc7903..a15e176c943ab 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml @@ -72,6 +72,7 @@ <!-- Delete category --> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <magentoCLI command="cron:run --group=index" stepKey="runCron"/> </after> <!-- Login as customer --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml index 622718d721577..e499f72004986 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml @@ -88,7 +88,7 @@ <actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/> <!--Verify invoiced items qty in ship tab--> - <actionGroup ref="goToShipmentIntoOrder" stepKey="goToShipment"/> + <actionGroup ref="GoToShipmentIntoOrderActionGroup" stepKey="goToShipment"/> <grabTextFrom selector="{{AdminShipmentItemsSection.itemQtyInvoiced('1')}}" stepKey="grabInvoicedItemQty"/> <assertEquals expected="5" expectedType="string" actual="$grabInvoicedItemQty" stepKey="assertInvoicedItemsQty"/> </test> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml index 1c35a9e900a8a..8177bbaa6e1d7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml @@ -45,6 +45,10 @@ </actionGroup> <!--Step 2: Select taxable address as billing address--> <selectOption selector="{{AdminOrderFormBillingAddressSection.selectAddress}}" userInput="{{US_Address_CA.state}}" stepKey="selectTaxableAddress" /> + <waitForPageLoad stepKey="waitForChangeBillingAddress"/> + <!--Step 3: Set shipping address same as billing --> + <checkOption selector="{{AdminOrderFormShippingAddressSection.SameAsBilling}}" stepKey="checkSameAsBillingAddressCheckbox"/> + <waitForPageLoad stepKey="waitForChangeShippingAddress"/> <!--Step 3: Select FlatRate shipping method--> <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="selectFlatRateShippingMethod"/> <!--Step 4: Verify that tax is applied to the order--> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml index 7e58e55c8981e..1fa01e0efa156 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithDateTimeOptionUITest.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateOrderWithDateTimeOptionUITest"> <annotations> + <stories value="Create Order"/> <title value="Admin create order with date time option UI test"/> <description value="Check asterisk rendered correctly for Product with custom option (datetime) at backend"/> <features value="Sales"/> diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Guest/ReorderTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Guest/ReorderTest.php new file mode 100644 index 0000000000000..964a10f232daf --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Controller/Guest/ReorderTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Controller\Guest; + +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Message\ManagerInterface as MessageManagerInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Sales\Controller\Guest\OrderLoader; +use Magento\Sales\Controller\Guest\Reorder; +use Magento\Sales\Helper\Reorder as ReorderHelper; +use Magento\Sales\Model\Order; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test class for Reorder + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ReorderTest extends TestCase +{ + /** + * Stub Order Id + */ + private const STUB_ORDER_ID = 1; + + /** + * @var Reorder + */ + private $reorder; + + /** + * @var Registry|MockObject + */ + private $registryMock; + + /** + * @var OrderLoader|MockObject + */ + private $orderLoaderMock; + + /** + * @var RequestInterface|MockObject + */ + private $requestMock; + + /** + * @var RedirectFactory|MockObject + */ + private $resultRedirectFactoryMock; + + /** + * @var ReorderHelper|MockObject + */ + private $reorderHelperMock; + + /** + * @var MessageManagerInterface|MockObject + */ + private $messageManagerMock; + + /** + * Setup environment for test + */ + protected function setUp() + { + $contextMock = $this->createMock(Context::class); + $this->registryMock = $this->createMock(Registry::class); + $this->orderLoaderMock = $this->createMock(OrderLoader::class); + $this->requestMock = $this->createMock(RequestInterface::class); + $this->resultRedirectFactoryMock = $this->createMock(RedirectFactory::class); + $this->reorderHelperMock = $this->createMock(ReorderHelper::class); + $this->messageManagerMock = $this->createMock(MessageManagerInterface::class); + + $contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); + $contextMock->expects($this->once())->method('getResultRedirectFactory') + ->willReturn($this->resultRedirectFactoryMock); + $contextMock->expects($this->once())->method('getMessageManager') + ->willReturn($this->messageManagerMock); + + $objectManagerMock = $this->createMock(ObjectManagerInterface::class); + $objectManagerMock->expects($this->once())->method('get') + ->with(ReorderHelper::class) + ->willReturn($this->reorderHelperMock); + + ObjectManager::setInstance($objectManagerMock); + + $objectManager = new ObjectManagerHelper($this); + $this->reorder = $objectManager->getObject( + Reorder::class, + [ + 'context' => $contextMock, + 'orderLoader' => $this->orderLoaderMock, + 'registry' => $this->registryMock + ] + ); + } + + /** + * Test execute() with the reorder is not allowed + */ + public function testExecuteWithReorderIsNotAllowed() + { + $orderMock = $this->createMock(Order::class); + $orderMock->method('getId')->willReturn(self::STUB_ORDER_ID); + + $this->orderLoaderMock->method('load') + ->with($this->requestMock) + ->willReturn($this->resultRedirectFactoryMock); + + $this->registryMock->expects($this->once())->method('registry') + ->with('current_order') + ->willReturn($orderMock); + + $this->reorderHelperMock->method('canReorder')->with(self::STUB_ORDER_ID) + ->willReturn(false); + + $resultRedirectMock = $this->createMock(Redirect::class); + $this->resultRedirectFactoryMock->expects($this->once())->method('create')->willReturn($resultRedirectMock); + + $this->reorderHelperMock->method('canReorder')->with(self::STUB_ORDER_ID) + ->willReturn(false); + + /** Expected Error Message */ + $this->messageManagerMock->expects($this->once()) + ->method('addErrorMessage') + ->with('Reorder is not available.')->willReturnSelf(); + $resultRedirectMock->expects($this->once()) + ->method('setPath') + ->with('checkout/cart')->willReturnSelf(); + + /** Assert result */ + $this->assertEquals($resultRedirectMock, $this->reorder->execute()); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/DiscountTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/DiscountTest.php index 8a45aa8c7958e..07826ff1d0cbd 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/DiscountTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/DiscountTest.php @@ -6,9 +6,6 @@ namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Total; -/** - * Class DiscountTest - */ class DiscountTest extends \PHPUnit\Framework\TestCase { /** @@ -36,6 +33,11 @@ class DiscountTest extends \PHPUnit\Framework\TestCase */ protected $orderItemMock; + /** + * @var \Magento\Tax\Model\Config|\PHPUnit\Framework\MockObject\MockObject + */ + private $taxConfig; + protected function setUp() { $this->orderMock = $this->createPartialMock( @@ -54,7 +56,9 @@ protected function setUp() 'getHasChildren', 'getBaseCost', 'getQty', 'getOrderItem', 'setDiscountAmount', 'setBaseDiscountAmount', 'isLast' ]); - $this->total = new \Magento\Sales\Model\Order\Creditmemo\Total\Discount(); + $this->taxConfig = $this->createMock(\Magento\Tax\Model\Config::class); + + $this->total = new \Magento\Sales\Model\Order\Creditmemo\Total\Discount($this->taxConfig); } public function testCollect() @@ -74,7 +78,7 @@ public function testCollect() $this->orderMock->expects($this->once()) ->method('getBaseShippingDiscountAmount') ->willReturn(1); - $this->orderMock->expects($this->exactly(3)) + $this->orderMock->expects($this->exactly(2)) ->method('getBaseShippingAmount') ->willReturn(1); $this->orderMock->expects($this->once()) @@ -150,7 +154,7 @@ public function testCollectNoBaseShippingAmount() $this->orderMock->expects($this->once()) ->method('getBaseShippingDiscountAmount') ->willReturn(1); - $this->orderMock->expects($this->exactly(3)) + $this->orderMock->expects($this->exactly(2)) ->method('getBaseShippingAmount') ->willReturn(1); $this->orderMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php index 76bfd62a7b889..3c7042c10f4d3 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php @@ -7,13 +7,11 @@ namespace Magento\Sales\Test\Unit\Model\Order; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Model\ResourceModel\OrderFactory; -use \Magento\Sales\Model\Order; /** - * Class ItemTest - * - * @package Magento\Sales\Model\Order + * Unit test for order item class. */ class ItemTest extends \PHPUnit\Framework\TestCase { @@ -72,7 +70,7 @@ public function testSetParentItem() public function testGetPatentItem() { $item = $this->objectManager->getObject(\Magento\Sales\Model\Order\Item::class, []); - $this->model->setData(\Magento\Sales\Api\Data\OrderItemInterface::PARENT_ITEM, $item); + $this->model->setData(OrderItemInterface::PARENT_ITEM, $item); $this->assertEquals($item, $this->model->getParentItem()); } @@ -184,7 +182,7 @@ public function testGetOriginalPrice() $this->assertEquals($price, $this->model->getOriginalPrice()); $originalPrice = 5.55; - $this->model->setData(\Magento\Sales\Api\Data\OrderItemInterface::ORIGINAL_PRICE, $originalPrice); + $this->model->setData(OrderItemInterface::ORIGINAL_PRICE, $originalPrice); $this->assertEquals($originalPrice, $this->model->getOriginalPrice()); } @@ -348,4 +346,24 @@ public function getItemQtyVariants() ] ]; } + + /** + * Test getPrice() method returns float + */ + public function testGetPriceReturnsFloat() + { + $price = 9.99; + $this->model->setPrice($price); + $this->assertEquals($price, $this->model->getPrice()); + } + + /** + * Test getPrice() method returns null + */ + public function testGetPriceReturnsNull() + { + $nullablePrice = null; + $this->model->setData(OrderItemInterface::PRICE, $nullablePrice); + $this->assertEquals($nullablePrice, $this->model->getPrice()); + } } diff --git a/app/code/Magento/Sales/etc/adminhtml/system.xml b/app/code/Magento/Sales/etc/adminhtml/system.xml index 473d3068d69c7..84caeca093bad 100644 --- a/app/code/Magento/Sales/etc/adminhtml/system.xml +++ b/app/code/Magento/Sales/etc/adminhtml/system.xml @@ -17,31 +17,31 @@ <resource>Magento_Sales::config_sales</resource> <group id="general" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> <label>General</label> - <field id="hide_customer_ip" translate="label comment" type="select" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="hide_customer_ip" translate="label comment" type="select" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Hide Customer IP</label> <comment>Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> - <group id="totals_sort" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="totals_sort" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1"> <label>Checkout Totals Sort Order</label> - <field id="discount" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="discount" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Discount</label> <validate>required-number validate-number</validate> </field> - <field id="grand_total" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="grand_total" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Grand Total</label> <validate>required-number validate-number</validate> </field> - <field id="shipping" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="shipping" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Shipping</label> <validate>required-number validate-number</validate> </field> - <field id="subtotal" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="subtotal" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Subtotal</label> <validate>required-number validate-number</validate> </field> - <field id="tax" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="tax" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Tax</label> <validate>required-number validate-number</validate> </field> @@ -86,21 +86,21 @@ </group> <group id="minimum_order" translate="label" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Minimum Order Amount</label> - <field id="active" translate="label" sortOrder="5" type="select" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="active" translate="label" sortOrder="5" type="select" showInDefault="1" showInWebsite="1"> <label>Enable</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="amount" translate="label comment" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="amount" translate="label comment" sortOrder="10" showInDefault="1" showInWebsite="1"> <label>Minimum Amount</label> <validate>validate-number validate-greater-than-zero</validate> <comment>Subtotal after discount.</comment> </field> - <field id="include_discount_amount" translate="label" sortOrder="12" type="select" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="include_discount_amount" translate="label" sortOrder="12" type="select" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Include Discount Amount</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Choosing yes will be used subtotal after discount, otherwise only subtotal will be used.</comment> </field> - <field id="tax_including" translate="label" sortOrder="15" type="select" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="tax_including" translate="label" sortOrder="15" type="select" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Include Tax to Amount</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -111,7 +111,7 @@ <field id="error_message" translate="label" sortOrder="30" type="textarea" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Error to Show in Shopping Cart</label> </field> - <field id="multi_address" translate="label" sortOrder="40" type="select" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="multi_address" translate="label" sortOrder="40" type="select" showInDefault="1" showInWebsite="1"> <label>Validate Each Address Separately in Multi-address Checkout</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -124,17 +124,17 @@ <comment>We'll use the default error above if you leave this empty.</comment> </field> </group> - <group id="dashboard" translate="label" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="dashboard" translate="label" sortOrder="60" showInDefault="1"> <label>Dashboard</label> - <field id="use_aggregated_data" translate="label comment" sortOrder="10" type="select" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="use_aggregated_data" translate="label comment" sortOrder="10" type="select" showInDefault="1" canRestore="1"> <label>Use Aggregated Data</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Improves dashboard performance but provides non-realtime data.</comment> </field> </group> - <group id="orders" translate="label" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="orders" translate="label" sortOrder="70" showInDefault="1" showInWebsite="1"> <label>Orders Cron Settings</label> - <field id="delete_pending_after" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="delete_pending_after" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Pending Payment Order Lifetime (minutes)</label> <validate>validate-number validate-greater-than-zero</validate> </field> @@ -144,14 +144,14 @@ <label>Sales Emails</label> <tab>sales</tab> <resource>Magento_Sales::sales_email</resource> - <group id="general" type="text" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="general" type="text" sortOrder="0" showInDefault="1"> <label>General Settings</label> - <field id="async_sending" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="async_sending" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Asynchronous sending</label> <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> <backend_model>Magento\Sales\Model\Config\Backend\Email\AsyncSending</backend_model> </field> - <field id="sending_limit" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="sending_limit" translate="label" type="text" sortOrder="2" showInDefault="1" canRestore="1"> <label>Limit per cron run</label> <comment>Limit how many entities (orders/shipments/etc) will be processed during one cron run.</comment> <validate>required-number validate-number validate-greater-than-zero</validate> @@ -169,25 +169,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>New Order Confirmation Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>New Order Confirmation Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>New Order Confirmation Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Order Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Order Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="order_comment" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -199,25 +214,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Order Comment Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Order Comment Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Order Comment Email Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Order Comment Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Order Comments Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="invoice" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -229,25 +259,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Invoice Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Invoice Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Invoice Email Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Invoice Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Invoice Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="invoice_comment" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -259,25 +304,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Invoice Comment Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Invoice Comment Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Invoice Comment Email Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Invoice Comment Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Invoice Comments Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="shipment" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -289,25 +349,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Shipment Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Shipment Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Shipment Email Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Shipment Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Shipment Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="shipment_comment" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -319,25 +394,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Shipment Comment Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Shipment Comment Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Shipment Comment Email Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Shipment Comment Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Shipment Comments Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="creditmemo" translate="label" type="text" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -349,25 +439,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Credit Memo Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Credit Memo Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Credit Memo Email Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Credit Memo Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Credit Memo Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="creditmemo_comment" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -379,25 +484,40 @@ <field id="identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Credit Memo Comment Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="template" translate="label comment" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Credit Memo Comment Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="guest_template" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Credit Memo Comment Email Template for Guest</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Credit Memo Comment Email Copy To</label> <comment>Comma-separated.</comment> <validate>validate-emails</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Send Credit Memo Comments Email Copy Method</label> <source_model>Magento\Config\Model\Config\Source\Email\Method</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> </section> @@ -437,9 +557,9 @@ </group> </section> <section id="dev"> - <group id="grid" translate="label" type="text" sortOrder="131" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="grid" translate="label" type="text" sortOrder="131" showInDefault="1"> <label>Grid Settings</label> - <field id="async_indexing" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="async_indexing" translate="label" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Asynchronous indexing</label> <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> <backend_model>Magento\Sales\Model\Config\Backend\Grid\AsyncIndexing</backend_model> diff --git a/app/code/Magento/Sales/etc/config.xml b/app/code/Magento/Sales/etc/config.xml index 2480da4ad214b..6918146d86337 100644 --- a/app/code/Magento/Sales/etc/config.xml +++ b/app/code/Magento/Sales/etc/config.xml @@ -8,6 +8,9 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> <default> <sales> + <general> + <hide_customer_ip>0</hide_customer_ip> + </general> <totals_sort> <discount>20</discount> <grand_total>100</grand_total> diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index f6618c9884d60..9f705c1a674c1 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -819,14 +819,6 @@ </argument> </arguments> </type> - <type name="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool"> - <arguments> - <argument name="appliers" xsi:type="array"> - <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item> - <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item> - </argument> - </arguments> - </type> <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory"> <arguments> <argument name="collections" xsi:type="array"> diff --git a/app/code/Magento/Sales/i18n/en_US.csv b/app/code/Magento/Sales/i18n/en_US.csv index f30315437533f..970df2770a524 100644 --- a/app/code/Magento/Sales/i18n/en_US.csv +++ b/app/code/Magento/Sales/i18n/en_US.csv @@ -799,3 +799,4 @@ Refunds,Refunds "Allow Zero GrandTotal","Allow Zero GrandTotal" "Could not save the shipment tracking","Could not save the shipment tracking" "Please enter a coupon code!","Please enter a coupon code!" +"Reorder is not available.","Reorder is not available." diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php index 1038f289eada2..ff905bf5cb9ff 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Conditions.php @@ -3,10 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\SalesRule\Block\Adminhtml\Promo\Quote\Edit\Tab; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Data\Form\Element\Fieldset; +use Magento\SalesRule\Model\Rule; +/** + * Block for rendering Conditions tab on Sales Rules creation page. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Conditions extends \Magento\Backend\Block\Widget\Form\Generic implements \Magento\Ui\Component\Layout\Tabs\TabInterface { @@ -33,8 +42,6 @@ class Conditions extends \Magento\Backend\Block\Widget\Form\Generic implements private $ruleFactory; /** - * Constructor - * * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Framework\Data\FormFactory $formFactory @@ -60,7 +67,8 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @codeCoverageIgnore */ public function getTabClass() @@ -69,7 +77,7 @@ public function getTabClass() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabUrl() { @@ -77,7 +85,7 @@ public function getTabUrl() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAjaxLoaded() { @@ -85,7 +93,7 @@ public function isAjaxLoaded() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabLabel() { @@ -93,7 +101,7 @@ public function getTabLabel() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabTitle() { @@ -101,7 +109,7 @@ public function getTabTitle() } /** - * {@inheritdoc} + * @inheritdoc */ public function canShowTab() { @@ -109,7 +117,7 @@ public function canShowTab() } /** - * {@inheritdoc} + * @inheritdoc */ public function isHidden() { @@ -133,7 +141,7 @@ protected function _prepareForm() /** * Handles addition of conditions tab to supplied form. * - * @param \Magento\SalesRule\Model\Rule $model + * @param Rule $model * @param string $fieldsetId * @param string $formName * @return \Magento\Framework\Data\Form diff --git a/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php b/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php index 53adcd268f81d..9f116cedcb340 100644 --- a/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php +++ b/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php @@ -53,6 +53,10 @@ public function collect(Quote $quote, ShippingAssignment $shippingAssignment, To $address->setShippingDiscountAmount(0); $address->setBaseShippingDiscountAmount(0); + if ($total->getShippingAmountForDiscount() !== null) { + $address->setShippingAmountForDiscount($total->getShippingAmountForDiscount()); + $address->setBaseShippingAmountForDiscount($total->getBaseShippingAmountForDiscount()); + } if ($address->getShippingAmount()) { $this->calculator->processShippingAmount($address); $total->addTotalAmount(DiscountCollector::COLLECTOR_TYPE_CODE, -$address->getShippingDiscountAmount()); diff --git a/app/code/Magento/SalesRule/Model/Quote/Discount.php b/app/code/Magento/SalesRule/Model/Quote/Discount.php index 69abac8309f90..a580a8f9d2eaa 100644 --- a/app/code/Magento/SalesRule/Model/Quote/Discount.php +++ b/app/code/Magento/SalesRule/Model/Quote/Discount.php @@ -85,6 +85,7 @@ public function __construct( * @param \Magento\Quote\Model\Quote\Address\Total $total * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function collect( \Magento\Quote\Model\Quote $quote, @@ -95,6 +96,11 @@ public function collect( $store = $this->storeManager->getStore($quote->getStoreId()); $address = $shippingAssignment->getShipping()->getAddress(); + + if ($quote->currentPaymentWasSet()) { + $address->setPaymentMethod($quote->getPayment()->getMethod()); + } + $this->calculator->reset($address); $items = $shippingAssignment->getItems(); diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php index 29cdf34c5a784..cf6301cb31a9c 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php @@ -65,6 +65,7 @@ public function loadAttributeOptions() 'base_subtotal' => __('Subtotal'), 'total_qty' => __('Total Items Quantity'), 'weight' => __('Total Weight'), + 'payment_method' => __('Payment Method'), 'shipping_method' => __('Shipping Method'), 'postcode' => __('Shipping Postcode'), 'region' => __('Shipping Region'), diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php index 1649dea80ef5b..6ade7a064e849 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php @@ -145,7 +145,8 @@ private function validateEntity($cond, \Magento\Framework\Model\AbstractModel $e private function retrieveValidateEntities($attributeScope, \Magento\Framework\Model\AbstractModel $entity) { if ($attributeScope === 'parent') { - $validateEntities = [$entity]; + $parentItem = $entity->getParentItem(); + $validateEntities = $parentItem ? [$parentItem] : [$entity]; } elseif ($attributeScope === 'children') { $validateEntities = $entity->getChildren() ?: [$entity]; } else { diff --git a/app/code/Magento/SalesRule/Model/Rule/RuleQuoteRecollectTotalsOnDemand.php b/app/code/Magento/SalesRule/Model/Rule/RuleQuoteRecollectTotalsOnDemand.php new file mode 100644 index 0000000000000..b983a59d79c5e --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Rule/RuleQuoteRecollectTotalsOnDemand.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\SalesRule\Model\Rule; + +use Magento\Quote\Model\ResourceModel\Quote; +use Magento\SalesRule\Model\Spi\RuleQuoteRecollectTotalsInterface; + +/** + * Forces related quotes to be recollected on demand. + */ +class RuleQuoteRecollectTotalsOnDemand implements RuleQuoteRecollectTotalsInterface +{ + /** + * @var Quote + */ + private $quoteResourceModel; + + /** + * Initializes dependencies + * + * @param Quote $quoteResourceModel + */ + public function __construct(Quote $quoteResourceModel) + { + $this->quoteResourceModel = $quoteResourceModel; + } + + /** + * Set "trigger_recollect" flag for active quotes which the given rule is applied to. + * + * @param int $ruleId + * @return void + */ + public function execute(int $ruleId): void + { + $this->quoteResourceModel->getConnection() + ->update( + $this->quoteResourceModel->getMainTable(), + ['trigger_recollect' => 1], + [ + 'is_active = ?' => 1, + 'FIND_IN_SET(?, applied_rule_ids)' => $ruleId + ] + ); + } +} diff --git a/app/code/Magento/SalesRule/Model/Spi/RuleQuoteRecollectTotalsInterface.php b/app/code/Magento/SalesRule/Model/Spi/RuleQuoteRecollectTotalsInterface.php new file mode 100644 index 0000000000000..e5562b22db6ca --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Spi/RuleQuoteRecollectTotalsInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\SalesRule\Model\Spi; + +/** + * Recollect totals for rule related quotes + */ +interface RuleQuoteRecollectTotalsInterface +{ + /** + * Recollect totals for rule related quotes. + * + * @param int $ruleId + * @return void + */ + public function execute(int $ruleId): void; +} diff --git a/app/code/Magento/SalesRule/Observer/RuleQuoteRecollectTotalsObserver.php b/app/code/Magento/SalesRule/Observer/RuleQuoteRecollectTotalsObserver.php new file mode 100644 index 0000000000000..c2d73d59c020b --- /dev/null +++ b/app/code/Magento/SalesRule/Observer/RuleQuoteRecollectTotalsObserver.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\SalesRule\Observer; + +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\SalesRule\Model\Rule; +use Magento\SalesRule\Model\Spi\RuleQuoteRecollectTotalsInterface; + +/** + * Forces related quotes to be recollected for inactive rule. + */ +class RuleQuoteRecollectTotalsObserver implements ObserverInterface +{ + /** + * @var RuleQuoteRecollectTotalsInterface + */ + private $recollectTotals; + + /** + * Initializes dependencies + * + * @param RuleQuoteRecollectTotalsInterface $recollectTotals + */ + public function __construct(RuleQuoteRecollectTotalsInterface $recollectTotals) + { + $this->recollectTotals = $recollectTotals; + } + + /** + * Forces related quotes to be recollected, if the rule was disabled or deleted. + * + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer): void + { + /** @var Rule $rule */ + $rule = $observer->getRule(); + if (!$rule->isObjectNew() && (!$rule->getIsActive() || $rule->isDeleted())) { + $this->recollectTotals->execute((int) $rule->getId()); + } + } +} diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml index 063409e9fc7ea..3cf96a8b3dc06 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontApplyDiscountCodeActionGroup.xml @@ -15,7 +15,7 @@ <click selector="{{DiscountSection.DiscountTab}}" stepKey="clickToAddDiscount"/> <fillField selector="{{DiscountSection.DiscountInput}}" userInput="{{discountCode}}" stepKey="fillFieldDiscountCode"/> <click selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="clickToApplyDiscount"/> - <waitForPageLoad stepKey="waitForDiscountToBeAdded"/> + <waitForElement selector="{{DiscountSection.DiscountVerificationMsg}}" time="30" stepKey="waitForDiscountToBeAdded"/> <see selector="{{DiscountSection.DiscountVerificationMsg}}" userInput="Your coupon was successfully applied" stepKey="assertDiscountApplyMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/SalesRule/etc/adminhtml/system.xml b/app/code/Magento/SalesRule/etc/adminhtml/system.xml index 0e30d1484c637..2655add5f8bbf 100644 --- a/app/code/Magento/SalesRule/etc/adminhtml/system.xml +++ b/app/code/Magento/SalesRule/etc/adminhtml/system.xml @@ -7,29 +7,29 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="promo" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0"> + <section id="promo" translate="label" type="text" sortOrder="400" showInDefault="1"> <class>separator-top</class> <label>Promotions</label> <tab>customer</tab> <resource>Magento_SalesRule::config_promo</resource> <group id="auto_generated_coupon_codes" translate="label" showInDefault="1" sortOrder="10"> <label>Auto Generated Specific Coupon Codes</label> - <field id="length" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="length" translate="label comment" type="text" sortOrder="10" showInDefault="1" canRestore="1"> <label>Code Length</label> <comment>Excluding prefix, suffix and separators.</comment> <frontend_class>validate-digits</frontend_class> </field> - <field id="format" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="format" translate="label" type="select" sortOrder="20" showInDefault="1" canRestore="1"> <label>Code Format</label> <source_model>Magento\SalesRule\Model\System\Config\Source\Coupon\Format</source_model> </field> - <field id="prefix" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="prefix" translate="label" type="text" sortOrder="30" showInDefault="1"> <label>Code Prefix</label> </field> - <field id="suffix" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="suffix" translate="label" type="text" sortOrder="40" showInDefault="1"> <label>Code Suffix</label> </field> - <field id="dash" translate="label comment" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="dash" translate="label comment" type="text" sortOrder="50" showInDefault="1"> <label>Dash Every X Characters</label> <comment>If empty no separation.</comment> <frontend_class>validate-digits</frontend_class> diff --git a/app/code/Magento/SalesRule/etc/di.xml b/app/code/Magento/SalesRule/etc/di.xml index 4ba67e2fa5871..0e5fe6d29aed6 100644 --- a/app/code/Magento/SalesRule/etc/di.xml +++ b/app/code/Magento/SalesRule/etc/di.xml @@ -13,7 +13,7 @@ <preference for="Magento\SalesRule\Api\Data\ConditionInterface" type="Magento\SalesRule\Model\Data\Condition" /> <preference for="Magento\SalesRule\Api\Data\RuleSearchResultInterface" - type="Magento\SalesRule\Model\RuleSearchResult" /> + type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\SalesRule\Api\Data\RuleLabelInterface" type="Magento\SalesRule\Model\Data\RuleLabel" /> <preference for="Magento\SalesRule\Api\Data\CouponInterface" @@ -23,7 +23,7 @@ <preference for="Magento\SalesRule\Model\Spi\CouponResourceInterface" type="Magento\SalesRule\Model\ResourceModel\Coupon" /> <preference for="Magento\SalesRule\Api\Data\CouponSearchResultInterface" - type="Magento\SalesRule\Model\CouponSearchResult" /> + type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\SalesRule\Api\Data\CouponGenerationSpecInterface" type="Magento\SalesRule\Model\Data\CouponGenerationSpec" /> <preference for="Magento\SalesRule\Api\Data\CouponMassDeleteResultInterface" @@ -34,6 +34,8 @@ type="Magento\SalesRule\Model\Data\RuleDiscount" /> <preference for="Magento\SalesRule\Api\Data\DiscountDataInterface" type="Magento\SalesRule\Model\Data\DiscountData" /> + <preference for="Magento\SalesRule\Model\Spi\RuleQuoteRecollectTotalsInterface" + type="\Magento\SalesRule\Model\Rule\RuleQuoteRecollectTotalsOnDemand" /> <type name="Magento\SalesRule\Helper\Coupon"> <arguments> <argument name="couponParameters" xsi:type="array"> diff --git a/app/code/Magento/SalesRule/etc/events.xml b/app/code/Magento/SalesRule/etc/events.xml index e421aafa96b55..5f899fb0cca5c 100644 --- a/app/code/Magento/SalesRule/etc/events.xml +++ b/app/code/Magento/SalesRule/etc/events.xml @@ -30,4 +30,10 @@ <event name="sales_quote_address_collect_totals_before"> <observer name="coupon_code_validation" instance="Magento\SalesRule\Observer\CouponCodeValidation" /> </event> + <event name="salesrule_rule_save_after"> + <observer name="salesrule_quote_recollect_totals_on_disabled" instance="\Magento\SalesRule\Observer\RuleQuoteRecollectTotalsObserver" /> + </event> + <event name="salesrule_rule_delete_after"> + <observer name="salesrule_quote_recollect_totals_on_delete" instance="\Magento\SalesRule\Observer\RuleQuoteRecollectTotalsObserver" /> + </event> </config> diff --git a/app/code/Magento/SalesRule/view/frontend/requirejs-config.js b/app/code/Magento/SalesRule/view/frontend/requirejs-config.js new file mode 100644 index 0000000000000..13b701c6fe65a --- /dev/null +++ b/app/code/Magento/SalesRule/view/frontend/requirejs-config.js @@ -0,0 +1,14 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + config: { + mixins: { + 'Magento_Checkout/js/action/select-payment-method': { + 'Magento_SalesRule/js/action/select-payment-method-mixin': true + } + } + } +}; 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 new file mode 100644 index 0000000000000..50d54d4e59789 --- /dev/null +++ b/app/code/Magento/SalesRule/view/frontend/web/js/action/select-payment-method-mixin.js @@ -0,0 +1,50 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'mage/utils/wrapper', + 'Magento_Checkout/js/model/quote', + 'Magento_SalesRule/js/model/payment/discount-messages', + 'Magento_Checkout/js/action/set-payment-information', + 'Magento_Checkout/js/action/get-totals', + 'Magento_SalesRule/js/model/coupon' +], function ($, wrapper, quote, messageContainer, setPaymentInformationAction, getTotalsAction, coupon) { + 'use strict'; + + return function (selectPaymentMethodAction) { + + return wrapper.wrap(selectPaymentMethodAction, function (originalSelectPaymentMethodAction, paymentMethod) { + + originalSelectPaymentMethodAction(paymentMethod); + + $.when( + setPaymentInformationAction( + messageContainer, + { + method: paymentMethod.method + } + ) + ).done( + function () { + var deferred = $.Deferred(), + + /** + * Update coupon form. + */ + updateCouponCallback = function () { + if (quote.totals() && !quote.totals()['coupon_code']) { + coupon.setCouponCode(''); + coupon.setIsApplied(false); + } + }; + + getTotalsAction([], deferred); + $.when(deferred).done(updateCouponCallback); + } + ); + }); + }; + +}); diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/model/coupon.js b/app/code/Magento/SalesRule/view/frontend/web/js/model/coupon.js new file mode 100644 index 0000000000000..1e3e057bbb401 --- /dev/null +++ b/app/code/Magento/SalesRule/view/frontend/web/js/model/coupon.js @@ -0,0 +1,49 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/** + * Coupon model. + */ +define([ + 'ko', + 'domReady!' +], function (ko) { + 'use strict'; + + var couponCode = ko.observable(null), + isApplied = ko.observable(null); + + return { + couponCode: couponCode, + isApplied: isApplied, + + /** + * @return {*} + */ + getCouponCode: function () { + return couponCode; + }, + + /** + * @return {Boolean} + */ + getIsApplied: function () { + return isApplied; + }, + + /** + * @param {*} couponCodeValue + */ + setCouponCode: function (couponCodeValue) { + couponCode(couponCodeValue); + }, + + /** + * @param {Boolean} isAppliedValue + */ + setIsApplied: function (isAppliedValue) { + isApplied(isAppliedValue); + } + }; +}); diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js b/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js index d2902d8863f3d..9c83cb7ba40ba 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js @@ -9,18 +9,19 @@ define([ 'uiComponent', 'Magento_Checkout/js/model/quote', 'Magento_SalesRule/js/action/set-coupon-code', - 'Magento_SalesRule/js/action/cancel-coupon' -], function ($, ko, Component, quote, setCouponCodeAction, cancelCouponAction) { + 'Magento_SalesRule/js/action/cancel-coupon', + 'Magento_SalesRule/js/model/coupon' +], function ($, ko, Component, quote, setCouponCodeAction, cancelCouponAction, coupon) { 'use strict'; var totals = quote.getTotals(), - couponCode = ko.observable(null), - isApplied; + couponCode = coupon.getCouponCode(), + isApplied = coupon.getIsApplied(); if (totals()) { couponCode(totals()['coupon_code']); } - isApplied = ko.observable(couponCode() != null); + isApplied(couponCode() != null); return Component.extend({ defaults: { diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml index 126d4612d7488..93a0f98f6f828 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml @@ -19,18 +19,8 @@ </annotations> <before> <!-- Login as admin --> - <comment userInput="Login as admin" stepKey="loginAsAdminComment"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Go to the catalog search term page --> - <comment userInput="Go to the catalog search term page" stepKey="goToSearchTermPageComment"/> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> - <!-- Delete all search terms --> - <comment userInput="Delete all search terms" stepKey="deleteAllSearchTermsComment"/> - <actionGroup ref="AdminDeleteAllSearchTermsActionGroup" stepKey="deleteAllSearchTerms"/> - <actionGroup ref="DeleteAllProductsUsingProductGridActionGroup" stepKey="deleteAllProducts"/> <!-- Create product with description --> - <comment userInput="Create product with description" stepKey="createProductWithDescriptionComment"/> <createData entity="SimpleProductWithDescription" stepKey="simpleProduct"/> <!-- Perform reindex and flush cache --> @@ -39,51 +29,40 @@ </before> <after> <!-- Delete created product --> - <comment userInput="Delete created product" stepKey="deleteCreatedProductComment"/> <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> <!-- Go to the catalog search term page --> - <comment userInput="Go to the catalog search term page" stepKey="goToSearchTermPageComment2"/> <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> <!-- Filter the search term --> - <comment userInput="Filter search term" stepKey="filterSearchTermComment"/> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> </actionGroup> <!-- Delete created below search terms --> - <comment userInput="Delete created below search terms" stepKey="deleteCreatedBelowSearchTermsComment"/> <actionGroup ref="AdminDeleteSearchTermActionGroup" stepKey="deleteSearchTerms"/> <actionGroup ref="logout" stepKey="logout"/> </after> <!-- Go to storefront home page --> - <comment userInput="Go to storefront home page" stepKey="goToStorefrontHomePageComment"/> <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> <!-- Storefront quick search by product name --> - <comment userInput="Storefront quick search by product name" stepKey="storefrontQuickSearchByProductNameComment"/> <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName"> <argument name="phrase" value="{{ApiProductDescription.value}}"/> </actionGroup> <!-- Verify search suggestions and select the suggestion from dropdown options --> - <comment userInput="Verify search suggestions and select the suggestion from dropdown options" stepKey="verifySearchSuggestionsComment"/> <actionGroup ref="StoreFrontSelectDropDownSearchSuggestionActionGroup" stepKey="seeDropDownSearchSuggestion"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> </actionGroup> <!-- Assert Product storefront main page --> - <comment userInput="See product name" stepKey="seeProductNameComment"/> <actionGroup ref="StorefrontAssertProductNameOnProductMainPageActionGroup" stepKey="seeProductName"> <argument name="productName" value="$$simpleProduct.name$$"/> </actionGroup> <!-- Go to the catalog search term page --> - <comment userInput="Go to the catalog search term page" stepKey="goToSearchTermPageComment3"/> <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> <!-- Filter the search term --> - <comment userInput="Filter search term" stepKey="filterSearchTermComment2"/> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> </actionGroup> <!-- Assert Search Term in grid --> - <comment userInput="Check is search term in grid or not" stepKey="isSearchTermInGridComment"/> <see stepKey="seeSearchTermInGrid" selector="{{AdminCatalogSearchTermIndexSection.gridRow}}" userInput="{{ApiProductDescription.value}}" /> <see selector="{{AdminCatalogSearchTermIndexSection.numberOfSearchTermResults}}" userInput="1" stepKey="seeNumberOfSearchTermResultInGrid"/> </test> diff --git a/app/code/Magento/Search/etc/adminhtml/system.xml b/app/code/Magento/Search/etc/adminhtml/system.xml index d3b7f64fbe0c4..8a539c9528e8e 100644 --- a/app/code/Magento/Search/etc/adminhtml/system.xml +++ b/app/code/Magento/Search/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <system> <section id="catalog"> <group id="search"> - <field id="engine" translate="label" type="select" sortOrder="19" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="engine" translate="label" type="select" sortOrder="19" showInDefault="1"> <label>Search Engine</label> <source_model>Magento\Search\Model\Adminhtml\System\Config\Source\Engine</source_model> </field> diff --git a/app/code/Magento/Search/etc/di.xml b/app/code/Magento/Search/etc/di.xml index 4aa211d23f0a2..382174f4327a2 100644 --- a/app/code/Magento/Search/etc/di.xml +++ b/app/code/Magento/Search/etc/di.xml @@ -45,14 +45,6 @@ <preference for="Magento\Search\Model\AutocompleteInterface" type="Magento\Search\Model\Autocomplete" /> <preference for="Magento\Search\Model\Autocomplete\ItemInterface" type="Magento\Search\Model\Autocomplete\Item" /> <preference for="Magento\Framework\Search\SearchEngineInterface" type="Magento\Search\Model\SearchEngine"/> - <type name="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool"> - <arguments> - <argument name="appliers" xsi:type="array"> - <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item> - <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item> - </argument> - </arguments> - </type> <!-- @api --> <virtualType name="Magento\Search\Model\ResourceModel\Synonyms\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult"> <arguments> diff --git a/app/code/Magento/Search/view/frontend/templates/form.mini.phtml b/app/code/Magento/Search/view/frontend/templates/form.mini.phtml index 0dd9c819c855a..35f3876599731 100644 --- a/app/code/Magento/Search/view/frontend/templates/form.mini.phtml +++ b/app/code/Magento/Search/view/frontend/templates/form.mini.phtml @@ -14,7 +14,8 @@ $helper = $this->helper(\Magento\Search\Helper\Data::class); <div class="block block-search"> <div class="block block-title"><strong><?= $block->escapeHtml(__('Search')) ?></strong></div> <div class="block block-content"> - <form class="form minisearch" id="search_mini_form" action="<?= $block->escapeUrl($helper->getResultUrl()) ?>" method="get"> + <form class="form minisearch" id="search_mini_form" + action="<?= $block->escapeUrl($helper->getResultUrl()) ?>" method="get"> <div class="field search"> <label class="label" for="search" data-role="minisearch-label"> <span><?= $block->escapeHtml(__('Search')) ?></span> @@ -29,7 +30,7 @@ $helper = $this->helper(\Magento\Search\Helper\Data::class); }' type="text" name="<?= $block->escapeHtmlAttr($helper->getQueryParamName()) ?>" - value="<?= $block->escapeHtmlAttr($helper->getEscapedQueryText()) ?>" + value="<?= /* @noEscape */ $helper->getEscapedQueryText() ?>" placeholder="<?= $block->escapeHtmlAttr(__('Search entire store here...')) ?>" class="input-text" maxlength="<?= $block->escapeHtmlAttr($helper->getMaxQueryLength()) ?>" diff --git a/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordComplexityTest.xml b/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordComplexityTest.xml index 74a9c68cb2f79..d7151aff22fa7 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordComplexityTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordComplexityTest.xml @@ -15,6 +15,7 @@ <title value="Notify the customer if password complexity does not match the requirements"/> <description value="Notify the customer if password complexity does not match the requirements"/> <testCaseId value="MC-14368"/> + <severity value="CRITICAL"/> <group value="security"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordLengthTest.xml b/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordLengthTest.xml index a10059d0603c5..298b4de11f9ca 100644 --- a/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordLengthTest.xml +++ b/app/code/Magento/Security/Test/Mftf/Test/NewCustomerPasswordLengthTest.xml @@ -15,6 +15,7 @@ <title value="Notify the customer if password length does not match the requirements"/> <description value="Notify the customer if password length does not match the requirements"/> <testCaseId value="MC-14367"/> + <severity value="CRITICAL"/> <group value="security"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/SendFriend/etc/adminhtml/system.xml b/app/code/Magento/SendFriend/etc/adminhtml/system.xml index 5cace4bcf92db..6360c42655a09 100644 --- a/app/code/Magento/SendFriend/etc/adminhtml/system.xml +++ b/app/code/Magento/SendFriend/etc/adminhtml/system.xml @@ -24,22 +24,37 @@ <label>Select Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="allow_guest" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Allow for Guests</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="max_recipients" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Max Recipients</label> <frontend_class>validate-digits</frontend_class> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="max_per_hour" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Max Products Sent in 1 Hour</label> <frontend_class>validate-digits</frontend_class> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="check_by" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Limit Sending By</label> <source_model>Magento\SendFriend\Model\Source\Checktype</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> </section> diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminGoToShipmentTabActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminGoToShipmentTabActionGroup.xml new file mode 100644 index 0000000000000..ff2522d110eeb --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminGoToShipmentTabActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminGoToShipmentTabActionGroup"> + <click selector="{{AdminOrderDetailsOrderViewSection.shipments}}" stepKey="clickOrderShipmentsTab"/> + <waitForLoadingMaskToDisappear stepKey="waitForShipmentTabLoad" after="clickOrderShipmentsTab"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentCreateShippingLabelActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentCreateShippingLabelActionGroup.xml new file mode 100644 index 0000000000000..663b6088395c4 --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentCreateShippingLabelActionGroup.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="AdminShipmentCreateShippingLabelActionGroup"> + <arguments> + <argument name="productName" type="string" defaultValue="{{SimpleProduct.name}}"/> + </arguments> + <waitForElementVisible selector="{{AdminShipmentCreatePackageMainSection.addProductsToPackage}}" stepKey="waitForAddProductElement"/> + <click selector="{{AdminShipmentCreatePackageMainSection.addProductsToPackage}}" stepKey="clickAddProducts"/> + <waitForElementVisible selector="{{AdminShipmentCreatePackageProductGridSection.concreteProductCheckbox('productName')}}" stepKey="waitForProductBeVisible"/> + <checkOption selector="{{AdminShipmentCreatePackageProductGridSection.concreteProductCheckbox('productName')}}" stepKey="checkProductCheckbox"/> + <waitForElementVisible selector="{{AdminShipmentCreatePackageMainSection.addSelectedProductToPackage}}" stepKey="waitForAddSelectedProductElement"/> + <click selector="{{AdminShipmentCreatePackageMainSection.addSelectedProductToPackage}}" stepKey="clickAddSelectedProduct"/> + <waitForElementNotVisible selector="{{AdminShipmentCreatePackageMainSection.saveButtonDisabled}}" stepKey="waitForBeEnabled"/> + <click selector="{{AdminShipmentCreatePackageMainSection.save}}" stepKey="clickSave"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear"/> + <waitForPageLoad stepKey="waitForSaving"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created. You created the shipping label." stepKey="seeShipmentCreateSuccess"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/GoToShipmentIntoOrderActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/GoToShipmentIntoOrderActionGroup.xml new file mode 100644 index 0000000000000..7a4280cf7d6d5 --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/GoToShipmentIntoOrderActionGroup.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="GoToShipmentIntoOrderActionGroup"> + <annotations> + <description>Clicks on the 'Ship' button on the view Admin Order page. Validates that the URL and Page Title are present and correct.</description> + </annotations> + + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> + <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Shipment" stepKey="seePageNameNewInvoicePage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/SeeProductInShipmentItemsActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/SeeProductInShipmentItemsActionGroup.xml new file mode 100644 index 0000000000000..8ba2eff4def0f --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/SeeProductInShipmentItemsActionGroup.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="SeeProductInShipmentItemsActionGroup"> + <annotations> + <description>Validates that the provided Product is present and correct on the view Admin Order Shipment page under the 'Items Shipped' section.</description> + </annotations> + <arguments> + <argument name="product"/> + </arguments> + + <see selector="{{AdminShipmentItemsSection.skuColumn}}" userInput="{{product.sku}}" stepKey="seeProductSkuInGrid"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/SubmitShipmentIntoOrderActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/SubmitShipmentIntoOrderActionGroup.xml new file mode 100644 index 0000000000000..8592566ff83b7 --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/SubmitShipmentIntoOrderActionGroup.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="SubmitShipmentIntoOrderActionGroup"> + <annotations> + <description>Clicks on the 'Submit Shipment' button on the view Admin Order Shipment page. Validates that the URL and Page Title are present and correct.</description> + </annotations> + + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> + <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPageShipping"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created." stepKey="seeShipmentCreateSuccess"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/VerifyBasicShipmentInformationActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/VerifyBasicShipmentInformationActionGroup.xml new file mode 100644 index 0000000000000..d76ba42003b81 --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/VerifyBasicShipmentInformationActionGroup.xml @@ -0,0 +1,34 @@ +<?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="VerifyBasicShipmentInformationActionGroup"> + <annotations> + <description>Validates that the provided Customer, Shipping Address, Billing Address and Customer Group are present and correct on the view Admin Order page.</description> + </annotations> + <arguments> + <argument name="customer" defaultValue=""/> + <argument name="shippingAddress" defaultValue=""/> + <argument name="billingAddress" defaultValue=""/> + <argument name="customerGroup" defaultValue="GeneralCustomerGroup"/> + </arguments> + + <see selector="{{AdminShipmentOrderInformationSection.customerName}}" userInput="{{customer.firstname}}" stepKey="seeCustomerName"/> + <see selector="{{AdminShipmentOrderInformationSection.customerEmail}}" userInput="{{customer.email}}" stepKey="seeCustomerEmail"/> + <see selector="{{AdminShipmentOrderInformationSection.customerGroup}}" userInput="{{customerGroup.code}}" stepKey="seeCustomerGroup"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{billingAddress.street[0]}}" stepKey="seeBillingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{billingAddress.city}}" stepKey="seeBillingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{billingAddress.country_id}}" stepKey="seeBillingAddressCountry"/> + <see selector="{{AdminShipmentAddressInformationSection.billingAddress}}" userInput="{{billingAddress.postcode}}" stepKey="seeBillingAddressPostcode"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.street[0]}}" stepKey="seeShippingAddressStreet"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.city}}" stepKey="seeShippingAddressCity"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.country_id}}" stepKey="seeShippingAddressCountry"/> + <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.postcode}}" stepKey="seeShippingAddressPostcode"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml similarity index 67% rename from app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentActionGroup.xml rename to app/code/Magento/Shipping/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml index 631db885ab3d9..28160ae9e4ebb 100644 --- a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentActionGroup.xml +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml @@ -7,7 +7,36 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Temporary file to pass CI verification --> + <actionGroup name="goToShipmentIntoOrder"> + <annotations> + <description>Clicks on the 'Ship' button on the view Admin Order page. Validates that the URL and Page Title are present and correct.</description> + </annotations> + + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> + <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Shipment" stepKey="seePageNameNewInvoicePage"/> + </actionGroup> + <actionGroup name="submitShipmentIntoOrder"> + <annotations> + <description>Clicks on the 'Submit Shipment' button on the view Admin Order Shipment page. Validates that the URL and Page Title are present and correct.</description> + </annotations> + + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> + <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPageShipping"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created." stepKey="seeShipmentCreateSuccess"/> + </actionGroup> + <actionGroup name="seeProductInShipmentItems"> + <annotations> + <description>Validates that the provided Product is present and correct on the view Admin Order Shipment page under the 'Items Shipped' section.</description> + </annotations> + <arguments> + <argument name="product"/> + </arguments> + + <see selector="{{AdminShipmentItemsSection.skuColumn}}" userInput="{{product.sku}}" stepKey="seeProductSkuInGrid"/> + </actionGroup> <actionGroup name="verifyBasicShipmentInformation"> <annotations> <description>Validates that the provided Customer, Shipping Address, Billing Address and Customer Group are present and correct on the view Admin Order page.</description> @@ -18,7 +47,7 @@ <argument name="billingAddress" defaultValue=""/> <argument name="customerGroup" defaultValue="GeneralCustomerGroup"/> </arguments> - + <see selector="{{AdminShipmentOrderInformationSection.customerName}}" userInput="{{customer.firstname}}" stepKey="seeCustomerName"/> <see selector="{{AdminShipmentOrderInformationSection.customerEmail}}" userInput="{{customer.email}}" stepKey="seeCustomerEmail"/> <see selector="{{AdminShipmentOrderInformationSection.customerGroup}}" userInput="{{customerGroup.code}}" stepKey="seeCustomerGroup"/> @@ -31,55 +60,4 @@ <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.country_id}}" stepKey="seeShippingAddressCountry"/> <see selector="{{AdminShipmentAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.postcode}}" stepKey="seeShippingAddressPostcode"/> </actionGroup> - - <actionGroup name="seeProductInShipmentItems"> - <annotations> - <description>Validates that the provided Product is present and correct on the view Admin Order Shipment page under the 'Items Shipped' section.</description> - </annotations> - <arguments> - <argument name="product"/> - </arguments> - - <see selector="{{AdminShipmentItemsSection.skuColumn}}" userInput="{{product.sku}}" stepKey="seeProductSkuInGrid"/> - </actionGroup> - - <actionGroup name="goToShipmentIntoOrder"> - <annotations> - <description>Clicks on the 'Ship' button on the view Admin Order page. Validates that the URL and Page Title are present and correct.</description> - </annotations> - - <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> - <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl"/> - <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Shipment" stepKey="seePageNameNewInvoicePage"/> - </actionGroup> - - <actionGroup name="submitShipmentIntoOrder"> - <annotations> - <description>Clicks on the 'Submit Shipment' button on the view Admin Order Shipment page. Validates that the URL and Page Title are present and correct.</description> - </annotations> - - <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> - <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPageShipping"/> - <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created." stepKey="seeShipmentCreateSuccess"/> - </actionGroup> - <actionGroup name="AdminShipmentCreateShippingLabelActionGroup"> - <arguments> - <argument name="productName" type="string" defaultValue="{{SimpleProduct.name}}"/> - </arguments> - <waitForElementVisible selector="{{AdminShipmentCreatePackageMainSection.addProductsToPackage}}" stepKey="waitForAddProductElement"/> - <click selector="{{AdminShipmentCreatePackageMainSection.addProductsToPackage}}" stepKey="clickAddProducts"/> - <waitForElementVisible selector="{{AdminShipmentCreatePackageProductGridSection.concreteProductCheckbox('productName')}}" stepKey="waitForProductBeVisible"/> - <checkOption selector="{{AdminShipmentCreatePackageProductGridSection.concreteProductCheckbox('productName')}}" stepKey="checkProductCheckbox"/> - <waitForElementVisible selector="{{AdminShipmentCreatePackageMainSection.addSelectedProductToPackage}}" stepKey="waitForAddSelectedProductElement"/> - <click selector="{{AdminShipmentCreatePackageMainSection.addSelectedProductToPackage}}" stepKey="clickAddSelectedProduct"/> - <waitForElementNotVisible selector="{{AdminShipmentCreatePackageMainSection.saveButtonDisabled}}" stepKey="waitForBeEnabled"/> - <click selector="{{AdminShipmentCreatePackageMainSection.save}}" stepKey="clickSave"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear"/> - <waitForPageLoad stepKey="waitForSaving"/> - <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The shipment has been created. You created the shipping label." stepKey="seeShipmentCreateSuccess"/> - </actionGroup> - <actionGroup name="AdminGoToShipmentTabActionGroup"> - <click selector="{{AdminOrderDetailsOrderViewSection.shipments}}" stepKey="clickOrderShipmentsTab"/> - <waitForLoadingMaskToDisappear stepKey="waitForShipmentTabLoad" after="clickOrderShipmentsTab"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml index a0edf4e13a80f..f1126e52359a0 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -14,7 +14,7 @@ <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl" after="clickShipAction"/> <see selector="{{AdminShipmentOrderInformationSection.orderStatus}}" userInput="Processing" stepKey="seeShipmentOrderStatus" after="seeOrderShipmentUrl"/> - <actionGroup ref="verifyBasicShipmentInformation" stepKey="checkBasicShipmentOrderInfo" after="seeShipmentOrderStatus"> + <actionGroup ref="VerifyBasicShipmentInformationActionGroup" stepKey="checkBasicShipmentOrderInfo" after="seeShipmentOrderStatus"> <argument name="customer" value="Simple_US_Customer"/> <argument name="shippingAddress" value="US_Address_TX"/> <argument name="billingAddress" value="US_Address_TX"/> @@ -34,17 +34,17 @@ <see selector="{{AdminOrderShipmentsTabSection.gridRow('1')}}" userInput="{{Simple_US_Customer.firstname}}" stepKey="seeOrderShipmentInTabGrid" after="waitForShipmentTabLoad"/> <click selector="{{AdminOrderShipmentsTabSection.viewGridRow('1')}}" stepKey="clickRowToViewShipment" after="seeOrderShipmentInTabGrid"/> <see selector="{{AdminShipmentOrderInformationSection.orderId}}" userInput="$getOrderId" stepKey="seeOrderIdOnShipment" after="clickRowToViewShipment"/> - <actionGroup ref="verifyBasicShipmentInformation" stepKey="checkShipmentOrderInformation" after="seeOrderIdOnShipment"> + <actionGroup ref="VerifyBasicShipmentInformationActionGroup" stepKey="checkShipmentOrderInformation" after="seeOrderIdOnShipment"> <argument name="customer" value="Simple_US_Customer"/> <argument name="shippingAddress" value="US_Address_TX"/> <argument name="billingAddress" value="US_Address_TX"/> </actionGroup> - <actionGroup ref="seeProductInShipmentItems" stepKey="seeSimpleProductInShipmentItems" after="checkShipmentOrderInformation"> + <actionGroup ref="SeeProductInShipmentItemsActionGroup" stepKey="seeSimpleProductInShipmentItems" after="checkShipmentOrderInformation"> <argument name="product" value="SimpleProduct"/> </actionGroup> - <actionGroup ref="seeProductInShipmentItems" stepKey="seeConfigurableProductInShipmentItems" after="seeSimpleProductInShipmentItems"> + <actionGroup ref="SeeProductInShipmentItemsActionGroup" stepKey="seeConfigurableProductInShipmentItems" after="seeSimpleProductInShipmentItems"> <argument name="product" value="BaseConfigurableProduct"/> </actionGroup> <click selector="{{AdminShipmentOrderInformationSection.orderId}}" stepKey="clickOrderIdOnShipment" after="seeConfigurableProductInShipmentItems"/> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Shipping/etc/adminhtml/system.xml b/app/code/Magento/Shipping/etc/adminhtml/system.xml index b0d38bc0d61a1..29862bdcfc8b1 100644 --- a/app/code/Magento/Shipping/etc/adminhtml/system.xml +++ b/app/code/Magento/Shipping/etc/adminhtml/system.xml @@ -11,32 +11,32 @@ <label>Shipping Settings</label> <tab>sales</tab> <resource>Magento_Shipping::config_shipping</resource> - <group id="origin" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="origin" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1"> <label>Origin</label> - <field id="country_id" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="country_id" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Country</label> <frontend_class>countries</frontend_class> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> </field> - <field id="region_id" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="region_id" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Region/State</label> </field> - <field id="postcode" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="postcode" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>ZIP/Postal Code</label> </field> - <field id="city" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="city" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1"> <label>City</label> </field> - <field id="street_line1" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="street_line1" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>Street Address</label> </field> - <field id="street_line2" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="street_line2" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1"> <label>Street Address Line 2</label> </field> </group> <group id="shipping_policy" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Shipping Policy Parameters</label> - <field id="enable_shipping_policy" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="enable_shipping_policy" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Apply custom Shipping Policy</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Shipping/etc/config.xml b/app/code/Magento/Shipping/etc/config.xml index 4c2e018191b59..e1a71ef9741bb 100644 --- a/app/code/Magento/Shipping/etc/config.xml +++ b/app/code/Magento/Shipping/etc/config.xml @@ -13,6 +13,9 @@ <postcode>90034</postcode> <region_id>12</region_id> </origin> + <shipping_policy> + <enable_shipping_policy>0</enable_shipping_policy> + </shipping_policy> </shipping> </default> </config> diff --git a/app/code/Magento/Signifyd/Test/Mftf/Test/AdminSignifydConfigDependentOnActiveFieldTest.xml b/app/code/Magento/Signifyd/Test/Mftf/Test/AdminSignifydConfigDependentOnActiveFieldTest.xml index dcae0c4091ba6..b8fb91c4dcd99 100644 --- a/app/code/Magento/Signifyd/Test/Mftf/Test/AdminSignifydConfigDependentOnActiveFieldTest.xml +++ b/app/code/Magento/Signifyd/Test/Mftf/Test/AdminSignifydConfigDependentOnActiveFieldTest.xml @@ -10,6 +10,7 @@ <test name="AdminSignifydConfigDependentOnActiveFieldTest"> <annotations> <features value="Signifyd"/> + <stories value="Signify ID Settings"/> <title value="Signifyd config dependent on active field" /> <description value="Signifyd system configs dependent by Enable this Solution field."/> <severity value="MINOR"/> diff --git a/app/code/Magento/Signifyd/etc/adminhtml/system.xml b/app/code/Magento/Signifyd/etc/adminhtml/system.xml index 272ce78aec2e5..182a67e4e1f35 100644 --- a/app/code/Magento/Signifyd/etc/adminhtml/system.xml +++ b/app/code/Magento/Signifyd/etc/adminhtml/system.xml @@ -11,9 +11,9 @@ <label>Fraud Protection</label> <tab>sales</tab> <resource>Magento_Sales::fraud_protection</resource> - <group id="signifyd" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="signifyd" type="text" sortOrder="10" showInDefault="1" showInWebsite="1"> <fieldset_css>signifyd-logo-header</fieldset_css> - <group id="about" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="about" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1"> <frontend_model>Magento\Signifyd\Block\Adminhtml\System\Config\Fieldset\Info</frontend_model> <fieldset_css>signifyd-about-header</fieldset_css> <label><![CDATA[Protect your store from fraud with Guaranteed Fraud Protection by Signifyd.]]></label> @@ -26,17 +26,17 @@ </comment> <more_url>https://www.signifyd.com/magento-guaranteed-fraud-protection</more_url> </group> - <group id="config" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="config" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1"> <fieldset_css>signifyd-about-header</fieldset_css> <label>Configuration</label> <comment><![CDATA[<a href="https://www.signifyd.com/resources/manual/magento-2/signifyd-on-magento-integration-guide/" target="_blank">View our setup guide</a> for step-by-step instructions on how to integrate Signifyd with Magento.<br />For support contact <a href="mailto:support@signifyd.com">support@signifyd.com</a>.]]> </comment> - <field id="active" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="active" translate="label" type="select" showInDefault="1" showInWebsite="1"> <label>Enable this Solution</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>fraud_protection/signifyd/active</config_path> </field> - <field id="api_key" translate="label" type="obscure" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="api_key" translate="label" type="obscure" sortOrder="20" showInDefault="1" showInWebsite="1"> <label>API Key</label> <comment><![CDATA[Your API key can be found on the <a href="http://signifyd.com/settings" target="_blank">settings page</a> in the Signifyd console.]]></comment> <config_path>fraud_protection/signifyd/api_key</config_path> @@ -45,7 +45,7 @@ <field id="active">1</field> </depends> </field> - <field id="api_url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="api_url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>API URL</label> <config_path>fraud_protection/signifyd/api_url</config_path> <comment>Don’t change unless asked to do so.</comment> @@ -53,7 +53,7 @@ <field id="active">1</field> </depends> </field> - <field id="debug" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>fraud_protection/signifyd/debug</config_path> @@ -61,7 +61,7 @@ <field id="active">1</field> </depends> </field> - <field id="webhook_url" translate="label comment" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="webhook_url" translate="label comment" type="text" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>Webhook URL</label> <comment><![CDATA[Your webhook URL will be used to <a href="https://app.signifyd.com/settings/notifications" target="_blank">configure</a> a guarantee completed webhook in Signifyd. Webhooks are used to sync Signifyd`s guarantee decisions back to Magento.]]></comment> <attribute type="handler_url">signifyd/webhooks/handler</attribute> diff --git a/app/code/Magento/Sitemap/etc/adminhtml/system.xml b/app/code/Magento/Sitemap/etc/adminhtml/system.xml index c3c9c85027354..57c426c68e83f 100644 --- a/app/code/Magento/Sitemap/etc/adminhtml/system.xml +++ b/app/code/Magento/Sitemap/etc/adminhtml/system.xml @@ -51,7 +51,7 @@ <comment>Valid values range from 0.0 to 1.0.</comment> </field> </group> - <group id="generate" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="generate" translate="label" type="text" sortOrder="4" showInDefault="1"> <label>Generation Settings</label> <field id="enabled" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enabled</label> @@ -60,23 +60,38 @@ <field id="error_email" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Error Email Recipient</label> <validate>validate-email</validate> + <depends> + <field id="enabled">1</field> + </depends> </field> - <field id="error_email_identity" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="error_email_identity" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Error Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> - <field id="error_email_template" translate="label comment" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="error_email_template" translate="label comment" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Error Email Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="frequency" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Frequency</label> <source_model>Magento\Cron\Model\Config\Source\Frequency</source_model> <backend_model>Magento\Cron\Model\Config\Backend\Sitemap</backend_model> + <depends> + <field id="enabled">1</field> + </depends> </field> <field id="time" translate="label" type="time" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Start Time</label> + <depends> + <field id="enabled">1</field> + </depends> </field> </group> <group id="limit" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> diff --git a/app/code/Magento/Store/Controller/Store/Redirect.php b/app/code/Magento/Store/Controller/Store/Redirect.php index 5d61275e72a28..8f63a43f5db7c 100644 --- a/app/code/Magento/Store/Controller/Store/Redirect.php +++ b/app/code/Magento/Store/Controller/Store/Redirect.php @@ -11,11 +11,7 @@ use Magento\Framework\App\Action\Context; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Action\HttpPostActionInterface; -use Magento\Framework\App\ResponseInterface; -use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Session\Generic as Session; -use Magento\Framework\Session\SidResolverInterface; use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Api\StoreResolverInterface; use Magento\Store\Model\Store; @@ -23,7 +19,7 @@ use Magento\Store\Model\StoreSwitcher\HashGenerator; /** - * Builds correct url to target store and performs redirect. + * Builds correct url to target store (group) and performs redirect. */ class Redirect extends Action implements HttpGetActionInterface, HttpPostActionInterface { @@ -37,16 +33,6 @@ class Redirect extends Action implements HttpGetActionInterface, HttpPostActionI */ private $storeResolver; - /** - * @var SidResolverInterface - */ - private $sidResolver; - - /** - * @var Session - */ - private $session; - /** * @var HashGenerator */ @@ -56,30 +42,28 @@ class Redirect extends Action implements HttpGetActionInterface, HttpPostActionI * @param Context $context * @param StoreRepositoryInterface $storeRepository * @param StoreResolverInterface $storeResolver - * @param Session $session - * @param SidResolverInterface $sidResolver + * @param \Magento\Framework\Session\Generic $session + * @param \Magento\Framework\Session\SidResolverInterface $sidResolver * @param HashGenerator $hashGenerator + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Context $context, StoreRepositoryInterface $storeRepository, StoreResolverInterface $storeResolver, - Session $session, - SidResolverInterface $sidResolver, + \Magento\Framework\Session\Generic $session, + \Magento\Framework\Session\SidResolverInterface $sidResolver, HashGenerator $hashGenerator ) { parent::__construct($context); $this->storeRepository = $storeRepository; $this->storeResolver = $storeResolver; - $this->session = $session; - $this->sidResolver = $sidResolver; $this->hashGenerator = $hashGenerator; } /** - * Performs store redirect + * @inheritDoc * - * @return ResponseInterface|ResultInterface * @throws NoSuchEntityException */ public function execute() @@ -113,12 +97,6 @@ public function execute() \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => $encodedUrl, ]; - if ($this->sidResolver->getUseSessionInUrl()) { - // allow customers to stay logged in during store switching - $sidName = $this->sidResolver->getSessionIdQueryParam($this->session); - $query[$sidName] = $this->session->getSessionId(); - } - $customerHash = $this->hashGenerator->generateHash($fromStore); $query = array_merge($query, $customerHash); diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index 5eda6f4a9b57d..68df88622d095 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -278,6 +278,7 @@ class Store extends AbstractExtensibleModel implements /** * @var \Magento\Framework\Session\SidResolverInterface + * @deprecated Not used anymore. */ protected $_sidResolver; @@ -1199,7 +1200,6 @@ public function isDefault() */ public function getCurrentUrl($fromStore = true) { - $sidQueryParam = $this->_sidResolver->getSessionIdQueryParam($this->_getSession()); $requestString = $this->_url->escape(ltrim($this->_request->getRequestString(), '/')); $storeUrl = $this->getUrl('', ['_secure' => $this->_storeManager->getStore()->isCurrentlySecure()]); @@ -1218,12 +1218,6 @@ public function getCurrentUrl($fromStore = true) } $currQuery = $this->_request->getQueryValue(); - if (isset($currQuery[$sidQueryParam]) - && !empty($currQuery[$sidQueryParam]) - && $this->_getSession()->getSessionIdForHost($storeUrl) != $currQuery[$sidQueryParam] - ) { - unset($currQuery[$sidQueryParam]); - } foreach ($currQuery as $key => $value) { $storeParsedQuery[$key] = $value; diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml index 569952019b29b..b24420061db65 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml @@ -11,6 +11,7 @@ <test name="AdminSetUpWatermarkForSwatchImageTest"> <annotations> <features value="Swatches"/> + <stories value="Product Swatches Images"/> <title value="Possibility to set up watermark for a swatch image type"/> <description value="Possibility to set up watermark for a swatch image type"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/text.js b/app/code/Magento/Swatches/view/adminhtml/web/js/text.js index 2bb1672d9e8c8..48a14c14bdf3a 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/text.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/text.js @@ -13,7 +13,8 @@ define([ 'mage/template', 'uiRegistry', 'jquery/ui', - 'prototype' + 'prototype', + 'validation' ], function (jQuery, mageTemplate, rg) { 'use strict'; diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/visual.js b/app/code/Magento/Swatches/view/adminhtml/web/js/visual.js index b91fea59229cd..19307432c4122 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/visual.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/visual.js @@ -14,7 +14,8 @@ define([ 'uiRegistry', 'jquery/colorpicker/js/colorpicker', 'prototype', - 'jquery/ui' + 'jquery/ui', + 'validation' ], function (jQuery, mageTemplate, rg) { 'use strict'; diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php index c70c715d32c1b..55a56bd857a58 100644 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php @@ -593,8 +593,11 @@ protected function processProductItems( $total->setSubtotalInclTax($subtotalInclTax); $total->setBaseSubtotalTotalInclTax($baseSubtotalInclTax); $total->setBaseSubtotalInclTax($baseSubtotalInclTax); - $shippingAssignment->getShipping()->getAddress()->setBaseSubtotalTotalInclTax($baseSubtotalInclTax); - $shippingAssignment->getShipping()->getAddress()->setBaseTaxAmount($baseTax); + $address = $shippingAssignment->getShipping()->getAddress(); + $address->setBaseTaxAmount($baseTax); + $address->setBaseSubtotalTotalInclTax($baseSubtotalInclTax); + $address->setSubtotal($total->getSubtotal()); + $address->setBaseSubtotal($total->getBaseSubtotal()); return $this; } diff --git a/app/code/Magento/Tax/etc/adminhtml/system.xml b/app/code/Magento/Tax/etc/adminhtml/system.xml index 7fc1744b8e27e..c1e1286041ce6 100644 --- a/app/code/Magento/Tax/etc/adminhtml/system.xml +++ b/app/code/Magento/Tax/etc/adminhtml/system.xml @@ -14,59 +14,59 @@ <resource>Magento_Tax::config_tax</resource> <group id="classes" translate="label" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Tax Classes</label> - <field id="shipping_tax_class" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="shipping_tax_class" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Tax Class for Shipping</label> <source_model>Magento\Tax\Model\TaxClass\Source\Product</source_model> </field> - <field id="default_product_tax_class" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="default_product_tax_class" translate="label" type="select" sortOrder="20" showInDefault="1" canRestore="1"> <label>Default Tax Class for Product</label> <source_model>Magento\Tax\Model\TaxClass\Source\Product</source_model> <backend_model>Magento\Tax\Model\Config\TaxClass</backend_model> </field> - <field id="default_customer_tax_class" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="default_customer_tax_class" translate="label" type="select" sortOrder="30" showInDefault="1" canRestore="1"> <label>Default Tax Class for Customer</label> <source_model>Magento\Tax\Model\TaxClass\Source\Customer</source_model> </field> </group> <group id="calculation" translate="label" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Calculation Settings</label> - <field id="algorithm" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="algorithm" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Tax Calculation Method Based On</label> <source_model>Magento\Tax\Model\System\Config\Source\Algorithm</source_model> </field> - <field id="based_on" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="based_on" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Tax Calculation Based On</label> <source_model>Magento\Tax\Model\Config\Source\Basedon</source_model> <backend_model>Magento\Tax\Model\Config\Notification</backend_model> </field> - <field id="price_includes_tax" translate="label comment" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="price_includes_tax" translate="label comment" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Catalog Prices</label> <comment>This sets whether catalog prices entered from Magento Admin include tax.</comment> <backend_model>Magento\Tax\Model\Config\Price\IncludePrice</backend_model> <source_model>Magento\Tax\Model\System\Config\Source\PriceType</source_model> </field> - <field id="shipping_includes_tax" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="shipping_includes_tax" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Shipping Prices</label> <comment>This sets whether shipping amounts entered from Magento Admin or obtained from gateways include tax.</comment> <backend_model>Magento\Tax\Model\Config\Price\IncludePrice</backend_model> <source_model>Magento\Tax\Model\System\Config\Source\PriceType</source_model> </field> - <field id="apply_after_discount" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="apply_after_discount" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Apply Customer Tax</label> <source_model>Magento\Tax\Model\System\Config\Source\Apply</source_model> <backend_model>Magento\Tax\Model\Config\Notification</backend_model> </field> - <field id="discount_tax" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="discount_tax" translate="label comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Apply Discount On Prices</label> <source_model>Magento\Tax\Model\System\Config\Source\PriceType</source_model> <backend_model>Magento\Tax\Model\Config\Notification</backend_model> <comment>Warning: To apply the discount on prices including tax and apply the tax after discount, set Catalog Prices to “Including Tax”.</comment> </field> - <field id="apply_tax_on" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="apply_tax_on" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Apply Tax On</label> <source_model>Magento\Tax\Model\Config\Source\Apply\On</source_model> </field> - <field id="cross_border_trade_enabled" translate="label comment" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="cross_border_trade_enabled" translate="label comment" type="select" sortOrder="70" showInDefault="1" showInWebsite="1"> <label>Enable Cross Border Trade</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>When catalog price includes tax, enable this setting to fix the price no matter what the customer's tax rate.</comment> diff --git a/app/code/Magento/Theme/Block/Html/Title.php b/app/code/Magento/Theme/Block/Html/Title.php index ea0feb403180c..9059afe19ab05 100644 --- a/app/code/Magento/Theme/Block/Html/Title.php +++ b/app/code/Magento/Theme/Block/Html/Title.php @@ -5,7 +5,9 @@ */ namespace Magento\Theme\Block\Html; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\View\Element\Template; +use Magento\Store\Model\ScopeInterface; /** * Html page title block @@ -19,6 +21,16 @@ */ class Title extends Template { + /** + * Config path to 'Translate Title' header settings + */ + private const XML_PATH_HEADER_TRANSLATE_TITLE = 'design/header/translate_title'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * Own page title to display on the page * @@ -26,6 +38,22 @@ class Title extends Template */ protected $pageTitle; + /** + * Constructor + * + * @param Template\Context $context + * @param ScopeConfigInterface $scopeConfig + * @param array $data + */ + public function __construct( + Template\Context $context, + ScopeConfigInterface $scopeConfig, + array $data = [] + ) { + parent::__construct($context, $data); + $this->scopeConfig = $scopeConfig; + } + /** * Provide own page title or pick it from Head Block * @@ -36,7 +64,10 @@ public function getPageTitle() if (!empty($this->pageTitle)) { return $this->pageTitle; } - return __($this->pageConfig->getTitle()->getShort()); + + $pageTitle = $this->pageConfig->getTitle()->getShort(); + + return $this->shouldTranslateTitle() ? __($pageTitle) : $pageTitle; } /** @@ -46,10 +77,9 @@ public function getPageTitle() */ public function getPageHeading() { - if (!empty($this->pageTitle)) { - return __($this->pageTitle); - } - return __($this->pageConfig->getTitle()->getShortHeading()); + $pageTitle = !empty($this->pageTitle) ? $this->pageTitle : $this->pageConfig->getTitle()->getShortHeading(); + + return $this->shouldTranslateTitle() ? __($pageTitle) : $pageTitle; } /** @@ -62,4 +92,17 @@ public function setPageTitle($pageTitle) { $this->pageTitle = $pageTitle; } + + /** + * Check if page title should be translated + * + * @return bool + */ + private function shouldTranslateTitle(): bool + { + return $this->scopeConfig->isSetFlag( + static::XML_PATH_HEADER_TRANSLATE_TITLE, + ScopeInterface::SCOPE_STORE + ); + } } diff --git a/app/code/Magento/Theme/Test/Unit/Block/Html/TitleTest.php b/app/code/Magento/Theme/Test/Unit/Block/Html/TitleTest.php index a4d56748eee6b..d6357d8d61995 100644 --- a/app/code/Magento/Theme/Test/Unit/Block/Html/TitleTest.php +++ b/app/code/Magento/Theme/Test/Unit/Block/Html/TitleTest.php @@ -5,29 +5,51 @@ */ namespace Magento\Theme\Test\Unit\Block\Html; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Phrase; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\View\Element\Template\Context; +use Magento\Framework\View\Page\Config; +use Magento\Framework\View\Page\Title as PageTitle; +use Magento\Store\Model\ScopeInterface; +use Magento\Theme\Block\Html\Title; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class TitleTest extends \PHPUnit\Framework\TestCase +/** + * Test class for \Magento\Theme\Block\Html\Title + */ +class TitleTest extends TestCase { + /** + * Config path to 'Translate Title' header settings + */ + private const XML_PATH_HEADER_TRANSLATE_TITLE = 'design/header/translate_title'; + /** * @var ObjectManagerHelper */ - protected $objectManagerHelper; + private $objectManagerHelper; + + /** + * @var Config|MockObject + */ + private $pageConfigMock; /** - * @var \Magento\Framework\View\Page\Config|\PHPUnit_Framework_MockObject_MockObject + * @var PageTitle|MockObject */ - protected $pageConfigMock; + private $pageTitleMock; /** - * @var \Magento\Framework\View\Page\Title|\PHPUnit_Framework_MockObject_MockObject + * @var ScopeConfigInterface|MockObject */ - protected $pageTitleMock; + private $scopeConfigMock; /** - * @var \Magento\Theme\Block\Html\Title + * @var Title */ - protected $htmlTitle; + private $htmlTitle; /** * @return void @@ -35,17 +57,22 @@ class TitleTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->pageConfigMock = $this->createMock(\Magento\Framework\View\Page\Config::class); - $this->pageTitleMock = $this->createMock(\Magento\Framework\View\Page\Title::class); + $this->pageConfigMock = $this->createMock(Config::class); + $this->pageTitleMock = $this->createMock(PageTitle::class); $context = $this->objectManagerHelper->getObject( - \Magento\Framework\View\Element\Template\Context::class, + Context::class, ['pageConfig' => $this->pageConfigMock] ); + $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); + $this->htmlTitle = $this->objectManagerHelper->getObject( - \Magento\Theme\Block\Html\Title::class, - ['context' => $context] + Title::class, + [ + 'context' => $context, + 'scopeConfig' => $this->scopeConfigMock + ] ); } @@ -64,10 +91,16 @@ public function testGetPageTitleWithSetPageTitle() } /** + * @param bool $shouldTranslateTitle + * * @return void + * @dataProvider dataProviderShouldTranslateTitle */ - public function testGetPageTitle() + public function testGetPageTitle($shouldTranslateTitle) { + $this->scopeConfigMock->method('isSetFlag') + ->with(static::XML_PATH_HEADER_TRANSLATE_TITLE, ScopeInterface::SCOPE_STORE) + ->willReturn($shouldTranslateTitle); $title = 'some title'; $this->pageTitleMock->expects($this->once()) @@ -77,28 +110,58 @@ public function testGetPageTitle() ->method('getTitle') ->willReturn($this->pageTitleMock); - $this->assertEquals($title, $this->htmlTitle->getPageTitle()); + $result = $this->htmlTitle->getPageTitle(); + + if ($shouldTranslateTitle) { + $this->assertInstanceOf(Phrase::class, $result); + } else { + $this->assertInternalType('string', $result); + } + + $this->assertEquals($title, $result); } /** + * @param bool $shouldTranslateTitle + * * @return void + * @dataProvider dataProviderShouldTranslateTitle */ - public function testGetPageHeadingWithSetPageTitle() + public function testGetPageHeadingWithSetPageTitle($shouldTranslateTitle) { + $this->scopeConfigMock->method('isSetFlag') + ->with(static::XML_PATH_HEADER_TRANSLATE_TITLE, ScopeInterface::SCOPE_STORE) + ->willReturn($shouldTranslateTitle); + $title = 'some title'; $this->htmlTitle->setPageTitle($title); $this->pageConfigMock->expects($this->never()) ->method('getTitle'); - $this->assertEquals($title, $this->htmlTitle->getPageHeading()); + $result = $this->htmlTitle->getPageHeading(); + + if ($shouldTranslateTitle) { + $this->assertInstanceOf(Phrase::class, $result); + } else { + $this->assertInternalType('string', $result); + } + + $this->assertEquals($title, $result); } /** + * @param bool $shouldTranslateTitle + * * @return void + * @dataProvider dataProviderShouldTranslateTitle */ - public function testGetPageHeading() + public function testGetPageHeading($shouldTranslateTitle) { + $this->scopeConfigMock->method('isSetFlag') + ->with(static::XML_PATH_HEADER_TRANSLATE_TITLE, ScopeInterface::SCOPE_STORE) + ->willReturn($shouldTranslateTitle); + $title = 'some title'; $this->pageTitleMock->expects($this->once()) @@ -108,6 +171,29 @@ public function testGetPageHeading() ->method('getTitle') ->willReturn($this->pageTitleMock); - $this->assertEquals($title, $this->htmlTitle->getPageHeading()); + $result = $this->htmlTitle->getPageHeading(); + + if ($shouldTranslateTitle) { + $this->assertInstanceOf(Phrase::class, $result); + } else { + $this->assertInternalType('string', $result); + } + + $this->assertEquals($title, $result); + } + + /** + * @return array + */ + public function dataProviderShouldTranslateTitle(): array + { + return [ + [ + true + ], + [ + false + ] + ]; } } diff --git a/app/code/Magento/Theme/etc/config.xml b/app/code/Magento/Theme/etc/config.xml index 1515c357e094e..c733f2a9503ee 100644 --- a/app/code/Magento/Theme/etc/config.xml +++ b/app/code/Magento/Theme/etc/config.xml @@ -43,6 +43,7 @@ Disallow: /*SID= </search_engine_robots> <header translate="welcome"> <welcome>Default welcome msg!</welcome> + <translate_title>1</translate_title> </header> <footer translate="copyright"> <copyright>Copyright © 2013-present Magento, Inc. All rights reserved.</copyright> diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml index 62f51e74b6007..9e06f6c0f4e51 100644 --- a/app/code/Magento/Theme/etc/di.xml +++ b/app/code/Magento/Theme/etc/di.xml @@ -206,6 +206,10 @@ <item name="path" xsi:type="string">design/header/welcome</item> <item name="fieldset" xsi:type="string">other_settings/header</item> </item> + <item name="header_translate_title" xsi:type="array"> + <item name="path" xsi:type="string">design/header/translate_title</item> + <item name="fieldset" xsi:type="string">other_settings/header</item> + </item> <item name="footer_copyright" xsi:type="array"> <item name="path" xsi:type="string">design/footer/copyright</item> <item name="fieldset" xsi:type="string">other_settings/footer</item> diff --git a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml index bc1f36222dd60..dfe11f3120cd8 100644 --- a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml @@ -208,6 +208,20 @@ <dataScope>header_logo_alt</dataScope> </settings> </field> + <field name="header_translate_title" formElement="select"> + <settings> + <dataType>text</dataType> + <label translate="true">Translate Title</label> + <dataScope>header_translate_title</dataScope> + </settings> + <formElements> + <select> + <settings> + <options class="Magento\Config\Model\Config\Source\Yesno"/> + </settings> + </select> + </formElements> + </field> </fieldset> <fieldset name="footer"> <settings> diff --git a/app/code/Magento/Translation/etc/adminhtml/system.xml b/app/code/Magento/Translation/etc/adminhtml/system.xml index ab854f8a4db52..b19ac5d1bfb09 100644 --- a/app/code/Magento/Translation/etc/adminhtml/system.xml +++ b/app/code/Magento/Translation/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <system> <section id="dev"> <group id="js"> - <field id="translate_strategy" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="translate_strategy" translate="label comment" type="select" sortOrder="30" showInDefault="1" canRestore="1"> <label>Translation Strategy</label> <source_model>Magento\Translation\Model\Js\Config\Source\Strategy</source_model> <comment>Please put your store into maintenance mode and redeploy static files after changing strategy</comment> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminFormSaveAndCloseActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminFormSaveAndCloseActionGroup.xml new file mode 100644 index 0000000000000..a1b8c8c7609e1 --- /dev/null +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminFormSaveAndCloseActionGroup.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="AdminFormSaveAndCloseActionGroup"> + <annotations> + <description>Clicks on 'Save and Close'. Validates that the Success Message is present.</description> + </annotations> + + <click selector="{{AdminProductFormActionSection.saveArrow}}" stepKey="openSaveDropDown"/> + <click selector="{{AdminProductFormActionSection.saveAndClose}}" stepKey="clickOnSaveAndClose"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertSaveMessageSuccess"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminFormSaveAndDuplicateActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminFormSaveAndDuplicateActionGroup.xml new file mode 100644 index 0000000000000..322fbffd90427 --- /dev/null +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminFormSaveAndDuplicateActionGroup.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="AdminFormSaveAndDuplicateActionGroup"> + <annotations> + <description>Clicks on 'Save and Duplicate'. Validates that the Success Message is present and correct.</description> + </annotations> + + <click selector="{{AdminProductFormActionSection.saveArrow}}" stepKey="openSaveDropDown"/> + <click selector="{{AdminProductFormActionSection.saveAndDuplicate}}" stepKey="clickOnSaveAndDuplicate"/> + <see selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertSaveSuccess" userInput="You saved the product."/> + <see selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertDuplicateSuccess" userInput="You duplicated the product."/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminSaveAndCloseActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml similarity index 92% rename from app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminSaveAndCloseActionGroup.xml rename to app/code/Magento/Ui/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml index d4e1c7e3ba873..681ada9484618 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminSaveAndCloseActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminFormSaveAndClose"> <annotations> <description>Clicks on 'Save and Close'. Validates that the Success Message is present.</description> @@ -17,7 +17,6 @@ <click selector="{{AdminProductFormActionSection.saveAndClose}}" stepKey="clickOnSaveAndClose"/> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertSaveMessageSuccess"/> </actionGroup> - <actionGroup name="AdminFormSaveAndDuplicate"> <annotations> <description>Clicks on 'Save and Duplicate'. Validates that the Success Message is present and correct.</description> @@ -27,5 +26,5 @@ <click selector="{{AdminProductFormActionSection.saveAndDuplicate}}" stepKey="clickOnSaveAndDuplicate"/> <see selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertSaveSuccess" userInput="You saved the product."/> <see selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertDuplicateSuccess" userInput="You duplicated the product."/> - </actionGroup> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Ui/etc/adminhtml/system.xml b/app/code/Magento/Ui/etc/adminhtml/system.xml index ab4272f8d2a34..77af492c70b36 100644 --- a/app/code/Magento/Ui/etc/adminhtml/system.xml +++ b/app/code/Magento/Ui/etc/adminhtml/system.xml @@ -9,12 +9,12 @@ <system> <section id="dev"> <group id="js"> - <field id="session_storage_logging" translate="label comment" type="select" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="session_storage_logging" translate="label comment" type="select" sortOrder="100" showInDefault="1" canRestore="1"> <label>Log JS Errors to Session Storage</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>If enabled, can be used by functional tests for extended reporting</comment> </field> - <field id="session_storage_key" translate="label comment" type="text" sortOrder="110" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="session_storage_key" translate="label comment" type="text" sortOrder="110" showInDefault="1" canRestore="1"> <label>Log JS Errors to Session Storage Key</label> <comment>Use this key to retrieve collected js errors</comment> </field> diff --git a/app/code/Magento/Ui/etc/di.xml b/app/code/Magento/Ui/etc/di.xml index 05ace9d556fa0..b0cef3b90d431 100644 --- a/app/code/Magento/Ui/etc/di.xml +++ b/app/code/Magento/Ui/etc/di.xml @@ -194,13 +194,11 @@ <argument name="uiReader" xsi:type="object">uiDefinitionReader</argument> </arguments> </type> - <type name="Magento\Ui\Component\Filter\FilterPool"> + <type name="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool"> <arguments> - <argument name="filters" xsi:type="array"> - <item name="filter_input" xsi:type="string">Magento\Ui\Component\Filter\Type\Input</item> - <item name="filter_select" xsi:type="string">Magento\Ui\Component\Filter\Type\Select</item> - <item name="filter_range" xsi:type="string">Magento\Ui\Component\Filter\Type\Range</item> - <item name="filter_store" xsi:type="string">Magento\Ui\Component\Filter\Type\Store</item> + <argument name="appliers" xsi:type="array"> + <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item> + <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js index 0491390d2b6c2..33acba6103b10 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js @@ -214,6 +214,19 @@ define([ return regions[name]; }, + /** + * Checks if the specified region has any elements + * associated with it. + * + * @param {String} name + * @returns {Boolean} + */ + regionHasElements: function (name) { + var region = this.getRegion(name); + + return region().length > 0; + }, + /** * Replaces specified regions' data with a provided one. * Creates region if it was not created yet. diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal.js b/app/code/Magento/Ui/view/base/web/js/modal/modal.js index bcbb2f3c31dbd..f5c284165d8d2 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/modal.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/modal.js @@ -192,7 +192,7 @@ define([ * @param {String} title */ setTitle: function (title) { - var $title = $(this.options.modalTitle), + var $title = this.modal.find(this.options.modalTitle), $subTitle = this.modal.find(this.options.modalSubTitle); $title.text(title); diff --git a/app/code/Magento/Ups/etc/adminhtml/system.xml b/app/code/Magento/Ups/etc/adminhtml/system.xml index c6a2516e96170..3a1676d221977 100644 --- a/app/code/Magento/Ups/etc/adminhtml/system.xml +++ b/app/code/Magento/Ups/etc/adminhtml/system.xml @@ -10,147 +10,147 @@ <section id="carriers"> <group id="ups" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1"> <label>UPS</label> - <field id="access_license_number" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="access_license_number" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Access License Number</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <depends> <field id="carriers/ups/active">1</field> </depends> </field> - <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled for Checkout</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="allowed_methods" translate="label" type="multiselect" sortOrder="170" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowed_methods" translate="label" type="multiselect" sortOrder="170" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allowed Methods</label> <source_model>Magento\Ups\Model\Config\Source\Method</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="shipment_requesttype" translate="label" type="select" sortOrder="47" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="shipment_requesttype" translate="label" type="select" sortOrder="47" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Packages Request Type</label> <source_model>Magento\Shipping\Model\Config\Source\Online\Requesttype</source_model> </field> - <field id="container" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="container" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Container</label> <source_model>Magento\Ups\Model\Config\Source\Container</source_model> </field> - <field id="free_shipping_enable" translate="label" type="select" sortOrder="210" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_enable" translate="label" type="select" sortOrder="210" showInDefault="1" showInWebsite="1"> <label>Enable Free Shipping Threshold</label> <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> </field> - <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="220" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="220" showInDefault="1" showInWebsite="1"> <label>Free Shipping Amount Threshold</label> <validate>validate-number validate-zero-or-greater</validate> <depends> <field id="free_shipping_enable">1</field> </depends> </field> - <field id="dest_type" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="dest_type" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Destination Type</label> <source_model>Magento\Ups\Model\Config\Source\DestType</source_model> </field> - <field id="free_method" translate="label" type="select" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="free_method" translate="label" type="select" sortOrder="200" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Free Method</label> <frontend_class>free-method</frontend_class> <source_model>Magento\Ups\Model\Config\Source\Freemethod</source_model> </field> - <field id="gateway_url" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="gateway_url" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Gateway URL</label> <backend_model>Magento\Ups\Model\Config\Backend\UpsUrl</backend_model> </field> - <field id="gateway_xml_url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="gateway_xml_url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Gateway XML URL</label> <backend_model>Magento\Ups\Model\Config\Backend\UpsUrl</backend_model> </field> - <field id="handling_type" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_type" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Calculate Handling Fee</label> <source_model>Magento\Shipping\Model\Source\HandlingType</source_model> </field> - <field id="handling_action" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_action" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Handling Applied</label> <source_model>Magento\Shipping\Model\Source\HandlingAction</source_model> </field> - <field id="handling_fee" translate="label" type="text" sortOrder="130" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="handling_fee" translate="label" type="text" sortOrder="130" showInDefault="1" showInWebsite="1"> <label>Handling Fee</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="max_package_weight" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="max_package_weight" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Maximum Package Weight (Please consult your shipping carrier for maximum supported shipping weight)</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="min_package_weight" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="min_package_weight" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Minimum Package Weight (Please consult your shipping carrier for minimum supported shipping weight)</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="origin_shipment" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="origin_shipment" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Origin of the Shipment</label> <source_model>Magento\Ups\Model\Config\Source\OriginShipment</source_model> </field> - <field id="password" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="password" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>Password</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <depends> <field id="carriers/ups/active">1</field> </depends> </field> - <field id="pickup" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="pickup" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Pickup Method</label> <source_model>Magento\Ups\Model\Config\Source\Pickup</source_model> </field> - <field id="sort_order" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="tracking_xml_url" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="tracking_xml_url" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Tracking XML URL</label> <backend_model>Magento\Ups\Model\Config\Backend\UpsUrl</backend_model> </field> - <field id="type" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="type" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>UPS Type</label> <source_model>Magento\Ups\Model\Config\Source\Type</source_model> </field> - <field id="is_account_live" translate="label" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="is_account_live" translate="label" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Live Account</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="unit_of_measure" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="unit_of_measure" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Weight Unit</label> <source_model>Magento\Ups\Model\Config\Source\Unitofmeasure</source_model> </field> - <field id="username" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="username" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> <label>User ID</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> <depends> <field id="carriers/ups/active">1</field> </depends> </field> - <field id="negotiated_active" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="negotiated_active" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable Negotiated Rates</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="include_taxes" translate="label" type="select" sortOrder="45" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="include_taxes" translate="label" type="select" sortOrder="45" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Request Tax-Inclusive Rate</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>When applicable, taxes (sales tax, VAT etc.) are included in the rate.</comment> </field> - <field id="shipper_number" translate="label comment" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="shipper_number" translate="label comment" type="text" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>Shipper Number</label> <comment>Required for negotiated rates; 6-character UPS</comment> </field> - <field id="sallowspecific" translate="label" type="select" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sallowspecific" translate="label" type="select" sortOrder="900" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ship to Applicable Countries</label> <frontend_class>shipping-applicable-country</frontend_class> <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="910" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="910" showInDefault="1" showInWebsite="1"> <label>Ship to Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="showmethod" translate="label" type="select" sortOrder="920" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="showmethod" translate="label" type="select" sortOrder="920" showInDefault="1" showInWebsite="1"> <label>Show Method if Not Applicable</label> <frontend_class>shipping-skip-hide</frontend_class> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -158,12 +158,12 @@ <field id="specificerrmsg" translate="label" type="textarea" sortOrder="800" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Displayed Error Message</label> </field> - <field id="mode_xml" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="mode_xml" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Mode</label> <comment>This enables or disables SSL verification of the Magento server by UPS.</comment> <source_model>Magento\Shipping\Model\Config\Source\Online\Mode</source_model> </field> - <field id="debug" translate="label" type="select" sortOrder="920" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="920" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategory2Test.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategory2Test.xml new file mode 100644 index 0000000000000..e14bb5342db91 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategory2Test.xml @@ -0,0 +1,87 @@ +<?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="AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategory2Test"> + <annotations> + <features value="UrlRewrite"/> + <stories value="Update url rewrites"/> + <title value="Check url rewrites in catalog categories after changing url key"/> + <description value="Check url rewrites in catalog categories after changing url key for store view and moving category"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-25622"/> + <group value="catalog"/> + <group value="url_rewrite"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Create two sub-categories in default category with simple products --> + <createData entity="_defaultCategory" stepKey="createFirstCategory"/> + <createData entity="_defaultProduct" stepKey="createFirstSimpleProduct"> + <requiredEntity createDataKey="createFirstCategory"/> + </createData> + <createData entity="_defaultCategory" stepKey="createSecondCategory"/> + <createData entity="_defaultProduct" stepKey="createSecondSimpleProduct"> + <requiredEntity createDataKey="createSecondCategory"/> + </createData> + + <!-- Log in to backend --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create additional Store View in Main Website Store --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"/> + <magentoCLI command="indexer:reindex" stepKey="reindexAll"/> + </before> + + <after> + <deleteData createDataKey="createFirstCategory" stepKey="deleteFirstCategory"/> + <deleteData createDataKey="createSecondCategory" stepKey="deleteSecondCategory"/> + <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearWebsitesGridFilters"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!-- On the categories editing page change store view to created additional view --> + <actionGroup ref="SwitchCategoryStoreViewActionGroup" stepKey="openFirstCategoryAndSwitchToCustomStoreView"> + <argument name="Store" value="customStore.name"/> + <argument name="CatName" value="$createFirstCategory.name$"/> + </actionGroup> + + <!-- Change url key for category for first category; save --> + <actionGroup ref="ChangeSeoUrlKeyForSubCategoryActionGroup" stepKey="changeFirstCategoryUrlKey"> + <argument name="value" value="{{SimpleRootSubCategory.url_key}}"/> + </actionGroup> + + <!-- Change store view to "All store views" for first category --> + <actionGroup ref="SwitchCategoryToAllStoreViewActionGroup" stepKey="switchToAllStoreViews"> + <argument name="CatName" value="$createFirstCategory.name$"/> + </actionGroup> + + <!-- Move first category inside second category --> + <actionGroup ref="MoveCategoryActionGroup" stepKey="moveFirstCategoryInsideSecondCategory"> + <argument name="childCategory" value="$createFirstCategory.name$"/> + <argument name="parentCategory" value="$createSecondCategory.name$"/> + </actionGroup> + + <!-- Open first category storefront page --> + <amOnPage url="$createSecondCategory.custom_attributes[url_key]$/$createFirstCategory.custom_attributes[url_key]$.html" stepKey="openFirstCategoryStorefrontPage"/> + <waitForPageLoad stepKey="waitForFirstCategoryStorefrontPageLoad"/> + <see userInput="$createFirstSimpleProduct.name$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="seeFirstProductInCategory"/> + + <!-- Switch to custom store view--> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchToCustomStoreView"> + <argument name="storeView" value="customStore"/> + </actionGroup> + + <!-- Assert category url with custom store view --> + <seeInCurrentUrl url="{{SimpleRootSubCategory.url_key}}.html" stepKey="seeUpdatedUrlKey"/> + <see userInput="$createFirstSimpleProduct.name$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="seeFirstProductInCategoryAgain"/> + </test> +</tests> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml index badda06b827ea..7f9ee3020c388 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml @@ -11,12 +11,15 @@ <annotations> <features value="Url Rewrite"/> <stories value="Update url rewrites"/> - <title value="Check url rewrites in catalog categories after changing url key"/> - <description value="Check url rewrites in catalog categories after changing url key for store view and moving category"/> + <title value="DEPRECATED. Check url rewrites in catalog categories after changing url key"/> + <description value="DEPRECATED. Check url rewrites in catalog categories after changing url key for store view and moving category"/> <severity value="CRITICAL"/> <testCaseId value="MC-5352"/> <group value="url_rewrite"/> <group value="mtf_migrated"/> + <skip> + <issueId value="DEPRECATED">Use AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategory2Test instead</issueId> + </skip> </annotations> <before> <!-- Create two sub-categories in default category with simple products --> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteHypenAsRequestPathTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteHypenAsRequestPathTest.xml index 4b9f37f628f34..ad952225c2ff5 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteHypenAsRequestPathTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteHypenAsRequestPathTest.xml @@ -14,6 +14,7 @@ <title value="Delete category URL rewrite, hyphen as request path"/> <description value="Delete category URL rewrite, hyphen as request path"/> <testCaseId value="MC-5348" /> + <severity value="MAJOR"/> <group value="urlRewrite"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteWithRequestPathTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteWithRequestPathTest.xml index 7c4023c6d0f75..dc9928773bf35 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteWithRequestPathTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCategoryUrlRewriteWithRequestPathTest.xml @@ -14,6 +14,7 @@ <title value="Delete category URL rewrite, with request path"/> <description value="Delete category URL rewrite, with request path"/> <testCaseId value="MC-5349" /> + <severity value="MAJOR"/> <group value="urlRewrite"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithNoRedirectsTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithNoRedirectsTest.xml index c40dd3256114e..e2f0d6af0deab 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithNoRedirectsTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithNoRedirectsTest.xml @@ -13,6 +13,7 @@ <stories value="Delete CMS Page URL rewrite with No Redirects"/> <title value="Delete CMS Page URL rewrite with No Redirects"/> <description value="Log in to admin and delete CMS Page URL rewrite with No Redirects"/> + <severity value="CRITICAL"/> <testCaseId value="MC-14648"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithTemporaryRedirectTest.xml index 43de4123f35a8..e3d417f3c1f39 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithTemporaryRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCmsPageUrlRewriteWithTemporaryRedirectTest.xml @@ -14,6 +14,7 @@ <title value="Delete CMS Page URL rewrite with Temporary Redirect"/> <description value="Log in to admin and delete CMS Page URL rewrite with Temporary Redirect"/> <testCaseId value="MC-14650"/> + <severity value="CRITICAL"/> <group value="mtf_migrated"/> </annotations> <before> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithNoRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithNoRedirectTest.xml index 6467a5051631d..b2fa13ead1164 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithNoRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithNoRedirectTest.xml @@ -12,6 +12,7 @@ <stories value="Update CMS Page URL Redirect With No Redirect"/> <title value="Update CMS Page URL Redirect With No Redirect"/> <description value="Login as Admin and tried to update the created URL Rewrite for CMS page"/> + <severity value="MINOR"/> <group value="cMSContent"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithTemporaryRedirectTest.xml index a7cadcdf753c3..b00241bc3acac 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithTemporaryRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCmsPageRewriteEntityWithTemporaryRedirectTest.xml @@ -12,6 +12,7 @@ <stories value="Update CMS Page URL Redirect With Temporary Redirect"/> <title value="Update CMS Page URL Redirect With Temporary Redirect"/> <description value="Login as Admin and tried to update the created URL Rewrite for CMS page"/> + <severity value="MINOR"/> <group value="cMSContent"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml index 88c28230b8347..15d54d2904b58 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductAfterImportTest.xml @@ -22,8 +22,6 @@ </annotations> <before> <comment userInput="Set the configuration for Generate category/product URL Rewrites" stepKey="commentSetURLRewriteConfiguration" /> - <comment userInput="Enable config to generate category/product URL Rewrites " stepKey="commentEnableConfig" /> - <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 1" stepKey="enableGenerateUrlRewrite"/> <createData entity="NewRootCategory" stepKey="simpleSubCategory1"> <field key="parent_id">2</field> </createData> @@ -44,8 +42,6 @@ <deleteData createDataKey="simpleSubCategory3" stepKey="deleteSimpleSubCategory3"/> <deleteData createDataKey="simpleSubCategory2" stepKey="deleteSimpleSubCategory2"/> <deleteData createDataKey="simpleSubCategory1" stepKey="deleteSimpleSubCategory1"/> - <comment userInput="Disable config to generate category/product URL Rewrites " stepKey="commentDisableConfig" /> - <magentoCLI command="config:set catalog/seo/generate_category_product_rewrites 0" stepKey="disableGenerateUrlRewrite"/> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> </after> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserClickRoleResourceTabActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserClickRoleResourceTabActionGroup.xml new file mode 100644 index 0000000000000..3e20eaf973674 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserClickRoleResourceTabActionGroup.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="AdminUserClickRoleResourceTabActionGroup"> + <annotations> + <description>Switch to role resource tab.</description> + </annotations> + <click selector="{{AdminEditRoleInfoSection.roleResourcesTab}}" stepKey="clickRoleResourcesTab" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserOpenAdminRolesPageActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserOpenAdminRolesPageActionGroup.xml new file mode 100644 index 0000000000000..71be9117e5caf --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserOpenAdminRolesPageActionGroup.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="AdminUserOpenAdminRolesPageActionGroup"> + <annotations> + <description>Navigate to User Role Grid</description> + </annotations> + <amOnPage url="{{AdminRolesPage.url}}" stepKey="navigateToUserRoleGrid" /> + <waitForPageLoad stepKey="waitForRolesGridLoad" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserSaveRoleActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserSaveRoleActionGroup.xml new file mode 100644 index 0000000000000..824e9407125f5 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserSaveRoleActionGroup.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="AdminUserSaveRoleActionGroup"> + <annotations> + <description>Click to Save Role</description> + </annotations> + <click selector="{{AdminEditRoleInfoSection.saveButton}}" stepKey="clickSaveRoleButton" /> + <see userInput="You saved the role." stepKey="seeUserRoleSavedMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/etc/adminhtml/system.xml b/app/code/Magento/User/etc/adminhtml/system.xml index 6c57b268968c3..584b40a023c93 100644 --- a/app/code/Magento/User/etc/adminhtml/system.xml +++ b/app/code/Magento/User/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <system> <section id="admin"> <group id="emails"> - <field id="user_notification_template" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="user_notification_template" translate="label comment" type="select" sortOrder="40" showInDefault="1" canRestore="1"> <label>User Notification Template</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> diff --git a/app/code/Magento/User/etc/config.xml b/app/code/Magento/User/etc/config.xml index c1f51bcbecef4..57631df18bb85 100644 --- a/app/code/Magento/User/etc/config.xml +++ b/app/code/Magento/User/etc/config.xml @@ -16,6 +16,7 @@ </emails> <security> <password_reset_link_expiration_period>2</password_reset_link_expiration_period> + <use_case_sensitive_login>0</use_case_sensitive_login> <lockout_failures>6</lockout_failures> <lockout_threshold>30</lockout_threshold> <password_lifetime>90</password_lifetime> diff --git a/app/code/Magento/Usps/etc/adminhtml/system.xml b/app/code/Magento/Usps/etc/adminhtml/system.xml index 0849572e7eb1c..b01f7be9a19f9 100644 --- a/app/code/Magento/Usps/etc/adminhtml/system.xml +++ b/app/code/Magento/Usps/etc/adminhtml/system.xml @@ -10,102 +10,102 @@ <section id="carriers"> <group id="usps" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="1"> <label>USPS</label> - <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enabled for Checkout</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="gateway_url" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="gateway_url" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Gateway URL</label> </field> - <field id="gateway_secure_url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="gateway_secure_url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Secure Gateway URL</label> </field> <field id="title" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> </field> - <field id="userid" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="userid" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1"> <label>User ID</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="password" translate="label" type="obscure" sortOrder="53" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="password" translate="label" type="obscure" sortOrder="53" showInDefault="1" showInWebsite="1"> <label>Password</label> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> - <field id="mode" translate="label" type="select" sortOrder="54" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="mode" translate="label" type="select" sortOrder="54" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Mode</label> <source_model>Magento\Shipping\Model\Config\Source\Online\Mode</source_model> </field> - <field id="shipment_requesttype" translate="label" type="select" sortOrder="55" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="shipment_requesttype" translate="label" type="select" sortOrder="55" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Packages Request Type</label> <source_model>Magento\Shipping\Model\Config\Source\Online\Requesttype</source_model> </field> - <field id="container" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="container" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Container</label> <source_model>Magento\Usps\Model\Source\Container</source_model> </field> - <field id="size" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="size" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Size</label> <source_model>Magento\Usps\Model\Source\Size</source_model> </field> - <field id="width" translate="label" type="text" sortOrder="73" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="width" translate="label" type="text" sortOrder="73" showInDefault="1" showInWebsite="1"> <label>Width</label> <depends> <field id="size">LARGE</field> </depends> </field> - <field id="length" translate="label" type="text" sortOrder="72" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="length" translate="label" type="text" sortOrder="72" showInDefault="1" showInWebsite="1"> <label>Length</label> <depends> <field id="size">LARGE</field> </depends> </field> - <field id="height" translate="label" type="text" sortOrder="74" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="height" translate="label" type="text" sortOrder="74" showInDefault="1" showInWebsite="1"> <label>Height</label> <depends> <field id="size">LARGE</field> </depends> </field> - <field id="girth" translate="label" type="text" sortOrder="76" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="girth" translate="label" type="text" sortOrder="76" showInDefault="1" showInWebsite="1"> <label>Girth</label> <depends> <field id="size">LARGE</field> </depends> </field> - <field id="machinable" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="machinable" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Machinable</label> <source_model>Magento\Usps\Model\Source\Machinable</source_model> </field> - <field id="max_package_weight" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="max_package_weight" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Maximum Package Weight (Please consult your shipping carrier for maximum supported shipping weight)</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="handling_type" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_type" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Calculate Handling Fee</label> <source_model>Magento\Shipping\Model\Source\HandlingType</source_model> </field> - <field id="handling_action" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="handling_action" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Handling Applied</label> <source_model>Magento\Shipping\Model\Source\HandlingAction</source_model> </field> - <field id="handling_fee" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="handling_fee" translate="label" type="text" sortOrder="120" showInDefault="1" showInWebsite="1"> <label>Handling Fee</label> <validate>validate-number validate-zero-or-greater</validate> </field> - <field id="allowed_methods" translate="label" type="multiselect" sortOrder="130" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="allowed_methods" translate="label" type="multiselect" sortOrder="130" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Allowed Methods</label> <source_model>Magento\Usps\Model\Source\Method</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="free_method" translate="label" type="select" sortOrder="140" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="free_method" translate="label" type="select" sortOrder="140" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Free Method</label> <frontend_class>free-method</frontend_class> <source_model>Magento\Usps\Model\Source\Freemethod</source_model> </field> - <field id="free_shipping_enable" translate="label" type="select" sortOrder="160" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_enable" translate="label" type="select" sortOrder="160" showInDefault="1" showInWebsite="1"> <label>Enable Free Shipping Threshold</label> <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> </field> - <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="165" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="free_shipping_subtotal" translate="label" type="text" sortOrder="165" showInDefault="1" showInWebsite="1"> <label>Free Shipping Amount Threshold</label> <validate>validate-number validate-zero-or-greater</validate> <depends> @@ -115,26 +115,26 @@ <field id="specificerrmsg" translate="label" type="textarea" sortOrder="170" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Displayed Error Message</label> </field> - <field id="sallowspecific" translate="label" type="select" sortOrder="180" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="sallowspecific" translate="label" type="select" sortOrder="180" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Ship to Applicable Countries</label> <frontend_class>shipping-applicable-country</frontend_class> <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model> </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="190" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="190" showInDefault="1" showInWebsite="1"> <label>Ship to Specific Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> <can_be_empty>1</can_be_empty> </field> - <field id="debug" translate="label" type="select" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="debug" translate="label" type="select" sortOrder="200" showInDefault="1" showInWebsite="1"> <label>Debug</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="showmethod" translate="label" type="select" sortOrder="210" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="showmethod" translate="label" type="select" sortOrder="210" showInDefault="1" showInWebsite="1"> <label>Show Method if Not Applicable</label> <frontend_class>shipping-skip-hide</frontend_class> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="sort_order" translate="label" type="text" sortOrder="220" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="sort_order" translate="label" type="text" sortOrder="220" showInDefault="1" showInWebsite="1"> <label>Sort Order</label> <validate>validate-number validate-zero-or-greater</validate> </field> diff --git a/app/code/Magento/VaultGraphQl/Model/Resolver/DeletePaymentToken.php b/app/code/Magento/VaultGraphQl/Model/Resolver/DeletePaymentToken.php index 8dc42cebe8dfc..146215059b365 100644 --- a/app/code/Magento/VaultGraphQl/Model/Resolver/DeletePaymentToken.php +++ b/app/code/Magento/VaultGraphQl/Model/Resolver/DeletePaymentToken.php @@ -9,7 +9,6 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; -use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -59,10 +58,6 @@ public function resolve( throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); } - if (!isset($args['public_hash'])) { - throw new GraphQlInputException(__('Specify the "public_hash" value.')); - } - $token = $this->paymentTokenManagement->getByPublicHash($args['public_hash'], $context->getUserId()); if (!$token) { throw new GraphQlNoSuchEntityException( diff --git a/app/code/Magento/VaultGraphQl/Test/Unit/Model/Resolver/DeletePaymentTokenTest.php b/app/code/Magento/VaultGraphQl/Test/Unit/Model/Resolver/DeletePaymentTokenTest.php new file mode 100644 index 0000000000000..0ec1a8b3907e7 --- /dev/null +++ b/app/code/Magento/VaultGraphQl/Test/Unit/Model/Resolver/DeletePaymentTokenTest.php @@ -0,0 +1,230 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\VaultGraphQl\Test\Unit\Model\Resolver; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\GraphQl\Model\Query\ContextInterface; +use Magento\GraphQl\Model\Query\ContextExtensionInterface; +use Magento\Vault\Api\PaymentTokenManagementInterface; +use Magento\Vault\Api\PaymentTokenRepositoryInterface; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\VaultGraphQl\Model\Resolver\DeletePaymentToken; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test class for \Magento\VaultGraphQl\Model\Resolver\DeletePaymentToken + */ +class DeletePaymentTokenTest extends TestCase +{ + /** + * Object Manager Instance + * + * @var ObjectManager + */ + private $objectManager; + + /** + * Testable Object + * + * @var DeletePaymentToken + */ + private $resolver; + + /** + * @var ContextInterface|MockObject + */ + private $contextMock; + + /** + * @var ContextExtensionInterface|MockObject + */ + private $contextExtensionMock; + + /** + * @var Field|MockObject + */ + private $fieldMock; + + /** + * @var PaymentTokenManagementInterface|MockObject + */ + private $paymentTokenManagementMock; + + /** + * @var PaymentTokenRepositoryInterface|MockObject + */ + private $paymentTokenRepositoryMock; + + /** + * @var PaymentTokenInterface|MockObject + */ + private $paymentTokenMock; + + /** + * @var ResolveInfo|MockObject + */ + private $resolveInfoMock; + + /** + * @inheritdoc + */ + protected function setUp() : void + { + $this->objectManager = new ObjectManager($this); + + $this->contextMock = $this->getMockBuilder(ContextInterface::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'getExtensionAttributes', + 'getUserId', + 'getUserType', + ] + ) + ->getMock(); + + $this->contextExtensionMock = $this->getMockBuilder(ContextExtensionInterface::class) + ->setMethods( + [ + 'getIsCustomer', + 'getStore', + 'setStore', + 'setIsCustomer', + ] + ) + ->getMock(); + + $this->fieldMock = $this->getMockBuilder(Field::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentTokenManagementMock = $this->getMockBuilder(PaymentTokenManagementInterface::class) + ->getMockForAbstractClass(); + + $this->paymentTokenRepositoryMock = $this->getMockBuilder(PaymentTokenRepositoryInterface::class) + ->getMockForAbstractClass(); + + $this->paymentTokenMock = $this->getMockBuilder(PaymentTokenInterface::class) + ->getMockForAbstractClass(); + + $this->resolveInfoMock = $this->getMockBuilder(ResolveInfo::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resolver = $this->objectManager->getObject( + DeletePaymentToken::class, + [ + 'paymentTokenManagement' => $this->paymentTokenManagementMock, + 'paymentTokenRepository' => $this->paymentTokenRepositoryMock, + ] + ); + } + + /** + * Test delete customer payment token + */ + public function testDeleteCustomerPaymentToken() + { + $isCustomer = true; + $paymentTokenResult = true; + + $this->contextMock + ->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->contextExtensionMock); + + $this->contextExtensionMock + ->expects($this->once()) + ->method('getIsCustomer') + ->willReturn($isCustomer); + + $this->paymentTokenManagementMock + ->expects($this->once()) + ->method('getByPublicHash') + ->willReturn($this->paymentTokenMock); + + $this->paymentTokenRepositoryMock + ->expects($this->once()) + ->method('delete') + ->with($this->paymentTokenMock) + ->willReturn($paymentTokenResult); + + $this->assertEquals( + [ + 'result' => true + ], + $this->resolver->resolve( + $this->fieldMock, + $this->contextMock, + $this->resolveInfoMock + ) + ); + } + + /** + * Test mutation when customer isn't authorized. + * + * @expectedException \Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testCustomerNotAuthorized() + { + $isCustomer = false; + + $this->contextMock + ->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->contextExtensionMock); + + $this->contextExtensionMock + ->expects($this->once()) + ->method('getIsCustomer') + ->willReturn($isCustomer); + + $this->resolver->resolve( + $this->fieldMock, + $this->contextMock, + $this->resolveInfoMock + ); + } + + /** + * Test mutation when provided token ID does not exist + */ + public function testCustomerPaymentTokenNotExists() + { + $isCustomer = true; + $token = false; + + $this->contextMock + ->expects($this->once()) + ->method('getExtensionAttributes') + ->willReturn($this->contextExtensionMock); + + $this->contextExtensionMock + ->expects($this->once()) + ->method('getIsCustomer') + ->willReturn($isCustomer); + + $this->paymentTokenManagementMock + ->expects($this->once()) + ->method('getByPublicHash') + ->willReturn($token); + + $this->expectException(GraphQlNoSuchEntityException::class); + + $this->resolver->resolve( + $this->fieldMock, + $this->contextMock, + $this->resolveInfoMock + ); + } +} diff --git a/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml b/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml index d6f40f5ac2023..ea60d34f78b0f 100644 --- a/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml +++ b/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml @@ -6,9 +6,9 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> <section id="webapi" type="text" sortOrder="102" showInDefault="1" showInWebsite="1" showInStore="1"> - <group id="webapisecurity" translate="label" type="text" sortOrder="250" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="webapisecurity" translate="label" type="text" sortOrder="250" showInDefault="1"> <label>Web API Security</label> - <field id="allow_insecure" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="allow_insecure" translate="label comment" type="select" sortOrder="1" showInDefault="1"> <label>Allow Anonymous Guest Access</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>This feature applies only to CMS, Catalog and Store APIs. Please consult your developers for details on potential security risks.</comment> diff --git a/app/code/Magento/Weee/etc/adminhtml/system.xml b/app/code/Magento/Weee/etc/adminhtml/system.xml index d3e9efb8f0b46..68ceae482daaa 100644 --- a/app/code/Magento/Weee/etc/adminhtml/system.xml +++ b/app/code/Magento/Weee/etc/adminhtml/system.xml @@ -10,39 +10,57 @@ <section id="tax"> <group id="weee" translate="label" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Fixed Product Taxes</label> - <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Enable FPT</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="display_list" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="display_list" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Display Prices In Product Lists</label> <source_model>Magento\Weee\Model\Config\Source\Display</source_model> + <depends> + <field id="enable">1</field> + </depends> </field> - <field id="display" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="display" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Display Prices On Product View Page</label> <source_model>Magento\Weee\Model\Config\Source\Display</source_model> + <depends> + <field id="enable">1</field> + </depends> </field> - <field id="display_sales" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="display_sales" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Display Prices In Sales Modules</label> <source_model>Magento\Weee\Model\Config\Source\Display</source_model> + <depends> + <field id="enable">1</field> + </depends> </field> - <field id="display_email" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="display_email" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Display Prices In Emails</label> <source_model>Magento\Weee\Model\Config\Source\Display</source_model> + <depends> + <field id="enable">1</field> + </depends> </field> - <field id="apply_vat" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="apply_vat" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Apply Tax To FPT</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="enable">1</field> + </depends> </field> - <field id="include_in_subtotal" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="include_in_subtotal" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Include FPT In Subtotal</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="enable">1</field> + </depends> </field> </group> </section> <section id="sales"> <group id="totals_sort"> - <field id="weee" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="weee" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Fixed Product Tax</label> <validate>required-number validate-number</validate> </field> diff --git a/app/code/Magento/Wishlist/Controller/Index/Plugin.php b/app/code/Magento/Wishlist/Controller/Index/Plugin.php index 150e4de72b40d..63ae419759250 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Plugin.php +++ b/app/code/Magento/Wishlist/Controller/Index/Plugin.php @@ -10,6 +10,7 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\Response\RedirectInterface; +use Magento\Store\Model\ScopeInterface; /** * Wishlist plugin before dispatch @@ -89,7 +90,7 @@ public function beforeDispatch(\Magento\Framework\App\ActionInterface $subject, $this->messageManager->addErrorMessage(__('You must login or register to add items to your wishlist.')); } } - if (!$this->config->isSetFlag('wishlist/general/active')) { + if (!$this->config->isSetFlag('wishlist/general/active', ScopeInterface::SCOPE_STORES)) { throw new NotFoundException(__('Page not found.')); } } diff --git a/app/code/Magento/Wishlist/Controller/Index/Update.php b/app/code/Magento/Wishlist/Controller/Index/Update.php index e5fbd4b93f82e..ceb001a61c405 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Update.php +++ b/app/code/Magento/Wishlist/Controller/Index/Update.php @@ -11,7 +11,7 @@ use Magento\Framework\Controller\ResultFactory; /** - * Class Update + * Controller for updating wishlists */ class Update extends \Magento\Wishlist\Controller\AbstractIndex implements HttpPostActionInterface { @@ -70,7 +70,12 @@ public function execute() } $post = $this->getRequest()->getPostValue(); - if ($post && isset($post['description']) && is_array($post['description'])) { + $resultRedirect->setPath('*', ['wishlist_id' => $wishlist->getId()]); + if (!$post) { + return $resultRedirect; + } + + if (isset($post['description']) && is_array($post['description'])) { $updatedItems = 0; foreach ($post['description'] as $itemId => $description) { @@ -136,13 +141,12 @@ public function execute() $this->messageManager->addErrorMessage(__('Can\'t update wish list')); } } + } - if (isset($post['save_and_share'])) { - $resultRedirect->setPath('*/*/share', ['wishlist_id' => $wishlist->getId()]); - return $resultRedirect; - } + if (isset($post['save_and_share'])) { + $resultRedirect->setPath('*/*/share', ['wishlist_id' => $wishlist->getId()]); } - $resultRedirect->setPath('*', ['wishlist_id' => $wishlist->getId()]); + return $resultRedirect; } } diff --git a/app/code/Magento/Wishlist/CustomerData/Wishlist.php b/app/code/Magento/Wishlist/CustomerData/Wishlist.php index ae54289d4b1c9..2f6b57a8650c4 100644 --- a/app/code/Magento/Wishlist/CustomerData/Wishlist.php +++ b/app/code/Magento/Wishlist/CustomerData/Wishlist.php @@ -68,7 +68,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getSectionData() { @@ -80,6 +80,8 @@ public function getSectionData() } /** + * Get counter + * * @return string */ protected function getCounter() @@ -156,7 +158,6 @@ protected function getItemData(\Magento\Wishlist\Model\Item $wishlistItem) * * @param \Magento\Catalog\Model\Product $product * @return array - * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function getImageData($product) { @@ -164,27 +165,11 @@ protected function getImageData($product) $helper = $this->imageHelperFactory->create() ->init($product, 'wishlist_sidebar_block'); - $template = 'Magento_Catalog/product/image_with_borders'; - - try { - $imagesize = $helper->getResizedImageInfo(); - } catch (NotLoadInfoImageException $exception) { - $imagesize = [$helper->getWidth(), $helper->getHeight()]; - } - - $width = $helper->getFrame() - ? $helper->getWidth() - : $imagesize[0]; - - $height = $helper->getFrame() - ? $helper->getHeight() - : $imagesize[1]; - return [ - 'template' => $template, + 'template' => 'Magento_Catalog/product/image_with_borders', 'src' => $helper->getUrl(), - 'width' => $width, - 'height' => $height, + 'width' => $helper->getWidth(), + 'height' => $helper->getHeight(), 'alt' => $helper->getLabel(), ]; } diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/PluginTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/PluginTest.php index 2b583f9101516..53b9ba7d846b1 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/PluginTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/PluginTest.php @@ -6,8 +6,12 @@ namespace Magento\Wishlist\Test\Unit\Controller\Index; +use Magento\Store\Model\ScopeInterface; + /** * Test for wishlist plugin before dispatch + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class PluginTest extends \PHPUnit\Framework\TestCase { @@ -175,7 +179,7 @@ public function testBeforeDispatch() $this->config ->expects($this->once()) ->method('isSetFlag') - ->with('wishlist/general/active') + ->with('wishlist/general/active', ScopeInterface::SCOPE_STORES) ->willReturn(false); $this->getPlugin()->beforeDispatch($indexController, $this->request); diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/UpdateTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/UpdateTest.php new file mode 100644 index 0000000000000..88aeec5e5a924 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/UpdateTest.php @@ -0,0 +1,290 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Wishlist\Test\Unit\Controller\Index; + +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Data\Form\FormKey\Validator; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Wishlist\Controller\Index\Update; +use Magento\Wishlist\Controller\WishlistProviderInterface; +use Magento\Wishlist\Helper\Data; +use Magento\Wishlist\Model\Item; +use Magento\Wishlist\Model\LocaleQuantityProcessor; +use Magento\Wishlist\Model\Wishlist; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Test for upate controller wishlist + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class UpdateTest extends TestCase +{ + private const STUB_ITEM_ID = 1; + + private const STUB_WISHLIST_PRODUCT_QTY = 21; + + /** + * @var MockObject|Validator $formKeyValidatorMock + */ + private $formKeyValidatorMock; + + /** + * @var MockObject|WishlistProviderInterface $wishlistProviderMock + */ + private $wishlistProviderMock; + + /** + * @var MockObject|LocaleQuantityProcessor $quantityProcessorMock + */ + private $quantityProcessorMock; + + /** + * @var Update $updateController + */ + private $updateController; + + /** + * @var MockObject|Context$contextMock + */ + private $contextMock; + + /** + * @var MockObject|Redirect $resultRedirectMock + */ + private $resultRedirectMock; + + /** + * @var MockObject|ResultFactory $resultFatoryMock + */ + private $resultFactoryMock; + + /** + * @var MockObject|RequestInterface $requestMock + */ + private $requestMock; + + /** + * @var MockObject|ObjectManagerInterface $objectManagerMock + */ + private $objectManagerMock; + + /** + * @var MockObject|ManagerInterface $messageManagerMock + */ + private $messageManagerMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->formKeyValidatorMock = $this->createMock(Validator::class); + $this->wishlistProviderMock = $this->createMock(WishlistProviderInterface::class); + $this->quantityProcessorMock = $this->createMock(LocaleQuantityProcessor::class); + $this->contextMock = $this->createMock(Context::class); + $this->resultRedirectMock = $this->createMock(Redirect::class); + $this->resultFactoryMock = $this->createPartialMock(ResultFactory::class, ['create']); + $this->messageManagerMock = $this->createMock(ManagerInterface::class); + $this->objectManagerMock = $this->createMock(ObjectManagerInterface::class); + $this->requestMock = $this->getMockBuilder(RequestInterface::class) + ->setMethods(['getPostValue']) + ->getMockForAbstractClass(); + + $this->resultFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->resultRedirectMock); + $this->contextMock->expects($this->once()) + ->method('getResultFactory') + ->willReturn($this->resultFactoryMock); + $this->contextMock->expects($this->once()) + ->method('getObjectManager') + ->willReturn($this->objectManagerMock); + $this->contextMock->expects($this->any()) + ->method('getRequest') + ->willReturn($this->requestMock); + $this->contextMock->expects($this->any()) + ->method('getMessageManager') + ->willReturn($this->messageManagerMock); + + $objectManager = new ObjectManagerHelper($this); + + $this->updateController = $objectManager->getObject( + Update::class, + [ + 'context' => $this->contextMock, + '_formKeyValidator' => $this->formKeyValidatorMock, + 'wishlistProvider' => $this->wishlistProviderMock, + 'quantityProcessor' => $this->quantityProcessorMock + ] + ); + } + + /** + * Test for update method Wishlist controller. + * + * @dataProvider getWishlistDataProvider + * @param array $wishlistDataProvider + * @param array $postData + * @return void + */ + public function testUpdate(array $wishlistDataProvider, array $postData): void + { + $wishlist = $this->createMock(Wishlist::class); + $itemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'load', + 'getId', + 'getWishlistId', + 'setQty', + 'save', + 'getDescription', + 'setDescription', + 'getProduct', + 'getName' + ] + )->getMock(); + $dataMock = $this->createMock(Data::class); + $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->formKeyValidatorMock->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $this->wishlistProviderMock->expects($this->once()) + ->method('getWishlist') + ->willReturn($wishlist); + $wishlist->expects($this->exactly(2)) + ->method('getId') + ->willReturn($wishlistDataProvider['id']); + $this->requestMock->expects($this->once()) + ->method('getPostValue') + ->willReturn($postData); + $this->resultRedirectMock->expects($this->once()) + ->method('setPath') + ->with('*', ['wishlist_id' => $wishlistDataProvider['id']]); + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with(Item::class) + ->willReturn($itemMock); + $itemMock->expects($this->once()) + ->method('load') + ->with(1) + ->willReturnSelf(); + $itemMock->expects($this->once()) + ->method('getWishLIstId') + ->willReturn($wishlistDataProvider['id']); + $itemMock->expects($this->once()) + ->method('getDescription') + ->willReturn(''); + $itemMock->expects($this->once()) + ->method('setDescription') + ->willReturnSelf(); + $itemMock->expects($this->once()) + ->method('setQty') + ->willReturnSelf(); + $this->objectManagerMock->expects($this->exactly(2)) + ->method('get') + ->with(Data::class) + ->willReturn($dataMock); + $dataMock->expects($this->once()) + ->method('defaultCommentString') + ->willReturn(''); + $dataMock->expects($this->once()) + ->method('calculate'); + $this->quantityProcessorMock->expects($this->once()) + ->method('process') + ->willReturn($postData['qty']); + $itemMock->expects($this->once()) + ->method('getProduct') + ->willReturn($productMock); + $productMock->expects($this->once()) + ->method('getName') + ->willReturn('product'); + $this->messageManagerMock->expects($this->once()) + ->method('addSuccessMessage'); + + $this->assertEquals($this->resultRedirectMock, $this->updateController->execute()); + } + + /** + * Verify update method if post data not available + * + * @dataProvider getWishlistDataProvider + * @param array $wishlistDataProvider + * @return void + */ + public function testUpdateRedirectWhenNoPostData(array $wishlistDataProvider): void + { + $wishlist = $this->createMock(Wishlist::class); + + $this->formKeyValidatorMock->expects($this->once()) + ->method('validate') + ->willReturn(true); + $this->wishlistProviderMock->expects($this->once()) + ->method('getWishlist') + ->willReturn($wishlist); + $wishlist->expects($this->exactly(1)) + ->method('getId') + ->willReturn($wishlistDataProvider['id']); + $this->resultRedirectMock->expects($this->once()) + ->method('setPath') + ->with('*', ['wishlist_id' => $wishlistDataProvider['id']]); + $this->requestMock->expects($this->once()) + ->method('getPostValue') + ->willReturn(null); + + $this->assertEquals($this->resultRedirectMock, $this->updateController->execute()); + } + + /** + * Check if wishlist not availbale, and exception is shown + * + * @return void + */ + public function testUpdateThrowsNotFoundExceptionWhenWishlistDoNotExist(): void + { + $this->formKeyValidatorMock->expects($this->once()) + ->method('validate') + ->willReturn(true); + $this->wishlistProviderMock->expects($this->once()) + ->method('getWishlist') + ->willReturn(null); + $this->expectException(NotFoundException::class); + $this->updateController->execute(); + } + + /** + * Dataprovider for Update test + * + * @return array + */ + public function getWishlistDataProvider(): array + { + return + [ + [ + [ + 'id' => self::STUB_ITEM_ID + ], + [ + 'qty' => [self::STUB_ITEM_ID => self::STUB_WISHLIST_PRODUCT_QTY], + 'description' => [self::STUB_ITEM_ID => 'Description for item_id 1'] + ] + ] + ]; + } +} diff --git a/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php index 4954712e5ff3b..6d90d8b1a5fed 100644 --- a/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php @@ -193,9 +193,6 @@ public function testGetSectionData() $this->catalogImageHelperMock->expects($this->any()) ->method('getFrame') ->willReturn(true); - $this->catalogImageHelperMock->expects($this->once()) - ->method('getResizedImageInfo') - ->willReturn([]); $this->wishlistHelperMock->expects($this->once()) ->method('getProductUrl') @@ -394,9 +391,6 @@ public function testGetSectionDataWithTwoItems() $this->catalogImageHelperMock->expects($this->any()) ->method('getFrame') ->willReturn(true); - $this->catalogImageHelperMock->expects($this->exactly(2)) - ->method('getResizedImageInfo') - ->willReturn([]); $this->wishlistHelperMock->expects($this->exactly(2)) ->method('getProductUrl') diff --git a/app/code/Magento/Wishlist/etc/adminhtml/system.xml b/app/code/Magento/Wishlist/etc/adminhtml/system.xml index e61c07abca993..efadd6c4d58ba 100644 --- a/app/code/Magento/Wishlist/etc/adminhtml/system.xml +++ b/app/code/Magento/Wishlist/etc/adminhtml/system.xml @@ -13,6 +13,9 @@ <resource>Magento_Wishlist::config_wishlist</resource> <group id="email" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Share Options</label> + <depends> + <field id="*/general/active">1</field> + </depends> <field id="email_identity" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Email Sender</label> <source_model>Magento\Config\Model\Config\Source\Email\Identity</source_model> @@ -42,11 +45,17 @@ <field id="show_in_sidebar" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Show in Sidebar</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="active">1</field> + </depends> </field> </group> - <group id="wishlist_link" translate="label" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="wishlist_link" translate="label" sortOrder="3" showInDefault="1" showInWebsite="1"> <label>My Wish List Link</label> - <field id="use_qty" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"> + <depends> + <field id="*/general/active">1</field> + </depends> + <field id="use_qty" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Display Wish List Summary</label> <source_model>Magento\Wishlist\Model\Config\Source\Summary</source_model> </field> diff --git a/app/code/Magento/Wishlist/etc/config.xml b/app/code/Magento/Wishlist/etc/config.xml index dd88e63bc90ad..780d6b904fedc 100644 --- a/app/code/Magento/Wishlist/etc/config.xml +++ b/app/code/Magento/Wishlist/etc/config.xml @@ -18,6 +18,9 @@ <number_limit>10</number_limit> <text_limit>255</text_limit> </email> + <wishlist_link> + <use_qty>0</use_qty> + </wishlist_link> </wishlist> <captcha translate="label"> <frontend> diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less index 44e93087399a1..cd7177d329e7f 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less @@ -543,7 +543,6 @@ } .compare, - .product-addto-links .action.tocompare, .product-item-actions .actions-secondary > .action.tocompare, [class*='block-compare'] { display: none; diff --git a/app/design/frontend/Magento/blank/web/css/source/_navigation.less b/app/design/frontend/Magento/blank/web/css/source/_navigation.less index 21b7315779764..fad906a089400 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_navigation.less +++ b/app/design/frontend/Magento/blank/web/css/source/_navigation.less @@ -142,7 +142,7 @@ display: block; } } - } + } .header.links { .lib-list-reset-styles(); border-bottom: 1px solid @color-gray82; @@ -154,7 +154,7 @@ &.greet.welcome { border-top: 1px solid @color-gray82; font-weight: @font-weight__bold; - padding: .8rem @indent__base; + padding: .8rem @submenu__padding-left; } > a { @@ -168,7 +168,7 @@ .lib-css(text-decoration, @navigation-level0-item__text-decoration); display: block; font-weight: @font-weight__bold; - padding: .8rem @indent__base; + padding: .8rem @submenu__padding-left; } .header.links { diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less index 27533a0eb598f..e3296c3bcf825 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less @@ -623,11 +623,6 @@ // _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) { - .product-social-links { - .action.tocompare { - display: none; - } - } .product-info-price { margin: 0 -@indent__s 0; diff --git a/composer.json b/composer.json index ab767fdac286d..9cbbf1689738f 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", "elasticsearch/elasticsearch": "~2.0||~5.1||~6.1", - "magento/composer": "~1.5.0", + "magento/composer": "1.6.x-dev", "magento/magento-composer-installer": ">=0.1.11", "magento/zendframework1": "~1.14.2", "monolog/monolog": "^1.17", @@ -46,9 +46,9 @@ "phpseclib/mcrypt_compat": "1.0.8", "phpseclib/phpseclib": "2.0.*", "ramsey/uuid": "~3.8.0", - "symfony/console": "~4.1.0||~4.2.0||~4.3.0", - "symfony/event-dispatcher": "~4.1.0||~4.2.0||~4.3.0", - "symfony/process": "~4.1.0||~4.2.0||~4.3.0", + "symfony/console": "~4.4.0", + "symfony/event-dispatcher": "~4.4.0", + "symfony/process": "~4.4.0", "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", "webonyx/graphql-php": "^0.13.8", diff --git a/composer.lock b/composer.lock index b6d834610059a..5b94f60fa80a9 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": "8d8e6b87c1f6ac98b3b7331eba9473f3", + "content-hash": "988eebffd81167973e4a51d7efd5be46", "packages": [ { "name": "braintree/braintree_php", @@ -164,16 +164,16 @@ }, { "name": "colinmollenhour/php-redis-session-abstract", - "version": "v1.4.1", + "version": "v1.4.2", "source": { "type": "git", "url": "https://github.com/colinmollenhour/php-redis-session-abstract.git", - "reference": "4949ca28b86037abb44984c77bab9d0a4e075643" + "reference": "669521218794f125c7b668252f4f576eda65e1e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/4949ca28b86037abb44984c77bab9d0a4e075643", - "reference": "4949ca28b86037abb44984c77bab9d0a4e075643", + "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/669521218794f125c7b668252f4f576eda65e1e4", + "reference": "669521218794f125c7b668252f4f576eda65e1e4", "shasum": "" }, "require": { @@ -197,20 +197,20 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2019-03-18T14:43:14+00:00" + "time": "2020-01-08T17:41:01+00:00" }, { "name": "composer/ca-bundle", - "version": "1.2.4", + "version": "1.2.6", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" + "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", - "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e", + "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e", "shasum": "" }, "require": { @@ -221,7 +221,7 @@ "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "psr/log": "^1.0", - "symfony/process": "^2.5 || ^3.0 || ^4.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" }, "type": "library", "extra": { @@ -253,20 +253,20 @@ "ssl", "tls" ], - "time": "2019-08-30T08:44:50+00:00" + "time": "2020-01-13T10:02:55+00:00" }, { "name": "composer/composer", - "version": "1.9.1", + "version": "1.9.2", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "bb01f2180df87ce7992b8331a68904f80439dd2f" + "reference": "7a04aa0201ddaa0b3cf64d41022bd8cdcd7fafeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/bb01f2180df87ce7992b8331a68904f80439dd2f", - "reference": "bb01f2180df87ce7992b8331a68904f80439dd2f", + "url": "https://api.github.com/repos/composer/composer/zipball/7a04aa0201ddaa0b3cf64d41022bd8cdcd7fafeb", + "reference": "7a04aa0201ddaa0b3cf64d41022bd8cdcd7fafeb", "shasum": "" }, "require": { @@ -333,28 +333,27 @@ "dependency", "package" ], - "time": "2019-11-01T16:20:17+00:00" + "time": "2020-01-14T15:30:32+00:00" }, { "name": "composer/semver", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.5 || ^5.0.5" }, "type": "library", "extra": { @@ -395,7 +394,7 @@ "validation", "versioning" ], - "time": "2019-03-19T17:25:45+00:00" + "time": "2020-01-13T12:06:48+00:00" }, { "name": "composer/spdx-licenses", @@ -595,16 +594,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.4.1", + "version": "6.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "0895c932405407fd3a7368b6910c09a24d26db11" + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11", - "reference": "0895c932405407fd3a7368b6910c09a24d26db11", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", "shasum": "" }, "require": { @@ -619,12 +618,13 @@ "psr/log": "^1.1" }, "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.3-dev" + "dev-master": "6.5-dev" } }, "autoload": { @@ -657,7 +657,7 @@ "rest", "web service" ], - "time": "2019-10-23T15:58:00+00:00" + "time": "2019-12-23T11:57:10+00:00" }, { "name": "guzzlehttp/promises", @@ -950,22 +950,22 @@ }, { "name": "magento/composer", - "version": "1.5.0", + "version": "1.6.x-dev", "source": { "type": "git", "url": "https://github.com/magento/composer.git", - "reference": "ea12b95be5c0833b3d9497aaefa08816c19e5dcd" + "reference": "fe738ac9155f550b669b260b3cfa6422eacb53fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/composer/zipball/ea12b95be5c0833b3d9497aaefa08816c19e5dcd", - "reference": "ea12b95be5c0833b3d9497aaefa08816c19e5dcd", + "url": "https://api.github.com/repos/magento/composer/zipball/fe738ac9155f550b669b260b3cfa6422eacb53fa", + "reference": "fe738ac9155f550b669b260b3cfa6422eacb53fa", "shasum": "" }, "require": { "composer/composer": "^1.6", "php": "~7.1.3||~7.2.0||~7.3.0", - "symfony/console": "~4.0.0 || ~4.1.0" + "symfony/console": "~4.4.0" }, "require-dev": { "phpunit/phpunit": "~7.0.0" @@ -982,7 +982,7 @@ "AFL-3.0" ], "description": "Magento composer library helps to instantiate Composer application and run composer commands.", - "time": "2019-07-29T19:52:05+00:00" + "time": "2020-01-17T16:43:51+00:00" }, { "name": "magento/magento-composer-installer", @@ -1065,16 +1065,16 @@ }, { "name": "magento/zendframework1", - "version": "1.14.2", + "version": "1.14.3", "source": { "type": "git", "url": "https://github.com/magento/zf1.git", - "reference": "8221062d42a198e431d183bbe672e5e1a2f98c5f" + "reference": "726855dfb080089dc7bc7b016624129f8e7bc4e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/zf1/zipball/8221062d42a198e431d183bbe672e5e1a2f98c5f", - "reference": "8221062d42a198e431d183bbe672e5e1a2f98c5f", + "url": "https://api.github.com/repos/magento/zf1/zipball/726855dfb080089dc7bc7b016624129f8e7bc4e5", + "reference": "726855dfb080089dc7bc7b016624129f8e7bc4e5", "shasum": "" }, "require": { @@ -1108,20 +1108,20 @@ "ZF1", "framework" ], - "time": "2019-07-26T16:43:11+00:00" + "time": "2019-11-26T15:09:40+00:00" }, { "name": "monolog/monolog", - "version": "1.25.2", + "version": "1.25.3", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287" + "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d5e2fb341cb44f7e2ab639d12a1e5901091ec287", - "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1", + "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1", "shasum": "" }, "require": { @@ -1186,7 +1186,7 @@ "logging", "psr-3" ], - "time": "2019-11-13T10:00:05+00:00" + "time": "2019-12-20T14:15:16+00:00" }, { "name": "paragonie/random_compat", @@ -1235,16 +1235,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.12.1", + "version": "v1.12.2", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "063cae9b3a7323579063e7037720f5b52b56c178" + "reference": "3b953109fdfc821c1979bc829c8b7421721fef82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/063cae9b3a7323579063e7037720f5b52b56c178", - "reference": "063cae9b3a7323579063e7037720f5b52b56c178", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/3b953109fdfc821c1979bc829c8b7421721fef82", + "reference": "3b953109fdfc821c1979bc829c8b7421721fef82", "shasum": "" }, "require": { @@ -1313,7 +1313,7 @@ "secret-key cryptography", "side-channel resistant" ], - "time": "2019-11-07T17:07:24+00:00" + "time": "2019-12-30T03:11:08+00:00" }, { "name": "pelago/emogrifier", @@ -1968,16 +1968,16 @@ }, { "name": "seld/phar-utils", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + "reference": "84715761c35808076b00908a20317a3a8a67d17e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/84715761c35808076b00908a20317a3a8a67d17e", + "reference": "84715761c35808076b00908a20317a3a8a67d17e", "shasum": "" }, "require": { @@ -2008,28 +2008,32 @@ "keywords": [ "phra" ], - "time": "2015-10-13T18:44:15+00:00" + "time": "2020-01-13T10:41:09+00:00" }, { "name": "symfony/console", - "version": "v4.1.12", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02" + "reference": "e9ee09d087e2c88eaf6e5fc0f5c574f64d100e4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e87c798f67dc9fceeb4f3d57847b52d945d1a02", - "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02", + "url": "https://api.github.com/repos/symfony/console/zipball/e9ee09d087e2c88eaf6e5fc0f5c574f64d100e4f", + "reference": "e9ee09d087e2c88eaf6e5fc0f5c574f64d100e4f", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1|^2" }, "conflict": { "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", "symfony/process": "<3.3" }, "provide": { @@ -2037,11 +2041,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" }, "suggest": { "psr/log": "For using the console logger", @@ -2052,7 +2057,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2079,20 +2084,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-01-25T14:34:37+00:00" + "time": "2020-01-10T21:54:01+00:00" }, { "name": "symfony/css-selector", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9" + "reference": "a167b1860995b926d279f9bb538f873e3bfa3465" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9", - "reference": "f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/a167b1860995b926d279f9bb538f873e3bfa3465", + "reference": "a167b1860995b926d279f9bb538f873e3bfa3465", "shasum": "" }, "require": { @@ -2101,7 +2106,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2132,20 +2137,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2019-10-02T08:36:26+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0df002fd4f500392eabd243c2947061a50937287" + "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0df002fd4f500392eabd243c2947061a50937287", - "reference": "0df002fd4f500392eabd243c2947061a50937287", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9e3de195e5bc301704dd6915df55892f6dfc208b", + "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b", "shasum": "" }, "require": { @@ -2161,12 +2166,12 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "^3.4|^4.0", - "symfony/service-contracts": "^1.1", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/dependency-injection": "", @@ -2175,7 +2180,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2202,7 +2207,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-11-03T09:04:05+00:00" + "time": "2020-01-10T21:54:01+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -2264,16 +2269,16 @@ }, { "name": "symfony/filesystem", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", "shasum": "" }, "require": { @@ -2283,7 +2288,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2310,20 +2315,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-08-20T14:07:54+00:00" + "time": "2020-01-21T08:20:44+00:00" }, { "name": "symfony/finder", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f" + "reference": "3a50be43515590faf812fbd7708200aabc327ec3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f", - "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "url": "https://api.github.com/repos/symfony/finder/zipball/3a50be43515590faf812fbd7708200aabc327ec3", + "reference": "3a50be43515590faf812fbd7708200aabc327ec3", "shasum": "" }, "require": { @@ -2332,7 +2337,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2359,20 +2364,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-10-30T12:53:54+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -2384,7 +2389,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -2417,20 +2422,20 @@ "polyfill", "portable" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", "shasum": "" }, "require": { @@ -2442,7 +2447,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -2476,20 +2481,78 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T14:18:11+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.13.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "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.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-11-27T16:25:15+00:00" }, { "name": "symfony/process", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0" + "reference": "f5697ab4cb14a5deed7473819e63141bf5352c36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0", - "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0", + "url": "https://api.github.com/repos/symfony/process/zipball/f5697ab4cb14a5deed7473819e63141bf5352c36", + "reference": "f5697ab4cb14a5deed7473819e63141bf5352c36", "shasum": "" }, "require": { @@ -2498,7 +2561,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -2525,7 +2588,65 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-10-28T17:07:32+00:00" + "time": "2020-01-09T09:50:08+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "144c5e51266b281231e947b51223ba14acf1a749" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "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": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-11-18T17:27:11+00:00" }, { "name": "tedivm/jshrink", @@ -2841,6 +2962,7 @@ "captcha", "zf" ], + "abandoned": "laminas/laminas-captcha", "time": "2019-06-18T09:32:52+00:00" }, { @@ -2894,6 +3016,7 @@ "code", "zf" ], + "abandoned": "laminas/laminas-code", "time": "2019-08-31T14:14:34+00:00" }, { @@ -2950,6 +3073,7 @@ "config", "zf2" ], + "abandoned": "laminas/laminas-config", "time": "2016-02-04T23:01:10+00:00" }, { @@ -3003,6 +3127,7 @@ "console", "zf" ], + "abandoned": "laminas/laminas-console", "time": "2019-02-04T19:48:22+00:00" }, { @@ -3053,20 +3178,21 @@ "crypt", "zf2" ], + "abandoned": "laminas/laminas-crypt", "time": "2016-02-03T23:46:30+00:00" }, { "name": "zendframework/zend-db", - "version": "2.10.0", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-db.git", - "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e" + "reference": "71626f95f6f9ee326e4be3c34228c1c466300a2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", - "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", + "url": "https://api.github.com/repos/zendframework/zend-db/zipball/71626f95f6f9ee326e4be3c34228c1c466300a2c", + "reference": "71626f95f6f9ee326e4be3c34228c1c466300a2c", "shasum": "" }, "require": { @@ -3074,7 +3200,7 @@ "zendframework/zend-stdlib": "^2.7 || ^3.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.25 || ^6.4.4", + "phpunit/phpunit": "^5.7.27 || ^6.5.14", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", @@ -3088,8 +3214,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9-dev", - "dev-develop": "2.10-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" }, "zf": { "component": "Zend\\Db", @@ -3111,7 +3237,8 @@ "db", "zf" ], - "time": "2019-02-25T11:37:45+00:00" + "abandoned": "laminas/laminas-db", + "time": "2019-12-31T19:43:46+00:00" }, { "name": "zendframework/zend-di", @@ -3158,6 +3285,7 @@ "di", "zf2" ], + "abandoned": "laminas/laminas-di", "time": "2016-04-25T20:58:11+00:00" }, { @@ -3220,6 +3348,7 @@ "psr", "psr-7" ], + "abandoned": "laminas/laminas-diactoros", "time": "2019-08-06T17:53:53+00:00" }, { @@ -3265,6 +3394,7 @@ "escaper", "zf" ], + "abandoned": "laminas/laminas-escaper", "time": "2019-09-05T20:03:20+00:00" }, { @@ -3319,6 +3449,7 @@ "events", "zf2" ], + "abandoned": "laminas/laminas-eventmanager", "time": "2018-04-25T15:33:34+00:00" }, { @@ -3382,6 +3513,7 @@ "feed", "zf" ], + "abandoned": "laminas/laminas-feed", "time": "2019-03-05T20:08:49+00:00" }, { @@ -3447,6 +3579,7 @@ "filter", "zf" ], + "abandoned": "laminas/laminas-filter", "time": "2019-08-19T07:08:04+00:00" }, { @@ -3525,20 +3658,21 @@ "form", "zf" ], + "abandoned": "laminas/laminas-form", "time": "2019-10-04T10:46:36+00:00" }, { "name": "zendframework/zend-http", - "version": "2.10.0", + "version": "2.11.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "4b4983178693a8fdda53b0bbee58552e2d2b1ac0" + "reference": "e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/4b4983178693a8fdda53b0bbee58552e2d2b1ac0", - "reference": "4b4983178693a8fdda53b0bbee58552e2d2b1ac0", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a", + "reference": "e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a", "shasum": "" }, "require": { @@ -3559,8 +3693,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" } }, "autoload": { @@ -3580,7 +3714,8 @@ "zend", "zf" ], - "time": "2019-02-19T18:58:14+00:00" + "abandoned": "laminas/laminas-http", + "time": "2019-12-30T20:47:33+00:00" }, { "name": "zendframework/zend-hydrator", @@ -3640,26 +3775,31 @@ "hydrator", "zf" ], + "abandoned": "laminas/laminas-hydrator", "time": "2019-10-04T11:17:36+00:00" }, { "name": "zendframework/zend-i18n", - "version": "2.9.2", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-i18n.git", - "reference": "e17a54b3aee333ab156958f570cde630acee8b07" + "reference": "84038e6a1838b611dcc491b1c40321fa4c3a123c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/e17a54b3aee333ab156958f570cde630acee8b07", - "reference": "e17a54b3aee333ab156958f570cde630acee8b07", + "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/84038e6a1838b611dcc491b1c40321fa4c3a123c", + "reference": "84038e6a1838b611dcc491b1c40321fa4c3a123c", "shasum": "" }, "require": { + "ext-intl": "*", "php": "^5.6 || ^7.0", "zendframework/zend-stdlib": "^2.7 || ^3.0" }, + "conflict": { + "phpspec/prophecy": "<1.9.0" + }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", "zendframework/zend-cache": "^2.6.1", @@ -3672,7 +3812,6 @@ "zendframework/zend-view": "^2.6.3" }, "suggest": { - "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", "zendframework/zend-cache": "Zend\\Cache component", "zendframework/zend-config": "Zend\\Config component", "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", @@ -3685,8 +3824,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" }, "zf": { "component": "Zend\\I18n", @@ -3708,7 +3847,8 @@ "i18n", "zf" ], - "time": "2019-09-30T12:04:37+00:00" + "abandoned": "laminas/laminas-i18n", + "time": "2019-12-12T14:08:22+00:00" }, { "name": "zendframework/zend-inputfilter", @@ -3765,6 +3905,7 @@ "inputfilter", "zf" ], + "abandoned": "laminas/laminas-inputfilter", "time": "2019-08-28T19:45:32+00:00" }, { @@ -3820,6 +3961,7 @@ "json", "zf2" ], + "abandoned": "laminas/laminas-json", "time": "2016-02-04T21:20:26+00:00" }, { @@ -3865,25 +4007,26 @@ "loader", "zf" ], + "abandoned": "laminas/laminas-loader", "time": "2019-09-04T19:38:14+00:00" }, { "name": "zendframework/zend-log", - "version": "2.11.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-log.git", - "reference": "cb278772afdacb1924342248a069330977625ae6" + "reference": "e5ec088dc8a7b4d96a3a6627761f720a738a36b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/cb278772afdacb1924342248a069330977625ae6", - "reference": "cb278772afdacb1924342248a069330977625ae6", + "url": "https://api.github.com/repos/zendframework/zend-log/zipball/e5ec088dc8a7b4d96a3a6627761f720a738a36b8", + "reference": "e5ec088dc8a7b4d96a3a6627761f720a738a36b8", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "psr/log": "^1.0", + "psr/log": "^1.1.2", "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", "zendframework/zend-stdlib": "^2.7 || ^3.0" }, @@ -3911,8 +4054,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.11.x-dev", - "dev-develop": "2.12.x-dev" + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" }, "zf": { "component": "Zend\\Log", @@ -3935,7 +4078,8 @@ "logging", "zf" ], - "time": "2019-08-23T21:28:18+00:00" + "abandoned": "laminas/laminas-log", + "time": "2019-12-27T16:18:31+00:00" }, { "name": "zendframework/zend-mail", @@ -3997,6 +4141,7 @@ "mail", "zf" ], + "abandoned": "laminas/laminas-mail", "time": "2018-06-07T13:37:07+00:00" }, { @@ -4047,6 +4192,7 @@ "math", "zf2" ], + "abandoned": "laminas/laminas-math", "time": "2018-12-04T15:34:17+00:00" }, { @@ -4097,6 +4243,7 @@ "mime", "zf" ], + "abandoned": "laminas/laminas-mime", "time": "2019-10-16T19:30:37+00:00" }, { @@ -4156,6 +4303,7 @@ "modulemanager", "zf" ], + "abandoned": "laminas/laminas-modulemanager", "time": "2019-10-28T13:29:38+00:00" }, { @@ -4251,6 +4399,7 @@ "mvc", "zf2" ], + "abandoned": "laminas/laminas-mvc", "time": "2018-05-03T13:13:41+00:00" }, { @@ -4300,6 +4449,7 @@ "psr", "psr-7" ], + "abandoned": "laminas/laminas-psr7bridge", "time": "2016-05-10T21:44:39+00:00" }, { @@ -4357,6 +4507,7 @@ "serializer", "zf" ], + "abandoned": "laminas/laminas-serializer", "time": "2019-10-19T08:06:30+00:00" }, { @@ -4404,6 +4555,7 @@ "server", "zf" ], + "abandoned": "laminas/laminas-server", "time": "2019-10-16T18:27:05+00:00" }, { @@ -4456,6 +4608,7 @@ "servicemanager", "zf2" ], + "abandoned": "laminas/laminas-servicemanager", "time": "2018-06-22T14:49:54+00:00" }, { @@ -4523,6 +4676,7 @@ "session", "zf" ], + "abandoned": "laminas/laminas-session", "time": "2019-10-28T19:40:43+00:00" }, { @@ -4576,6 +4730,7 @@ "soap", "zf2" ], + "abandoned": "laminas/laminas-soap", "time": "2019-04-30T16:45:35+00:00" }, { @@ -4622,6 +4777,7 @@ "stdlib", "zf" ], + "abandoned": "laminas/laminas-stdlib", "time": "2018-08-28T21:34:05+00:00" }, { @@ -4670,6 +4826,7 @@ "text", "zf" ], + "abandoned": "laminas/laminas-text", "time": "2019-10-16T20:36:27+00:00" }, { @@ -4717,29 +4874,32 @@ "uri", "zf" ], + "abandoned": "laminas/laminas-uri", "time": "2019-10-07T13:35:33+00:00" }, { "name": "zendframework/zend-validator", - "version": "2.12.2", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", - "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62" + "reference": "b54acef1f407741c5347f2a97f899ab21f2229ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/fd24920c2afcf2a70d11f67c3457f8f509453a62", - "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/b54acef1f407741c5347f2a97f899ab21f2229ef", + "reference": "b54acef1f407741c5347f2a97f899ab21f2229ef", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", - "php": "^5.6 || ^7.0", + "php": "^7.1", "zendframework/zend-stdlib": "^3.2.1" }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", "psr/http-message": "^1.0", "zendframework/zend-cache": "^2.6.1", "zendframework/zend-coding-standard": "~1.0.0", @@ -4767,8 +4927,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.12.x-dev", - "dev-develop": "2.13.x-dev" + "dev-master": "2.13.x-dev", + "dev-develop": "2.14.x-dev" }, "zf": { "component": "Zend\\Validator", @@ -4790,20 +4950,21 @@ "validator", "zf" ], - "time": "2019-10-29T08:33:25+00:00" + "abandoned": "laminas/laminas-validator", + "time": "2019-12-28T04:07:18+00:00" }, { "name": "zendframework/zend-view", - "version": "2.11.3", + "version": "2.11.4", "source": { "type": "git", "url": "https://github.com/zendframework/zend-view.git", - "reference": "e766457bd6ce13c5354e443bb949511b6904d7f5" + "reference": "a8b1b2d9b52e191539be861a6529f8c8a0c06b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/e766457bd6ce13c5354e443bb949511b6904d7f5", - "reference": "e766457bd6ce13c5354e443bb949511b6904d7f5", + "url": "https://api.github.com/repos/zendframework/zend-view/zipball/a8b1b2d9b52e191539be861a6529f8c8a0c06b9d", + "reference": "a8b1b2d9b52e191539be861a6529f8c8a0c06b9d", "shasum": "" }, "require": { @@ -4877,7 +5038,8 @@ "view", "zf" ], - "time": "2019-10-11T21:10:04+00:00" + "abandoned": "laminas/laminas-view", + "time": "2019-12-04T08:40:50+00:00" } ], "packages-dev": [ @@ -4934,23 +5096,23 @@ }, { "name": "allure-framework/allure-php-api", - "version": "1.1.5", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/allure-framework/allure-php-commons.git", - "reference": "c7a675823ad75b8e02ddc364baae21668e7c4e88" + "reference": "2c62a70d60eca190253a6b80e9aa4c2ebc2e6e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/c7a675823ad75b8e02ddc364baae21668e7c4e88", - "reference": "c7a675823ad75b8e02ddc364baae21668e7c4e88", + "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/2c62a70d60eca190253a6b80e9aa4c2ebc2e6e7f", + "reference": "2c62a70d60eca190253a6b80e9aa4c2ebc2e6e7f", "shasum": "" }, "require": { - "jms/serializer": "^0.16.0", + "jms/serializer": "^0.16 || ^1.0", "php": ">=5.4.0", - "ramsey/uuid": "^3.0.0", - "symfony/http-foundation": "^2.0" + "ramsey/uuid": "^3.0", + "symfony/http-foundation": "^2.0 || ^3.0 || ^4.0" }, "require-dev": { "phpunit/phpunit": "^4.0.0" @@ -4983,7 +5145,7 @@ "php", "report" ], - "time": "2018-05-25T14:02:11+00:00" + "time": "2020-01-09T10:26:09+00:00" }, { "name": "allure-framework/allure-phpunit", @@ -5280,16 +5442,16 @@ }, { "name": "codeception/phpunit-wrapper", - "version": "6.7.0", + "version": "6.8.0", "source": { "type": "git", "url": "https://github.com/Codeception/phpunit-wrapper.git", - "reference": "93f59e028826464eac086052fa226e58967f6907" + "reference": "20e054e9cee8de0523322367bcccde8e6a3db263" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/93f59e028826464eac086052fa226e58967f6907", - "reference": "93f59e028826464eac086052fa226e58967f6907", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/20e054e9cee8de0523322367bcccde8e6a3db263", + "reference": "20e054e9cee8de0523322367bcccde8e6a3db263", "shasum": "" }, "require": { @@ -5322,7 +5484,7 @@ } ], "description": "PHPUnit classes used by Codeception", - "time": "2019-08-18T15:43:35+00:00" + "time": "2020-01-03T08:01:16+00:00" }, { "name": "codeception/stub", @@ -6162,16 +6324,16 @@ }, { "name": "doctrine/cache", - "version": "1.9.1", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "89a5c76c39c292f7798f964ab3c836c3f8192a55" + "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/89a5c76c39c292f7798f964ab3c836c3f8192a55", - "reference": "89a5c76c39c292f7798f964ab3c836c3f8192a55", + "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62", + "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62", "shasum": "" }, "require": { @@ -6238,10 +6400,9 @@ "memcached", "php", "redis", - "riak", "xcache" ], - "time": "2019-11-15T14:31:57+00:00" + "time": "2019-11-29T15:36:20+00:00" }, { "name": "doctrine/inflector", @@ -6486,20 +6647,21 @@ "selenium", "webdriver" ], + "abandoned": "php-webdriver/webdriver", "time": "2019-06-13T08:02:18+00:00" }, { "name": "flow/jsonpath", - "version": "0.4.0", + "version": "0.5.0", "source": { "type": "git", "url": "https://github.com/FlowCommunications/JSONPath.git", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" + "reference": "b9738858c75d008c1211612b973e9510f8b7f8ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", + "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/b9738858c75d008c1211612b973e9510f8b7f8ea", + "reference": "b9738858c75d008c1211612b973e9510f8b7f8ea", "shasum": "" }, "require": { @@ -6507,7 +6669,7 @@ }, "require-dev": { "peekmo/jsonpath": "dev-master", - "phpunit/phpunit": "^4.0" + "phpunit/phpunit": "^7.0" }, "type": "library", "autoload": { @@ -6527,7 +6689,7 @@ } ], "description": "JSONPath implementation for parsing, searching and flattening arrays", - "time": "2018-03-04T16:39:47+00:00" + "time": "2019-07-15T17:23:22+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -6620,16 +6782,16 @@ }, { "name": "fzaninotto/faker", - "version": "v1.9.0", + "version": "v1.9.1", "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "27a216cbe72327b2d6369fab721a5843be71e57d" + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/27a216cbe72327b2d6369fab721a5843be71e57d", - "reference": "27a216cbe72327b2d6369fab721a5843be71e57d", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", "shasum": "" }, "require": { @@ -6642,7 +6804,9 @@ }, "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "1.9-dev" + } }, "autoload": { "psr-4": { @@ -6664,7 +6828,7 @@ "faker", "fixtures" ], - "time": "2019-11-14T13:13:06+00:00" + "time": "2019-12-12T13:22:17+00:00" }, { "name": "grasmash/expander", @@ -6761,48 +6925,6 @@ "description": "Expands internal property references in a yaml file.", "time": "2017-12-16T16:06:03+00:00" }, - { - "name": "ircmaxell/password-compat", - "version": "v1.0.4", - "source": { - "type": "git", - "url": "https://github.com/ircmaxell/password_compat.git", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "4.*" - }, - "type": "library", - "autoload": { - "files": [ - "lib/password.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anthony Ferrara", - "email": "ircmaxell@php.net", - "homepage": "http://blog.ircmaxell.com" - } - ], - "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", - "homepage": "https://github.com/ircmaxell/password_compat", - "keywords": [ - "hashing", - "password" - ], - "time": "2014-11-20T16:49:30+00:00" - }, { "name": "jms/metadata", "version": "1.7.0", @@ -6895,44 +7017,56 @@ }, { "name": "jms/serializer", - "version": "0.16.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9" + "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", + "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", "shasum": "" }, "require": { - "doctrine/annotations": "1.*", - "jms/metadata": "~1.1", + "doctrine/annotations": "^1.0", + "doctrine/instantiator": "^1.0.3", + "jms/metadata": "^1.3", "jms/parser-lib": "1.*", - "php": ">=5.3.2", - "phpcollection/phpcollection": "~0.1" + "php": "^5.5|^7.0", + "phpcollection/phpcollection": "~0.1", + "phpoption/phpoption": "^1.1" + }, + "conflict": { + "twig/twig": "<1.12" }, "require-dev": { "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "~1.0.1", - "jackalope/jackalope-doctrine-dbal": "1.0.*", + "doctrine/phpcr-odm": "^1.3|^2.0", + "ext-pdo_sqlite": "*", + "jackalope/jackalope-doctrine-dbal": "^1.1.5", + "phpunit/phpunit": "^4.8|^5.0", "propel/propel1": "~1.7", - "symfony/filesystem": "2.*", - "symfony/form": "~2.1", - "symfony/translation": "~2.0", - "symfony/validator": "~2.0", - "symfony/yaml": "2.*", - "twig/twig": ">=1.8,<2.0-dev" + "psr/container": "^1.0", + "symfony/dependency-injection": "^2.7|^3.3|^4.0", + "symfony/expression-language": "^2.6|^3.0", + "symfony/filesystem": "^2.1", + "symfony/form": "~2.1|^3.0", + "symfony/translation": "^2.1|^3.0", + "symfony/validator": "^2.2|^3.0", + "symfony/yaml": "^2.1|^3.0", + "twig/twig": "~1.12|~2.0" }, "suggest": { + "doctrine/cache": "Required if you like to use cache functionality.", + "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", "symfony/yaml": "Required if you'd like to serialize data to YAML format." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.15-dev" + "dev-1.x": "1.14-dev" } }, "autoload": { @@ -6942,14 +7076,16 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache2" + "MIT" ], "authors": [ { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", - "role": "Developer of wrapped JMSSerializerBundle" + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" } ], "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", @@ -6961,7 +7097,7 @@ "serialization", "xml" ], - "time": "2014-03-18T08:39:00+00:00" + "time": "2019-04-17T08:12:16+00:00" }, { "name": "league/container", @@ -7030,16 +7166,16 @@ }, { "name": "league/flysystem", - "version": "1.0.57", + "version": "1.0.63", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "0e9db7f0b96b9f12dcf6f65bc34b72b1a30ea55a" + "reference": "8132daec326565036bc8e8d1876f77ec183a7bd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0e9db7f0b96b9f12dcf6f65bc34b72b1a30ea55a", - "reference": "0e9db7f0b96b9f12dcf6f65bc34b72b1a30ea55a", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8132daec326565036bc8e8d1876f77ec183a7bd6", + "reference": "8132daec326565036bc8e8d1876f77ec183a7bd6", "shasum": "" }, "require": { @@ -7110,7 +7246,7 @@ "sftp", "storage" ], - "time": "2019-10-16T21:01:05+00:00" + "time": "2020-01-04T16:30:31+00:00" }, { "name": "lusitanian/oauth", @@ -7344,16 +7480,16 @@ }, { "name": "mustache/mustache", - "version": "v2.12.0", + "version": "v2.13.0", "source": { "type": "git", "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" + "reference": "e95c5a008c23d3151d59ea72484d4f72049ab7f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/e95c5a008c23d3151d59ea72484d4f72049ab7f4", + "reference": "e95c5a008c23d3151d59ea72484d4f72049ab7f4", "shasum": "" }, "require": { @@ -7386,20 +7522,20 @@ "mustache", "templating" ], - "time": "2017-07-11T12:54:05+00:00" + "time": "2019-11-23T21:40:31+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.3", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", "shasum": "" }, "require": { @@ -7434,59 +7570,7 @@ "object", "object graph" ], - "time": "2019-08-09T12:45:53+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.3.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "0.0.5", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "time": "2019-11-08T13:50:10+00:00" + "time": "2020-01-17T21:11:47+00:00" }, { "name": "pdepend/pdepend", @@ -7731,16 +7815,16 @@ }, { "name": "phpcompatibility/php-compatibility", - "version": "9.3.4", + "version": "9.3.5", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", - "reference": "1f37659196e4f3113ea506a7efba201c52303bf1" + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/1f37659196e4f3113ea506a7efba201c52303bf1", - "reference": "1f37659196e4f3113ea506a7efba201c52303bf1", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", + "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", "shasum": "" }, "require": { @@ -7785,7 +7869,7 @@ "phpcs", "standards" ], - "time": "2019-11-15T04:12:02+00:00" + "time": "2019-12-27T09:44:58+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -7841,16 +7925,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.2", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", "shasum": "" }, "require": { @@ -7862,6 +7946,7 @@ "require-dev": { "doctrine/instantiator": "^1.0.5", "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", "phpunit/phpunit": "^6.4" }, "type": "library", @@ -7888,7 +7973,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-09-12T14:27:41+00:00" + "time": "2019-12-28T18:55:12+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -8007,33 +8092,34 @@ }, { "name": "phpoption/phpoption", - "version": "1.5.2", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "2ba2586380f8d2b44ad1b9feb61c371020b27793" + "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/2ba2586380f8d2b44ad1b9feb61c371020b27793", - "reference": "2ba2586380f8d2b44ad1b9feb61c371020b27793", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959", + "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^5.5.9 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.7|^5.0" + "bamarni/composer-bin-plugin": "^1.3", + "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.7-dev" } }, "autoload": { - "psr-0": { - "PhpOption\\": "src/" + "psr-4": { + "PhpOption\\": "src/PhpOption/" } }, "notification-url": "https://packagist.org/downloads/", @@ -8044,6 +8130,10 @@ { "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" } ], "description": "Option Type for PHP", @@ -8053,37 +8143,37 @@ "php", "type" ], - "time": "2019-11-06T22:27:00+00:00" + "time": "2019-12-15T19:35:24+00:00" }, { "name": "phpspec/prophecy", - "version": "1.9.0", + "version": "v1.10.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -8116,24 +8206,23 @@ "spy", "stub" ], - "time": "2019-10-03T11:07:50+00:00" + "time": "2020-01-20T15:57:02+00:00" }, { "name": "phpstan/phpstan", - "version": "0.12.3", + "version": "0.12.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c15a6ea55da71d8133399306f560cfe4d30301b7" + "reference": "07fa7958027fd98c567099bbcda5d6a0f2ec5197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c15a6ea55da71d8133399306f560cfe4d30301b7", - "reference": "c15a6ea55da71d8133399306f560cfe4d30301b7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/07fa7958027fd98c567099bbcda5d6a0f2ec5197", + "reference": "07fa7958027fd98c567099bbcda5d6a0f2ec5197", "shasum": "" }, "require": { - "nikic/php-parser": "^4.3.0", "php": "^7.1" }, "bin": [ @@ -8156,7 +8245,7 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2019-12-14T13:41:17+00:00" + "time": "2020-01-20T21:59:06+00:00" }, { "name": "phpunit/php-code-coverage", @@ -8925,23 +9014,27 @@ }, { "name": "sebastian/finder-facade", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/finder-facade.git", - "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f" + "reference": "167c45d131f7fc3d159f56f191a0a22228765e16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", - "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", + "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/167c45d131f7fc3d159f56f191a0a22228765e16", + "reference": "167c45d131f7fc3d159f56f191a0a22228765e16", "shasum": "" }, "require": { - "symfony/finder": "~2.3|~3.0|~4.0", - "theseer/fdomdocument": "~1.3" + "php": "^7.1", + "symfony/finder": "^2.3|^3.0|^4.0|^5.0", + "theseer/fdomdocument": "^1.6" }, "type": "library", + "extra": { + "branch-alias": [] + }, "autoload": { "classmap": [ "src/" @@ -8960,7 +9053,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2017-11-18T17:31:49+00:00" + "time": "2020-01-16T08:08:45+00:00" }, { "name": "sebastian/global-state", @@ -9346,27 +9439,27 @@ }, { "name": "symfony/browser-kit", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "b14fa08508afd152257d5dcc7adb5f278654d972" + "reference": "45cae6dd8683d2de56df7ec23638e9429c70135f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/b14fa08508afd152257d5dcc7adb5f278654d972", - "reference": "b14fa08508afd152257d5dcc7adb5f278654d972", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/45cae6dd8683d2de56df7ec23638e9429c70135f", + "reference": "45cae6dd8683d2de56df7ec23638e9429c70135f", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/dom-crawler": "~3.4|~4.0" + "symfony/dom-crawler": "^3.4|^4.0|^5.0" }, "require-dev": { - "symfony/css-selector": "~3.4|~4.0", - "symfony/http-client": "^4.3", - "symfony/mime": "^4.3", - "symfony/process": "~3.4|~4.0" + "symfony/css-selector": "^3.4|^4.0|^5.0", + "symfony/http-client": "^4.3|^5.0", + "symfony/mime": "^4.3|^5.0", + "symfony/process": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/process": "" @@ -9374,7 +9467,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -9401,36 +9494,36 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2019-10-28T17:07:32+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/config", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "8267214841c44d315a55242ea867684eb43c42ce" + "reference": "4d3979f54472637169080f802dc82197e21fdcce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/8267214841c44d315a55242ea867684eb43c42ce", - "reference": "8267214841c44d315a55242ea867684eb43c42ce", + "url": "https://api.github.com/repos/symfony/config/zipball/4d3979f54472637169080f802dc82197e21fdcce", + "reference": "4d3979f54472637169080f802dc82197e21fdcce", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0", + "symfony/filesystem": "^3.4|^4.0|^5.0", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/finder": "<3.4" }, "require-dev": { - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/messenger": "~4.1", - "symfony/yaml": "~3.4|~4.0" + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" @@ -9438,7 +9531,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -9465,29 +9558,29 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-11-08T08:31:27+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "80c6d9e19467dfbba14f830ed478eb592ce51b64" + "reference": "6faf589e1f6af78692aed3ab6b3c336c58d5d83c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/80c6d9e19467dfbba14f830ed478eb592ce51b64", - "reference": "80c6d9e19467dfbba14f830ed478eb592ce51b64", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6faf589e1f6af78692aed3ab6b3c336c58d5d83c", + "reference": "6faf589e1f6af78692aed3ab6b3c336c58d5d83c", "shasum": "" }, "require": { "php": "^7.1.3", "psr/container": "^1.0", - "symfony/service-contracts": "^1.1.6" + "symfony/service-contracts": "^1.1.6|^2" }, "conflict": { - "symfony/config": "<4.3", + "symfony/config": "<4.3|>=5.0", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" @@ -9498,8 +9591,8 @@ }, "require-dev": { "symfony/config": "^4.3", - "symfony/expression-language": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/yaml": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/config": "", @@ -9511,7 +9604,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -9538,20 +9631,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-11-08T16:22:27+00:00" + "time": "2020-01-21T07:39:36+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4b9efd5708c3a38593e19b6a33e40867f4f89d72" + "reference": "b66fe8ccc850ea11c4cd31677706c1219768bea1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4b9efd5708c3a38593e19b6a33e40867f4f89d72", - "reference": "4b9efd5708c3a38593e19b6a33e40867f4f89d72", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b66fe8ccc850ea11c4cd31677706c1219768bea1", + "reference": "b66fe8ccc850ea11c4cd31677706c1219768bea1", "shasum": "" }, "require": { @@ -9564,7 +9657,7 @@ }, "require-dev": { "masterminds/html5": "^2.6", - "symfony/css-selector": "~3.4|~4.0" + "symfony/css-selector": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/css-selector": "" @@ -9572,7 +9665,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -9599,35 +9692,35 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2019-10-28T17:07:32+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/http-foundation", - "version": "v2.8.52", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "3929d9fe8148d17819ad0178c748b8d339420709" + "reference": "c33998709f3fe9b8e27e0277535b07fbf6fde37a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3929d9fe8148d17819ad0178c748b8d339420709", - "reference": "3929d9fe8148d17819ad0178c748b8d339420709", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c33998709f3fe9b8e27e0277535b07fbf6fde37a", + "reference": "c33998709f3fe9b8e27e0277535b07fbf6fde37a", "shasum": "" }, "require": { - "php": ">=5.3.9", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php54": "~1.0", - "symfony/polyfill-php55": "~1.0" + "php": "^7.1.3", + "symfony/mime": "^4.3|^5.0", + "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { - "symfony/expression-language": "~2.4|~3.0.0" + "predis/predis": "~1.0", + "symfony/expression-language": "^3.4|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -9654,34 +9747,43 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-11-12T12:34:41+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { - "name": "symfony/options-resolver", - "version": "v4.3.8", + "name": "symfony/mime", + "version": "v5.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4" + "url": "https://github.com/symfony/mime.git", + "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f46c7fc8e207bd8a2188f54f8738f232533765a4", - "reference": "f46c7fc8e207bd8a2188f54f8738f232533765a4", + "url": "https://api.github.com/repos/symfony/mime/zipball/2a3c7fee1f1a0961fa9cf360d5da553d05095e59", + "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.0-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" + "Symfony\\Component\\Mime\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -9701,47 +9803,43 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "A library to manipulate MIME messages", "homepage": "https://symfony.com", "keywords": [ - "config", - "configuration", - "options" + "mime", + "mime-type" ], - "time": "2019-10-28T20:59:01+00:00" + "time": "2020-01-04T14:08:26+00:00" }, { - "name": "symfony/polyfill-php54", - "version": "v1.12.0", + "name": "symfony/options-resolver", + "version": "v4.4.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "a043bcced870214922fbb4bf22679d431ec0296a" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/a043bcced870214922fbb4bf22679d431ec0296a", - "reference": "a043bcced870214922fbb4bf22679d431ec0296a", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0", + "reference": "9a02d6662660fe7bfadad63b5f0b0718d4c8b6b0", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "4.4-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php54\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -9750,51 +9848,54 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 5.4+ features to lower PHP versions", + "description": "Symfony OptionsResolver Component", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "config", + "configuration", + "options" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { - "name": "symfony/polyfill-php55", - "version": "v1.12.0", + "name": "symfony/polyfill-intl-idn", + "version": "v1.13.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "548bb39407e78e54f785b4e18c7e0d5d9e493265" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/548bb39407e78e54f785b4e18c7e0d5d9e493265", - "reference": "548bb39407e78e54f785b4e18c7e0d5d9e493265", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", "shasum": "" }, "require": { - "ircmaxell/password-compat": "~1.0", - "php": ">=5.3.3" + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php55\\": "" + "Symfony\\Polyfill\\Intl\\Idn\\": "" }, "files": [ "bootstrap.php" @@ -9806,36 +9907,38 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "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": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "54b4c428a0054e254223797d2713c31e08610831" + "reference": "af23c7bb26a73b850840823662dda371484926c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", - "reference": "54b4c428a0054e254223797d2713c31e08610831", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/af23c7bb26a73b850840823662dda371484926c4", + "reference": "af23c7bb26a73b850840823662dda371484926c4", "shasum": "" }, "require": { @@ -9845,7 +9948,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -9881,20 +9984,20 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "04ce3335667451138df4307d6a9b61565560199e" + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", - "reference": "04ce3335667451138df4307d6a9b61565560199e", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", "shasum": "" }, "require": { @@ -9903,7 +10006,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -9936,88 +10039,30 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v1.1.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffc7f5692092df31515df2a5ecf3b7302b3ddacf", - "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "psr/container": "^1.0" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "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": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "time": "2019-10-14T12:27:06+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "e96c259de6abcd0cead71f0bf4d730d53ee464d0" + "reference": "abc08d7c48987829bac301347faa10f7e8bbf4fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e96c259de6abcd0cead71f0bf4d730d53ee464d0", - "reference": "e96c259de6abcd0cead71f0bf4d730d53ee464d0", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/abc08d7c48987829bac301347faa10f7e8bbf4fb", + "reference": "abc08d7c48987829bac301347faa10f7e8bbf4fb", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/service-contracts": "^1.0" + "symfony/service-contracts": "^1.0|^2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -10044,20 +10089,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-11-05T14:48:09+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/yaml", - "version": "v4.3.8", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "324cf4b19c345465fad14f3602050519e09e361d" + "reference": "cd014e425b3668220adb865f53bff64b3ad21767" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/324cf4b19c345465fad14f3602050519e09e361d", - "reference": "324cf4b19c345465fad14f3602050519e09e361d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/cd014e425b3668220adb865f53bff64b3ad21767", + "reference": "cd014e425b3668220adb865f53bff64b3ad21767", "shasum": "" }, "require": { @@ -10068,7 +10113,7 @@ "symfony/console": "<3.4" }, "require-dev": { - "symfony/console": "~3.4|~4.0" + "symfony/console": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -10076,7 +10121,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "4.4-dev" } }, "autoload": { @@ -10103,7 +10148,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-10-30T12:58:49+00:00" + "time": "2020-01-21T11:12:16+00:00" }, { "name": "theseer/fdomdocument", @@ -10238,31 +10283,29 @@ }, { "name": "webmozart/assert", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", "shasum": "" }, "require": { "php": "^5.3.3 || ^7.0", "symfony/polyfill-ctype": "^1.8" }, + "conflict": { + "vimeo/psalm": "<3.6.0" + }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -10284,7 +10327,7 @@ "check", "validate" ], - "time": "2019-08-24T08:43:50+00:00" + "time": "2019-11-24T13:36:37+00:00" }, { "name": "weew/helpers-array", @@ -10327,6 +10370,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "magento/composer": 20, "phpmd/phpmd": 0 }, "prefer-stable": true, diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerCheckConfigurableProductInWishlistActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerCheckConfigurableProductInWishlistActionGroup.xml new file mode 100644 index 0000000000000..8ea33d82f278a --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerCheckConfigurableProductInWishlistActionGroup.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"> + <!-- Check configurable product in wishlist --> + <actionGroup name="StorefrontCustomerCheckConfigurableProductInWishlistActionGroup"> + <annotations> + <description>Validates that the provided Configurable Product and Product Option is present in the Storefront Wish List.</description> + </annotations> + <arguments> + <argument name="productVar"/> + <argument name="optionProductVar"/> + </arguments> + + <waitForElement selector="{{StorefrontCustomerWishlistProductSection.ProductTitleByName(productVar.name)}}" time="30" stepKey="assertWishlistProductName"/> + <see userInput="${{optionProductVar.price}}.00" selector="{{StorefrontCustomerWishlistProductSection.ProductPriceByName(productVar.name)}}" stepKey="AssertWishlistProductPrice"/> + <moveMouseOver selector="{{StorefrontCustomerWishlistProductSection.ProductInfoByName(productVar.name)}}" stepKey="wishlistMoveMouseOverProduct"/> + <seeElement selector="{{StorefrontCustomerWishlistProductSection.ProductAddToCartByName(productVar.name)}}" stepKey="AssertWishlistAddToCart"/> + <seeElement selector="{{StorefrontCustomerWishlistProductSection.ProductImageByName(productVar.name)}}" stepKey="AssertWishlistProductImage"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerCheckConfigurableProductInWishlistSidebarActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerCheckConfigurableProductInWishlistSidebarActionGroup.xml new file mode 100644 index 0000000000000..604e97bf0a515 --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerCheckConfigurableProductInWishlistSidebarActionGroup.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"> + <!-- Check configurable product in wishlist sidebar --> + <actionGroup name="StorefrontCustomerCheckConfigurableProductInWishlistSidebarActionGroup"> + <annotations> + <description>Validates that the provided Configurable Product and Product Option is present in the Storefront Wish List sidebar.</description> + </annotations> + <arguments> + <argument name="productVar"/> + <argument name="optionProductVar"/> + </arguments> + + <waitForElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductTitleByName(productVar.name)}}" time="30" stepKey="assertWishlistSidebarProductName"/> + <see userInput="${{optionProductVar.price}}.00" selector="{{StorefrontCustomerWishlistSidebarSection.ProductPriceByName(productVar.name)}}" stepKey="AssertWishlistSidebarProductPrice"/> + <seeElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductAddToCartByName(productVar.name)}}" stepKey="AssertWishlistSidebarAddToCart"/> + <seeElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductImageByName(productVar.name)}}" stepKey="AssertWishlistSidebarProductImage"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerWishlistActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/_Deprecated_ActionGroup.xml similarity index 92% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerWishlistActionGroup.xml rename to dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/_Deprecated_ActionGroup.xml index 598ff8deabcc0..e00a96b41b974 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/StorefrontCustomerWishlistActionGroup.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/ActionGroup/_Deprecated_ActionGroup.xml @@ -7,8 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <!-- Check configurable product in wishlist --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontCustomerCheckConfigurableProductInWishlist"> <annotations> <description>Validates that the provided Configurable Product and Product Option is present in the Storefront Wish List.</description> @@ -24,8 +23,6 @@ <seeElement selector="{{StorefrontCustomerWishlistProductSection.ProductAddToCartByName(productVar.name)}}" stepKey="AssertWishlistAddToCart"/> <seeElement selector="{{StorefrontCustomerWishlistProductSection.ProductImageByName(productVar.name)}}" stepKey="AssertWishlistProductImage"/> </actionGroup> - - <!-- Check configurable product in wishlist sidebar --> <actionGroup name="StorefrontCustomerCheckConfigurableProductInWishlistSidebar"> <annotations> <description>Validates that the provided Configurable Product and Product Option is present in the Storefront Wish List sidebar.</description> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml index cb3d9edbc1cbb..ec6edcea3b357 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml @@ -17,11 +17,11 @@ <actionGroup ref="StorefrontCustomerAddProductToWishlistActionGroup" after="wishlistClickConfigurableProduct" stepKey="wishlistAddConfigurableProductToWishlist"> <argument name="productVar" value="$$createConfigProduct$$"/> </actionGroup> - <actionGroup ref="StorefrontCustomerCheckConfigurableProductInWishlist" after="wishlistAddConfigurableProductToWishlist" stepKey="wishlistCheckConfigurableProductInWishlist"> + <actionGroup ref="StorefrontCustomerCheckConfigurableProductInWishlistActionGroup" after="wishlistAddConfigurableProductToWishlist" stepKey="wishlistCheckConfigurableProductInWishlist"> <argument name="productVar" value="$$createConfigProduct$$"/> <argument name="optionProductVar" value="$$createConfigChildProduct1$$"/> </actionGroup> - <actionGroup ref="StorefrontCustomerCheckConfigurableProductInWishlistSidebar" after="wishlistCheckConfigurableProductInWishlist" stepKey="wishlistCheckConfigurableProductInWishlistSidebar"> + <actionGroup ref="StorefrontCustomerCheckConfigurableProductInWishlistSidebarActionGroup" after="wishlistCheckConfigurableProductInWishlist" stepKey="wishlistCheckConfigurableProductInWishlistSidebar"> <argument name="productVar" value="$$createConfigProduct$$"/> <argument name="optionProductVar" value="$$createConfigChildProduct1$$"/> </actionGroup> diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 9ee3b3baa5fc2..9ac5f6959d12e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -42,7 +42,7 @@ public function testFilterForNonExistingCategory() { products(filter: {category_id: {eq: "99999999"}}) { filters { - name + name } } } @@ -204,7 +204,7 @@ private function getQueryProductsWithArrayOfCustomAttributes($attributeCode, $fi { return <<<QUERY { - products(filter:{ + products(filter:{ $attributeCode: {in:["{$firstOption}", "{$secondOption}"]} } pageSize: 3 @@ -212,7 +212,7 @@ private function getQueryProductsWithArrayOfCustomAttributes($attributeCode, $fi ) { total_count - items + items { name sku @@ -225,14 +225,14 @@ private function getQueryProductsWithArrayOfCustomAttributes($attributeCode, $fi filters{ name request_var - filter_items_count + filter_items_count filter_items{ label items_count value_string __typename } - } + } aggregations{ attribute_code count @@ -243,8 +243,8 @@ private function getQueryProductsWithArrayOfCustomAttributes($attributeCode, $fi count } } - - } + + } } QUERY; } @@ -262,16 +262,16 @@ public function testFilterProductsByDropDownCustomAttribute() $optionValue = $this->getDefaultAttributeOptionValue($attributeCode); $query = <<<QUERY { - products(filter:{ + products(filter:{ $attributeCode: {eq: "{$optionValue}"} } - + pageSize: 3 currentPage: 1 ) { total_count - items + items { name sku @@ -284,14 +284,14 @@ public function testFilterProductsByDropDownCustomAttribute() filters{ name request_var - filter_items_count + filter_items_count filter_items{ label items_count value_string __typename } - + } aggregations{ attribute_code @@ -304,8 +304,8 @@ public function testFilterProductsByDropDownCustomAttribute() value } } - - } + + } } QUERY; @@ -365,7 +365,7 @@ private function reIndexAndCleanCache() : void $appDir = dirname(Bootstrap::getInstance()->getAppTempDir()); $out = ''; // phpcs:ignore Magento2.Security.InsecureFunction - exec("php -f {$appDir}/bin/magento indexer:reindex", $out); + exec("php -f {$appDir}/bin/magento indexer:reindex catalog_category_product", $out); CacheCleaner::cleanAll(); } @@ -393,15 +393,15 @@ public function testFilterProductsByMultiSelectCustomAttributes() } $query = <<<QUERY { - products(filter:{ - $attributeCode: {in:["{$optionValues[0]}", "{$optionValues[1]}", "{$optionValues[2]}"]} + products(filter:{ + $attributeCode: {in:["{$optionValues[0]}", "{$optionValues[1]}", "{$optionValues[2]}"]} } pageSize: 3 currentPage: 1 ) { total_count - items + items { name sku @@ -414,14 +414,14 @@ public function testFilterProductsByMultiSelectCustomAttributes() filters{ name request_var - filter_items_count + filter_items_count filter_items{ label items_count value_string __typename } - } + } aggregations{ attribute_code count @@ -430,11 +430,11 @@ public function testFilterProductsByMultiSelectCustomAttributes() { label value - + } } - - } + + } } QUERY; @@ -485,7 +485,7 @@ public function testSearchAndFilterByCustomAttribute() ) { total_count - items + items { name sku @@ -498,15 +498,15 @@ public function testSearchAndFilterByCustomAttribute() filters{ name request_var - filter_items_count + filter_items_count filter_items{ label items_count value_string __typename } - - } + + } aggregations { attribute_code @@ -518,10 +518,10 @@ public function testSearchAndFilterByCustomAttribute() label value } - } - } - + + } + } QUERY; $response = $this->graphQlQuery($query); @@ -631,7 +631,7 @@ public function testFilterByCategoryIdAndCustomAttribute() ) { total_count - items + items { name sku @@ -644,7 +644,7 @@ public function testFilterByCategoryIdAndCustomAttribute() filters{ name request_var - filter_items_count + filter_items_count filter_items{ label items_count @@ -664,7 +664,7 @@ public function testFilterByCategoryIdAndCustomAttribute() value } } - } + } } QUERY; $response = $this->graphQlQuery($query); @@ -788,7 +788,7 @@ public function testFilterBySingleProductUrlKey() ) { total_count - items + items { name sku @@ -802,7 +802,7 @@ public function testFilterBySingleProductUrlKey() filters{ name request_var - filter_items_count + filter_items_count filter_items{ label items_count @@ -822,7 +822,7 @@ public function testFilterBySingleProductUrlKey() value } } - } + } } QUERY; $response = $this->graphQlQuery($query); @@ -851,17 +851,17 @@ public function testFilterBySingleProductUrlKey() ) { total_count - items + items { name sku url_key } - + filters{ name request_var - filter_items_count + filter_items_count } aggregations { @@ -875,7 +875,7 @@ public function testFilterBySingleProductUrlKey() value } } - } + } } QUERY; $response = $this->graphQlQuery($query2); @@ -914,7 +914,7 @@ public function testFilterByMultipleProductUrlKeys() ) { total_count - items + items { name sku @@ -923,12 +923,12 @@ public function testFilterByMultipleProductUrlKeys() page_info{ current_page page_size - + } filters{ name request_var - filter_items_count + filter_items_count } aggregations { @@ -942,7 +942,7 @@ public function testFilterByMultipleProductUrlKeys() value } } - } + } } QUERY; $response = $this->graphQlQuery($query); @@ -1198,10 +1198,10 @@ public function testFilterByMultipleFilterFieldsSortedByMultipleSortFields() products( filter: { - price:{to :"50"} + price:{to :"50"} sku:{in:["simple1", "simple2"]} name:{match:"Simple"} - + } pageSize:4 currentPage:1 @@ -1236,10 +1236,10 @@ public function testFilterByMultipleFilterFieldsSortedByMultipleSortFields() page_size current_page } - sort_fields + sort_fields { default - options + options { value label @@ -1382,7 +1382,7 @@ public function testFilteringForProductsFromMultipleCategories() $query = <<<QUERY { - products(filter:{ + products(filter:{ category_id :{in:["4","5","12"]} }) { @@ -1429,7 +1429,7 @@ public function testFilterProductsBySingleCategoryId() category_id:{eq:"{$queryCategoryId}"} } pageSize:2 - + ) { items @@ -1446,7 +1446,7 @@ public function testFilterProductsBySingleCategoryId() } } total_count - + } } @@ -1520,7 +1520,7 @@ public function testSearchAndSortByRelevance() ) { total_count - items + items { name sku @@ -1533,14 +1533,14 @@ public function testSearchAndSortByRelevance() filters{ name request_var - filter_items_count + filter_items_count filter_items{ label items_count value_string __typename } - } + } aggregations{ attribute_code count @@ -1550,9 +1550,9 @@ public function testSearchAndSortByRelevance() value count } - } + } } - + } QUERY; $response = $this->graphQlQuery($query); @@ -1679,7 +1679,7 @@ public function testProductBasicFullTextSearchQuery() items_count label value_string - } + } } aggregations{ attribute_code @@ -1838,11 +1838,11 @@ public function testQueryFilterNoMatchingItems() { products( filter: - { + { price:{from:"50"} - + description:{match:"Description"} - + } pageSize:2 currentPage:1 @@ -1995,7 +1995,7 @@ public function testFilterProductsThatAreOutOfStockWithConfigSettings() sku:{eq:"simple_visible_in_stock"} } pageSize:20 - + ) { items @@ -2004,7 +2004,7 @@ public function testFilterProductsThatAreOutOfStockWithConfigSettings() name } total_count - + } } QUERY; diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogCms/CategoryBlockTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogCms/CategoryBlockTest.php index 52985422c3355..5788313c4704e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogCms/CategoryBlockTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogCms/CategoryBlockTest.php @@ -29,7 +29,7 @@ public function testCategoryCmsBlock() $blockRepository = Bootstrap::getObjectManager()->get(BlockRepositoryInterface::class); $block = $blockRepository->getById($blockId); $filter = Bootstrap::getObjectManager()->get(FilterEmulate::class); - $renderedContent = $filter->setUseSessionInUrl(false)->filter($block->getContent()); + $renderedContent = $filter->filter($block->getContent()); /** @var CategoryRepositoryInterface $categoryRepository */ $categoryRepository = Bootstrap::getObjectManager()->get(CategoryRepositoryInterface::class); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php index d598a463a48a7..f0b7ab1b924b6 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Cms/CmsBlockTest.php @@ -43,7 +43,7 @@ public function testGetCmsBlock() { $cmsBlock = $this->blockRepository->getById('enabled_block'); $cmsBlockData = $cmsBlock->getData(); - $renderedContent = $this->filterEmulate->setUseSessionInUrl(false)->filter($cmsBlock->getContent()); + $renderedContent = $this->filterEmulate->filter($cmsBlock->getContent()); $query = <<<QUERY @@ -77,7 +77,7 @@ public function testGetCmsBlockByBlockId() $cmsBlock = $this->blockRepository->getById('enabled_block'); $cmsBlockData = $cmsBlock->getData(); $blockId = $cmsBlockData['block_id']; - $renderedContent = $this->filterEmulate->setUseSessionInUrl(false)->filter($cmsBlock->getContent()); + $renderedContent = $this->filterEmulate->filter($cmsBlock->getContent()); $query = <<<QUERY diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCustomerCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCustomerCartTest.php index 8100bce4ac718..6ee9bcc516172 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCustomerCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCustomerCartTest.php @@ -11,6 +11,8 @@ use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Quote\Model\ResourceModel\Quote\Collection; +use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\TestCase\GraphQlAbstract; /** @@ -28,11 +30,29 @@ class GetCustomerCartTest extends GraphQlAbstract */ private $customerTokenService; + /** + * @var ObjectManagerInterface + */ + private $objectManager; + protected function setUp() { - $objectManager = Bootstrap::getObjectManager(); - $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $this->objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $this->objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quoteCollection = $this->objectManager->create(Collection::class); + foreach ($quoteCollection as $quote) { + $quote->delete(); + } + parent::tearDown(); } /** @@ -177,9 +197,8 @@ public function testGetInactiveCustomerCart() */ public function testGetCustomerCartSecondStore() { - $maskedQuoteIdSecondStore = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1_not_default_store'); $customerCartQuery = $this->getCustomerCartQuery(); - + $maskedQuoteIdSecondStore = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1_not_default_store'); $headerMap = $this->getHeaderMap(); $headerMap['Store'] = 'fixture_second_store'; $responseSecondStore = $this->graphQlQuery($customerCartQuery, [], '', $headerMap); diff --git a/dev/tests/api-functional/testsuite/Magento/SalesRule/Api/GuestTotalsInformationManagement.php b/dev/tests/api-functional/testsuite/Magento/SalesRule/Api/GuestTotalsInformationManagement.php new file mode 100644 index 0000000000000..2057a97087fff --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/SalesRule/Api/GuestTotalsInformationManagement.php @@ -0,0 +1,71 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\SalesRule\Api; + +use Magento\Framework\Webapi\Rest\Request; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Tests disabled cart rules for guest's cart + */ +class GuestTotalsInformationManagement extends WebapiAbstract +{ + private const SERVICE_OPERATION = 'calculate'; + private const SERVICE_NAME = 'checkoutGuestTotalsInformationManagementV1'; + private const SERVICE_VERSION = 'V1'; + private const RESOURCE_PATH = '/V1/guest-carts/:cartId/totals-information'; + private const QUOTE_RESERVED_ORDER_ID = 'test01'; + private const SALES_RULE_ID = 'Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition/salesRuleId'; + + /** + * Test sales rule changes should be persisted in the database + * + * @magentoApiDataFixture Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition.php + * @magentoApiDataFixture Magento/Sales/_files/quote.php + */ + public function testCalculate() + { + /** @var \Magento\Quote\Model\Quote $quote */ + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ + /** @var \Magento\SalesRule\Model\Rule $salesRule */ + /** @var \Magento\Framework\Registry $registry */ + $registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + $quote = Bootstrap::getObjectManager()->get(\Magento\Quote\Model\QuoteFactory::class)->create(); + $quote->load(self::QUOTE_RESERVED_ORDER_ID, 'reserved_order_id'); + $quoteIdMask = Bootstrap::getObjectManager()->get(\Magento\Quote\Model\QuoteIdMaskFactory::class)->create(); + $quoteIdMask->load($quote->getId(), 'quote_id'); + $salesRuleId = $registry->registry(self::SALES_RULE_ID); + $salesRule = Bootstrap::getObjectManager()->create(\Magento\SalesRule\Model\RuleFactory::class)->create(); + $salesRule->load($salesRuleId); + $this->assertContains($salesRule->getRuleId(), str_getcsv($quote->getAppliedRuleIds())); + $salesRule->setIsActive(0); + $salesRule->save(); + $response = $this->_webApiCall( + [ + 'rest' => [ + 'resourcePath' => str_replace(':cartId', $quoteIdMask->getMaskedId(), self::RESOURCE_PATH), + 'httpMethod' => Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::SERVICE_OPERATION, + ], + ], + [ + 'addressInformation' => [ + 'address' => [] + ] + ] + ); + $this->assertNotEmpty($response); + $quote->load(self::QUOTE_RESERVED_ORDER_ID, 'reserved_order_id'); + $this->assertNotContains($salesRule->getId(), str_getcsv($quote->getAppliedRuleIds())); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/SalesRule/Api/TotalsInformationManagement.php b/dev/tests/api-functional/testsuite/Magento/SalesRule/Api/TotalsInformationManagement.php new file mode 100644 index 0000000000000..6bde30b9acab4 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/SalesRule/Api/TotalsInformationManagement.php @@ -0,0 +1,82 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\SalesRule\Api; + +use Magento\Framework\Webapi\Rest\Request; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Tests disabled cart rules for customer's cart + */ +class TotalsInformationManagement extends WebapiAbstract +{ + private const SERVICE_OPERATION = 'calculate'; + private const SERVICE_NAME = 'checkoutTotalsInformationManagementV1'; + private const SERVICE_VERSION = 'V1'; + private const RESOURCE_PATH = '/V1/carts/mine/totals-information'; + private const QUOTE_RESERVED_ORDER_ID = 'test01'; + private const SALES_RULE_ID = 'Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition/salesRuleId'; + private const CUSTOMER_EMAIL = 'customer@example.com'; + private const CUSTOMER_PASSWORD = 'password'; + + /** + * Test sales rule changes should be persisted in the database + * + * @magentoApiDataFixture Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition.php + * @magentoApiDataFixture Magento/Sales/_files/quote_with_customer.php + */ + public function testCalculate() + { + /** @var \Magento\Quote\Model\Quote $quote */ + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ + /** @var \Magento\SalesRule\Model\Rule $salesRule */ + /** @var \Magento\Framework\Registry $registry */ + $registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + $quote = Bootstrap::getObjectManager()->get(\Magento\Quote\Model\QuoteFactory::class)->create(); + $quote->load(self::QUOTE_RESERVED_ORDER_ID, 'reserved_order_id'); + $quoteIdMask = Bootstrap::getObjectManager()->get(\Magento\Quote\Model\QuoteIdMaskFactory::class)->create(); + $quoteIdMask->load($quote->getId(), 'quote_id'); + $salesRuleId = $registry->registry(self::SALES_RULE_ID); + $salesRule = Bootstrap::getObjectManager()->create(\Magento\SalesRule\Model\RuleFactory::class)->create(); + $salesRule->load($salesRuleId); + $this->assertContains($salesRule->getRuleId(), str_getcsv($quote->getAppliedRuleIds())); + $salesRule->setIsActive(0); + $salesRule->save(); + $response = $this->_webApiCall( + [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST, + 'token' => Bootstrap::getObjectManager() + ->create( + \Magento\Integration\Api\CustomerTokenServiceInterface::class + ) + ->createCustomerAccessToken( + self::CUSTOMER_EMAIL, + self::CUSTOMER_PASSWORD + ) + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::SERVICE_OPERATION, + ], + ], + [ + 'cartId' => $quote->getId(), + 'addressInformation' => [ + 'address' => [] + ] + ] + ); + $this->assertNotEmpty($response); + $quote->load(self::QUOTE_RESERVED_ORDER_ID, 'reserved_order_id'); + $this->assertNotContains($salesRule->getId(), str_getcsv($quote->getAppliedRuleIds())); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCard.xml b/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCard.xml index 7e3266cadf0a3..b2c866f9cdce1 100644 --- a/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCard.xml +++ b/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCard.xml @@ -10,7 +10,7 @@ <dataset name="visa_default"> <field name="cc_number" xsi:type="string">4111111111111111</field> <field name="cc_exp_month" xsi:type="string">01 - January</field> - <field name="cc_exp_year" xsi:type="string">2020</field> + <field name="cc_exp_year" xsi:type="string">2025</field> <field name="cc_cid" xsi:type="string">123</field> </dataset> @@ -18,28 +18,28 @@ <field name="cc_type" xsi:type="string">Visa</field> <field name="cc_number" xsi:type="string">4111111111111111</field> <field name="cc_exp_month" xsi:type="string">01 - January</field> - <field name="cc_exp_year" xsi:type="string">2020</field> + <field name="cc_exp_year" xsi:type="string">2025</field> <field name="cc_cid" xsi:type="string">123</field> </dataset> <dataset name="visa_alt"> <field name="cc_number" xsi:type="string">4012888888881881</field> <field name="cc_exp_month" xsi:type="string">02 - February</field> - <field name="cc_exp_year" xsi:type="string">2021</field> + <field name="cc_exp_year" xsi:type="string">2025</field> <field name="cc_cid" xsi:type="string">123</field> </dataset> <dataset name="amex_default"> <field name="cc_number" xsi:type="string">378282246310005</field> <field name="cc_exp_month" xsi:type="string">02 - February</field> - <field name="cc_exp_year" xsi:type="string">2021</field> + <field name="cc_exp_year" xsi:type="string">2025</field> <field name="cc_cid" xsi:type="string">1234</field> </dataset> <dataset name="visa_direct"> <field name="cc_number" xsi:type="string">4617747819866651</field> <field name="cc_exp_month" xsi:type="string">01 - January</field> - <field name="cc_exp_year" xsi:type="string">2020</field> + <field name="cc_exp_year" xsi:type="string">2025</field> <field name="cc_cid" xsi:type="string">123</field> </dataset> @@ -54,14 +54,14 @@ <dataset name="visa_cvv_mismatch"> <field name="cc_number" xsi:type="string">4111111111111111</field> <field name="cc_exp_month" xsi:type="string">01 - January</field> - <field name="cc_exp_year" xsi:type="string">2020</field> + <field name="cc_exp_year" xsi:type="string">2025</field> <field name="cc_cid" xsi:type="string">306</field> </dataset> <dataset name="mastercard_default"> <field name="cc_number" xsi:type="string">5555555555554444</field> <field name="cc_exp_month" xsi:type="string">01 - January</field> - <field name="cc_exp_year" xsi:type="string">2020</field> + <field name="cc_exp_year" xsi:type="string">2025</field> <field name="cc_cid" xsi:type="string">123</field> </dataset> </repository> diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/DateGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/DateGroupDataProvider.php index 7f9d5362c4f83..7e83b3f349d4d 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/DateGroupDataProvider.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/DateGroupDataProvider.php @@ -8,6 +8,7 @@ namespace Magento\TestFramework\Catalog\Block\Product\View\Options; use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface; use Magento\Catalog\Model\Product\Option; /** @@ -31,7 +32,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, Option::KEY_IS_REQUIRE => 1, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-date-title-1', ], [ @@ -46,7 +47,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-date-title-2', ], [ @@ -61,7 +62,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-date-title-3', ], [ @@ -76,7 +77,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Option::KEY_SKU => 'test-option-date-title-4', ], [ @@ -91,7 +92,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, Option::KEY_IS_REQUIRE => 1, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-date-and-time-title-1', ], [ @@ -106,7 +107,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-date-and-time-title-2', ], [ @@ -121,7 +122,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-date-and-time-title-3', ], [ @@ -136,7 +137,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Option::KEY_SKU => 'test-option-date-and-time-title-4', ], [ @@ -151,7 +152,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, Option::KEY_IS_REQUIRE => 1, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-time-title-1', ], [ @@ -166,7 +167,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-time-title-2', ], [ @@ -181,7 +182,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-time-title-3', ], [ @@ -196,7 +197,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_TIME, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Option::KEY_SKU => 'test-option-time-title-4', ], [ diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/FileGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/FileGroupDataProvider.php index c28cb770a806e..1817509539eec 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/FileGroupDataProvider.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/FileGroupDataProvider.php @@ -8,6 +8,7 @@ namespace Magento\TestFramework\Catalog\Block\Product\View\Options; use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface; use Magento\Catalog\Model\Product\Option; /** @@ -31,7 +32,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, Option::KEY_IS_REQUIRE => 1, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-file-title-1', Option::KEY_SORT_ORDER => 1, Option::KEY_FILE_EXTENSION => 'png, jpg', @@ -51,7 +52,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-file-title-2', Option::KEY_SORT_ORDER => 1, Option::KEY_FILE_EXTENSION => 'png, jpg', @@ -71,7 +72,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-file-title-3', Option::KEY_SORT_ORDER => 1, Option::KEY_FILE_EXTENSION => 'png, jpg', @@ -91,7 +92,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Option::KEY_SKU => 'test-option-file-title-4', Option::KEY_SORT_ORDER => 1, Option::KEY_FILE_EXTENSION => 'png, jpg', @@ -111,7 +112,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FILE, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-file-title-5', Option::KEY_SORT_ORDER => 1, Option::KEY_FILE_EXTENSION => 'png, jpg', @@ -122,7 +123,7 @@ public function getData(): array 'block_with_required_class' => '<div class="field file">', 'label_for_created_option' => '<label class="label" for="options_%s_file"', 'title' => '<span>Test option file title 5</span>', - 'price' => 'data-price-amount="5"', + 'price' => 'data-price-amount="50"', 'required_element' => '/<input type="file"/', 'file_extension' => '<strong>png, jpg</strong>', 'file_width' => '/%s:.*<strong>10 px.<\/strong>/', diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/SelectGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/SelectGroupDataProvider.php index 2a13c1cd45466..ad9ffb40f5762 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/SelectGroupDataProvider.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/SelectGroupDataProvider.php @@ -8,6 +8,7 @@ namespace Magento\TestFramework\Catalog\Block\Product\View\Options; use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface; use Magento\Catalog\Model\Product\Option; use Magento\Catalog\Model\Product\Option\Value; @@ -35,7 +36,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option drop-down title 1 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-drop-down-title-1-value-1', ], [ @@ -58,7 +59,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option drop-down title 2 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-drop-down-title-2-value-1', ], [ @@ -81,7 +82,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option drop-down title 3 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-drop-down-title-3-value-1', ], [ @@ -104,7 +105,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option drop-down title 4 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Value::KEY_SKU => 'test-option-drop-down-title-4-value-1', ], [ @@ -127,7 +128,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option radio-button title 1 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-radio-button-title-1-value-1', ], [ @@ -147,7 +148,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option radio-button title 2 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-radio-button-title-2-value-1', ], [ @@ -167,7 +168,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option radio-button title 3 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-radio-button-title-3-value-1', ], [ @@ -187,7 +188,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option radio-button title 4 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Value::KEY_SKU => 'test-option-radio-button-title-4-value-1', ], [ @@ -207,7 +208,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option checkbox title 1 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-checkbox-title-1-value-1', ], [ @@ -227,7 +228,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option checkbox title 2 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-checkbox-title-2-value-1', ], [ @@ -247,7 +248,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option checkbox title 3 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-checkbox-title-3-value-1', ], [ @@ -267,7 +268,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option checkbox title 4 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Value::KEY_SKU => 'test-option-checkbox-title-4-value-1', ], [ @@ -287,7 +288,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option multiselect title 1 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-multiselect-title-1-value-1', ], [ @@ -307,7 +308,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option multiselect title 2 value 1', Value::KEY_PRICE => 10, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-multiselect-title-2-value-1', ], [ @@ -327,7 +328,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option multiselect title 3 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'fixed', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Value::KEY_SKU => 'test-option-multiselect-title-3-value-1', ], [ @@ -347,7 +348,7 @@ public function getData(): array [ Value::KEY_TITLE => 'Test option multiselect title 4 value 1', Value::KEY_PRICE => 50, - Value::KEY_PRICE_TYPE => 'percent', + Value::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Value::KEY_SKU => 'test-option-multiselect-title-4-value-1', ], [ diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/TextGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/TextGroupDataProvider.php index 75a6da0593d73..b0d22a222dd85 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/TextGroupDataProvider.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Block/Product/View/Options/TextGroupDataProvider.php @@ -8,6 +8,7 @@ namespace Magento\TestFramework\Catalog\Block\Product\View\Options; use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface; use Magento\Catalog\Model\Product\Option; /** @@ -31,7 +32,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, Option::KEY_IS_REQUIRE => 1, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-field-title-1', Option::KEY_MAX_CHARACTERS => 0, ], @@ -49,7 +50,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-field-title-2', Option::KEY_MAX_CHARACTERS => 0, ], @@ -67,7 +68,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-field-title-3', Option::KEY_MAX_CHARACTERS => 0, ], @@ -85,7 +86,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Option::KEY_SKU => 'test-option-field-title-4', Option::KEY_MAX_CHARACTERS => 0, ], @@ -103,7 +104,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-field-title-5', Option::KEY_MAX_CHARACTERS => 99, ], @@ -122,7 +123,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, Option::KEY_IS_REQUIRE => 1, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-area-title-1', Option::KEY_MAX_CHARACTERS => 0, ], @@ -140,7 +141,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-area-title-2', Option::KEY_MAX_CHARACTERS => 0, ], @@ -158,7 +159,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-area-title-3', Option::KEY_MAX_CHARACTERS => 0, ], @@ -176,7 +177,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 50, - Option::KEY_PRICE_TYPE => 'percent', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_PERCENT, Option::KEY_SKU => 'test-option-area-title-4', Option::KEY_MAX_CHARACTERS => 0, ], @@ -194,7 +195,7 @@ public function getData(): array Option::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_AREA, Option::KEY_IS_REQUIRE => 0, Option::KEY_PRICE => 10, - Option::KEY_PRICE_TYPE => 'fixed', + Option::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, Option::KEY_SKU => 'test-option-area-title-5', Option::KEY_MAX_CHARACTERS => 99, ], diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/GetCategoryByName.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/GetCategoryByName.php new file mode 100644 index 0000000000000..75b123e2dc906 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/GetCategoryByName.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Model; + +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; + +/** + * Load category by category name + */ +class GetCategoryByName +{ + /** @var CollectionFactory */ + private $categoryCollectionFactory; + + /** + * @param CollectionFactory $categoryCollectionFactory + */ + public function __construct(CollectionFactory $categoryCollectionFactory) + { + $this->categoryCollectionFactory = $categoryCollectionFactory; + } + + /** + * Load category by name. + * + * @param string $categoryName + * @return CategoryInterface + */ + public function execute(string $categoryName): CategoryInterface + { + $categoryCollection = $this->categoryCollectionFactory->create(); + + return $categoryCollection->addAttributeToFilter(CategoryInterface::KEY_NAME, $categoryName) + ->setPageSize(1) + ->getFirstItem(); + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Layer/QuickSearchByQuery.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Layer/QuickSearchByQuery.php new file mode 100644 index 0000000000000..506aba9f6bd84 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Layer/QuickSearchByQuery.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Model\Layer; + +use Magento\Catalog\Model\Layer\SearchFactory; +use Magento\Catalog\Model\ResourceModel\Product\Collection; + +/** + * Quick search products by query. + */ +class QuickSearchByQuery +{ + /** + * @var SearchFactory + */ + private $searchFactory; + + /** + * @param SearchFactory $searchFactory + */ + public function __construct( + SearchFactory $searchFactory + ) { + $this->searchFactory = $searchFactory; + } + + /** + * Flush search instances cache and find products by search query. + * + * @param string $query + * @param string $sortedField + * @param string $sortOrder + * @return Collection + */ + public function execute( + string $query, + string $sortedField = 'relevance', + string $sortOrder = 'desc' + ): Collection { + $productCollection = $this->searchFactory->create()->getProductCollection(); + $productCollection->addSearchFilter($query); + $productCollection->setOrder($sortedField, $sortOrder); + + return $productCollection; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/Decimal.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/Decimal.php new file mode 100644 index 0000000000000..ad72f2d197794 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/Decimal.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider; + +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\TestFramework\Eav\Model\Attribute\DataProvider\AbstractBaseAttributeData; +use Magento\Store\Model\Store; +use Magento\Catalog\Model\Product\Attribute\Backend\Price as BackendPrice; + +/** + * Product attribute data for attribute with input type weee. + */ +class Decimal extends AbstractBaseAttributeData +{ + /** + * @inheritdoc + */ + public function __construct() + { + parent::__construct(); + $this->defaultAttributePostData['is_filterable'] = '0'; + $this->defaultAttributePostData['is_filterable_in_search'] = '0'; + $this->defaultAttributePostData['used_for_sort_by'] = '0'; + } + + /** + * @inheritdoc + */ + public function getAttributeData(): array + { + $result = parent::getAttributeData(); + unset($result["{$this->getFrontendInput()}_with_default_value"]); + unset($result["{$this->getFrontendInput()}_without_default_value"]); + + return $result; + } + + /** + * @inheritdoc + */ + public function getAttributeDataWithCheckArray(): array + { + $result = parent::getAttributeDataWithCheckArray(); + unset($result["{$this->getFrontendInput()}_with_default_value"]); + unset($result["{$this->getFrontendInput()}_without_default_value"]); + + return $result; + } + + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'decimal_attribute', + ], + ], + ] + ); + } + + /** + * @inheritdoc + */ + protected function getFrontendInput(): string + { + return 'price'; + } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Decimal Attribute Update', + ], + 'frontend_input' => 'price', + 'is_required' => '1', + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_filterable' => '2', + 'is_filterable_in_search' => '1', + 'position' => '2', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Decimal Attribute Update', + 'attribute_code' => 'decimal_attribute', + 'is_global' => ScopedAttributeInterface::SCOPE_GLOBAL, + 'default_value' => null, + 'frontend_class' => null, + 'is_user_defined' => '1', + 'backend_type' => 'decimal', + 'backend_model' => BackendPrice::class, + ] + ); + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/MediaImage.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/MediaImage.php index dd706ab29c326..7ae0169efa497 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/MediaImage.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/MediaImage.php @@ -7,6 +7,9 @@ namespace Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider; +use Magento\Catalog\Model\Product\Attribute\Frontend\Image; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; use Magento\TestFramework\Eav\Model\Attribute\DataProvider\AbstractBaseAttributeData; /** @@ -47,6 +50,27 @@ public function getAttributeDataWithCheckArray(): array return $result; } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'image_attribute', + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -54,4 +78,55 @@ protected function getFrontendInput(): string { return 'media_image'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Decimal Attribute Update', + ], + 'frontend_input' => 'media_image', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Decimal Attribute Update', + 'is_required' => '0', + 'attribute_code' => 'image_attribute', + 'default_value' => null, + 'is_unique' => '0', + 'frontend_class' => null, + 'is_searchable' => '0', + 'search_weight' => '1', + 'is_visible_in_advanced_search' => '0', + 'is_comparable' => '0', + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'is_used_for_promo_rules' => '0', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '0', + 'used_in_product_listing' => '1', + 'used_for_sort_by' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'varchar', + 'frontend_model' => Image::class, + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/Price.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/Price.php deleted file mode 100644 index 04ee6bb0a5740..0000000000000 --- a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Attribute/DataProvider/Price.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider; - -use Magento\TestFramework\Eav\Model\Attribute\DataProvider\AbstractBaseAttributeData; - -/** - * Product attribute data for attribute with input type weee. - */ -class Price extends AbstractBaseAttributeData -{ - /** - * @inheritdoc - */ - public function __construct() - { - parent::__construct(); - $this->defaultAttributePostData['is_filterable'] = '0'; - $this->defaultAttributePostData['is_filterable_in_search'] = '0'; - $this->defaultAttributePostData['used_for_sort_by'] = '0'; - } - - /** - * @inheritdoc - */ - public function getAttributeData(): array - { - $result = parent::getAttributeData(); - unset($result["{$this->getFrontendInput()}_with_default_value"]); - unset($result["{$this->getFrontendInput()}_without_default_value"]); - - return $result; - } - - /** - * @inheritdoc - */ - public function getAttributeDataWithCheckArray(): array - { - $result = parent::getAttributeDataWithCheckArray(); - unset($result["{$this->getFrontendInput()}_with_default_value"]); - unset($result["{$this->getFrontendInput()}_without_default_value"]); - - return $result; - } - - /** - * @inheritdoc - */ - protected function getFrontendInput(): string - { - return 'price'; - } -} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Price/GetPriceIndexDataByProductId.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Price/GetPriceIndexDataByProductId.php new file mode 100644 index 0000000000000..b1794c55d06f3 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Product/Price/GetPriceIndexDataByProductId.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Model\Product\Price; + +use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; + +/** + * Search and return price data from price index table. + */ +class GetPriceIndexDataByProductId +{ + /** + * @var ProductResource + */ + private $productResource; + + /** + * @var PriceTableResolver + */ + private $priceTableResolver; + + /** + * @var DimensionFactory + */ + private $dimensionFactory; + + /** + * @param ProductResource $productResource + * @param PriceTableResolver $priceTableResolver + * @param DimensionFactory $dimensionFactory + */ + public function __construct( + ProductResource $productResource, + PriceTableResolver $priceTableResolver, + DimensionFactory $dimensionFactory + ) { + $this->productResource = $productResource; + $this->priceTableResolver = $priceTableResolver; + $this->dimensionFactory = $dimensionFactory; + } + + /** + * Returns price data by product id. + * + * @param int $productId + * @param int $groupId + * @param int $websiteId + * @return array + */ + public function execute(int $productId, int $groupId, int $websiteId): array + { + $tableName = $this->priceTableResolver->resolve( + 'catalog_product_index_price', + [ + $this->dimensionFactory->create(WebsiteDimensionProvider::DIMENSION_NAME, (string)$websiteId), + $this->dimensionFactory->create(CustomerGroupDimensionProvider::DIMENSION_NAME, (string)$groupId), + ] + ); + + $select = $this->productResource->getConnection()->select() + ->from($tableName) + ->where('entity_id = ?', $productId) + ->where('customer_group_id = ?', $groupId) + ->where('website_id = ?', $websiteId); + + return $this->productResource->getConnection()->fetchAll($select); + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/DateGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/DateGroupDataProvider.php new file mode 100644 index 0000000000000..a03d3912dbd06 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/DateGroupDataProvider.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\ConfigurableProduct\Block\CustomOptions; + +use Magento\TestFramework\Catalog\Block\Product\View\Options\DateGroupDataProvider as OptionsDateGroupDataProvider; + +/** + * @inheritdoc + */ +class DateGroupDataProvider extends OptionsDateGroupDataProvider +{ + /** + * @inheritdoc + */ + public function getData(): array + { + $optionsData = parent::getData(); + unset( + $optionsData['type_date_percent_price'], + $optionsData['type_date_and_time_percent_price'], + $optionsData['type_time_percent_price'] + ); + + return $optionsData; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/FileGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/FileGroupDataProvider.php new file mode 100644 index 0000000000000..e0a22341a7ca0 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/FileGroupDataProvider.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\ConfigurableProduct\Block\CustomOptions; + +use Magento\TestFramework\Catalog\Block\Product\View\Options\FileGroupDataProvider as OptionsFileGroupDataProvider; + +/** + * @inheritdoc + */ +class FileGroupDataProvider extends OptionsFileGroupDataProvider +{ + /** + * @inheritdoc + */ + public function getData(): array + { + $optionsData = parent::getData(); + unset($optionsData['type_file_percent_price']); + + return $optionsData; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/SelectGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/SelectGroupDataProvider.php new file mode 100644 index 0000000000000..89a94f423d052 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/SelectGroupDataProvider.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\ConfigurableProduct\Block\CustomOptions; + +use Magento\TestFramework\Catalog\Block\Product\View\Options\SelectGroupDataProvider as OptionsSelectGroupDataProvider; + +/** + * @inheritdoc + */ +class SelectGroupDataProvider extends OptionsSelectGroupDataProvider +{ + /** + * @inheritdoc + */ + public function getData(): array + { + $optionsData = parent::getData(); + unset( + $optionsData['type_drop_down_value_percent_price'], + $optionsData['type_radio_button_value_percent_price'], + $optionsData['type_checkbox_value_percent_price'], + $optionsData['type_multiselect_value_percent_price'] + ); + + return $optionsData; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/TextGroupDataProvider.php b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/TextGroupDataProvider.php new file mode 100644 index 0000000000000..cf442a978b09a --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Block/CustomOptions/TextGroupDataProvider.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\ConfigurableProduct\Block\CustomOptions; + +use Magento\TestFramework\Catalog\Block\Product\View\Options\TextGroupDataProvider as OptionsTextGroupDataProvider; + +/** + * @inheritdoc + */ +class TextGroupDataProvider extends OptionsTextGroupDataProvider +{ + /** + * @inheritdoc + */ + public function getData(): array + { + $optionsData = parent::getData(); + unset( + $optionsData['type_field_percent_price'], + $optionsData['type_area_percent_price'] + ); + + return $optionsData; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Model/DeleteConfigurableProduct.php b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Model/DeleteConfigurableProduct.php new file mode 100644 index 0000000000000..3414efa1e30ed --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/ConfigurableProduct/Model/DeleteConfigurableProduct.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\ConfigurableProduct\Model; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; + +/** + * Delete configurable product with linked products + */ +class DeleteConfigurableProduct +{ + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var ProductResource */ + private $productResource; + + /** @var Registry */ + private $registry; + + /** + * @param ProductRepositoryInterface $productRepository + * @param ProductResource $productResource + * @param Registry $registry + */ + public function __construct( + ProductRepositoryInterface $productRepository, + ProductResource $productResource, + Registry $registry + ) { + $this->productRepository = $productRepository; + $this->productResource = $productResource; + $this->registry = $registry; + } + + /** + * Delete configurable product and linked products + * + * @param string $sku + * @return void + */ + public function execute(string $sku): void + { + $configurableProduct = $this->productRepository->get($sku, false, null, true); + $childrenIds = $configurableProduct->getExtensionAttributes()->getConfigurableProductLinks(); + $childrenSkus = array_column($this->productResource->getProductsSku($childrenIds), 'sku'); + $childrenSkus[] = $sku; + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + foreach ($childrenSkus as $childSku) { + try { + $this->productRepository->deleteById($childSku); + } catch (NoSuchEntityException $e) { + //product already removed + } + } + + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Directory/Model/RemoveCurrencyRateByCode.php b/dev/tests/integration/framework/Magento/TestFramework/Directory/Model/RemoveCurrencyRateByCode.php new file mode 100644 index 0000000000000..86895045db945 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Directory/Model/RemoveCurrencyRateByCode.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Directory\Model; + +use Magento\Directory\Model\ResourceModel\Currency; + +/** + * Remove currency rates by currency code + */ +class RemoveCurrencyRateByCode +{ + /** @var Currency */ + private $currencyResource; + + /** + * @param Currency $currencyResource + */ + public function __construct(Currency $currencyResource) + { + $this->currencyResource = $currencyResource; + } + + /** + * Remove currency rates + * + * @param string $currencyCode + * @return void + */ + public function execute(string $currencyCode): void + { + $connection = $this->currencyResource->getConnection(); + $rateTable = $this->currencyResource->getTable('directory_currency_rate'); + $connection->delete($rateTable, $connection->quoteInto('currency_to = ? OR currency_from = ?', $currencyCode)); + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractAttributeDataWithOptions.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractAttributeDataWithOptions.php index 8f25651e2e036..affd063d99ab5 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractAttributeDataWithOptions.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractAttributeDataWithOptions.php @@ -7,6 +7,8 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Store\Model\Store; + /** * Base POST data for create attribute with options. */ @@ -72,6 +74,67 @@ public function getAttributeDataWithCheckArray(): array return $result; } + /** + * Return product attribute data set for update attribute options. + * + * @return array + */ + public function getUpdateOptionsProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return [ + "{$frontendInput}_update_options" => [ + 'post_data' => [ + 'options_array' => [ + 'option_1' => [ + 'order' => '5', + 'value' => [ + Store::DEFAULT_STORE_ID => 'Option 1 Admin', + 'default' => 'Option 1 Store 1', + 'fixture_second_store' => 'Option 1 Store 2', + 'fixture_third_store' => 'Option 1 Store 3', + ], + 'delete' => '', + ], + 'option_2' => [ + 'order' => '6', + 'value' => [ + Store::DEFAULT_STORE_ID => 'Option 2 Admin', + 'default' => 'Option 2 Store 1', + 'fixture_second_store' => 'Option 2 Store 2', + 'fixture_third_store' => 'Option 2 Store 3', + ], + 'delete' => '', + 'default' => 1, + ], + ], + ], + ], + "{$frontendInput}_delete_options" => [ + 'post_data' => [ + 'options_array' => [ + 'option_1' => [ + 'value' => [], + 'delete' => '', + ], + 'option_2' => [ + 'value' => [], + 'delete' => '1', + ], + 'option_3' => [ + 'value' => [], + 'delete' => '', + ], + 'option_4' => [ + 'value' => [], + 'delete' => '1', + ], + ], + ], + ], + ]; + } + /** * Return attribute options data. * diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractBaseAttributeData.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractBaseAttributeData.php index af9e58d02fb5a..cd93e57a849cf 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractBaseAttributeData.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/AbstractBaseAttributeData.php @@ -8,6 +8,7 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; use Magento\Store\Model\Store; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; /** * Base POST data for create attribute. @@ -28,7 +29,7 @@ abstract class AbstractBaseAttributeData 'dropdown_attribute_validation' => '', 'dropdown_attribute_validation_unique' => '', 'attribute_code' => '', - 'is_global' => '0', + 'is_global' => ScopedAttributeInterface::SCOPE_STORE, 'default_value_text' => '', 'default_value_yesno' => '0', 'default_value_date' => '', @@ -68,10 +69,10 @@ public function getAttributeData(): array $this->defaultAttributePostData, ], "{$this->getFrontendInput()}_with_global_scope" => [ - array_merge($this->defaultAttributePostData, ['is_global' => '1']), + array_merge($this->defaultAttributePostData, ['is_global' => ScopedAttributeInterface::SCOPE_GLOBAL]), ], "{$this->getFrontendInput()}_with_website_scope" => [ - array_merge($this->defaultAttributePostData, ['is_global' => '2']), + array_merge($this->defaultAttributePostData, ['is_global' => ScopedAttributeInterface::SCOPE_WEBSITE]), ], "{$this->getFrontendInput()}_with_attribute_code" => [ array_merge($this->defaultAttributePostData, ['attribute_code' => 'test_custom_attribute_code']), @@ -143,19 +144,19 @@ public function getAttributeDataWithCheckArray(): array "{$this->getFrontendInput()}_with_store_view_scope" => [ [ 'attribute_code' => 'test_attribute_name', - 'is_global' => '0', + 'is_global' => ScopedAttributeInterface::SCOPE_STORE, ], ], "{$this->getFrontendInput()}_with_global_scope" => [ [ 'attribute_code' => 'test_attribute_name', - 'is_global' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_GLOBAL, ], ], "{$this->getFrontendInput()}_with_website_scope" => [ [ 'attribute_code' => 'test_attribute_name', - 'is_global' => '2', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, ], ], "{$this->getFrontendInput()}_with_attribute_code" => [ @@ -215,10 +216,173 @@ public function getAttributeDataWithCheckArray(): array ); } + /** + * Return product attribute data set for update attribute. + * + * @return array + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return [ + "{$frontendInput}_update_all_fields" => [ + 'post_data' => $this->getUpdatePostData(), + 'expected_data' => $this->getUpdateExpectedData(), + ], + "{$frontendInput}_other_is_user_defined" => [ + 'post_data' => [ + 'is_user_defined' => '2', + ], + 'expected_data' => [ + 'is_user_defined' => '1', + ], + ], + "{$frontendInput}_with_is_global_null" => [ + 'post_data' => [ + 'is_global' => null, + ], + 'expected_data' => [ + 'is_global' => ScopedAttributeInterface::SCOPE_GLOBAL, + ], + ], + "{$frontendInput}_is_visible_in_advanced_search" => [ + 'post_data' => [ + 'is_searchable' => '0', + 'is_visible_in_advanced_search' => '1', + ], + 'expected_data' => [ + 'is_searchable' => '0', + 'is_visible_in_advanced_search' => '0', + ], + ], + "{$frontendInput}_update_with_attribute_set" => [ + 'post_data' => [ + 'set' => '4', + 'new_attribute_set_name' => 'Text Attribute Set', + 'group' => 'text_attribute_group', + 'groupName' => 'Text Attribute Group', + 'groupSortOrder' => '1', + ], + 'expected_data' => [], + ], + ]; + } + + /** + * Return product attribute data set with error message for update attribute. + * + * @return array + */ + public function getUpdateProviderWithErrorMessage(): array + { + $frontendInput = $this->getFrontendInput(); + return [ + "{$frontendInput}_same_attribute_set_name" => [ + 'post_data' => [ + 'set' => '4', + 'new_attribute_set_name' => 'Default', + ], + 'error_message' => (string)__('An attribute set named \'Default\' already exists.'), + ], + "{$frontendInput}_empty_set_id" => [ + 'post_data' => [ + 'set' => '', + 'new_attribute_set_name' => 'Text Attribute Set', + ], + 'error_message' => (string)__('Something went wrong while saving the attribute.'), + ], + "{$frontendInput}_nonexistent_attribute_id" => [ + 'post_data' => [ + 'attribute_id' => 9999, + ], + 'error_message' => (string)__('This attribute no longer exists.'), + ], + "{$frontendInput}_attribute_other_entity_type" => [ + 'post_data' => [ + 'attribute_id' => 45, + ], + 'error_message' => (string)__('We can\'t update the attribute.'), + ], + ]; + } + + /** + * Return product attribute data set for update attribute frontend labels. + * + * @return array + */ + public function getUpdateFrontendLabelsProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return [ + "{$frontendInput}_update_frontend_label" => [ + 'post_data' => [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Test Attribute Update', + 'default' => 'Default Store Update', + 'fixture_second_store' => 'Second Store Update', + 'fixture_third_store' => 'Third Store Update', + ] + ], + 'expected_data' => [ + 'frontend_label' => 'Test Attribute Update', + 'store_labels' => [ + 'default' => 'Default Store Update', + 'fixture_second_store' => 'Second Store Update', + 'fixture_third_store' => 'Third Store Update', + ], + ], + ], + "{$frontendInput}_remove_frontend_label" => [ + 'post_data' => [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Test Attribute Update', + 'default' => 'Default Store Update', + 'fixture_second_store' => '', + 'fixture_third_store' => '', + ] + ], + 'expected_data' => [ + 'frontend_label' => 'Test Attribute Update', + 'store_labels' => [ + 'default' => 'Default Store Update', + ], + ], + ], + "{$frontendInput}_with_frontend_label_string" => [ + 'post_data' => [ + 'frontend_label' => 'Test Attribute Update', + ], + 'expected_data' => [ + 'frontend_label' => 'Test Attribute Update', + 'store_labels' => [ + 'default' => 'Default Store View', + 'fixture_second_store' => 'Fixture Second Store', + 'fixture_third_store' => 'Fixture Third Store', + ], + ], + ], + ]; + } + /** * Return attribute frontend input. * * @return string */ abstract protected function getFrontendInput(): string; + + /** + * Return post data for attribute update. + * + * @return array + */ + abstract protected function getUpdatePostData(): array; + + /** + * Return expected data for attribute update. + * + * @return array + */ + abstract protected function getUpdateExpectedData(): array; } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Date.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Date.php index 7a6f8ee41c1f8..c4e34ef8984a8 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Date.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Date.php @@ -7,6 +7,9 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + /** * Product attribute data for attribute with input type date. */ @@ -56,6 +59,46 @@ public function getAttributeDataWithCheckArray(): array ); } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'date_attribute', + ], + ], + ] + ); + } + + /** + * @inheritdoc + */ + public function getUpdateProviderWithErrorMessage(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProviderWithErrorMessage(), + [ + "{$frontendInput}_wrong_default_value" => [ + 'post_data' => [ + 'default_value_date' => '//2019/12/12', + ], + 'error_message' => (string)__('The default date is invalid. Verify the date and try again.'), + ], + ] + ); + } + /** * @inheritdoc */ @@ -63,4 +106,56 @@ protected function getFrontendInput(): string { return 'date'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Date Attribute Update', + ], + 'frontend_input' => 'date', + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'default_value_date' => '12/29/2019', + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + unset($updatePostData['default_value_date']); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Date Attribute Update', + 'attribute_code' => 'date_attribute', + 'default_value' => '2019-12-29 00:00:00', + 'frontend_class' => null, + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'datetime', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/DateTime.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/DateTime.php new file mode 100644 index 0000000000000..70a8dc670e6d3 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/DateTime.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; + +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + +/** + * Product attribute data for attribute with input type datetime. + */ +class DateTime extends AbstractBaseAttributeData +{ + /** + * @inheritdoc + */ + public function __construct() + { + parent::__construct(); + $this->defaultAttributePostData['used_for_sort_by'] = '0'; + } + + /** + * @inheritdoc + */ + public function getAttributeData(): array + { + return array_replace_recursive( + parent::getAttributeData(), + [ + "{$this->getFrontendInput()}_with_default_value" => [ + [ + 'default_value_text' => '', + 'default_value_datetime' => '02/4/2020 6:30 AM', + ] + ] + ] + ); + } + + /** + * @inheritdoc + */ + public function getAttributeDataWithCheckArray(): array + { + return array_replace_recursive( + parent::getAttributeDataWithCheckArray(), + [ + "{$this->getFrontendInput()}_with_default_value" => [ + 1 => [ + 'default_value' => '2020-02-04 06:30:00', + ], + ], + ] + ); + } + + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'datetime_attribute', + ], + ], + ] + ); + } + + /** + * @inheritdoc + */ + public function getUpdateProviderWithErrorMessage(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProviderWithErrorMessage(), + [ + "{$frontendInput}_wrong_default_value" => [ + 'post_data' => [ + 'default_value_datetime' => '//02/4/2020 6:30 AM', + ], + 'error_message' => (string)__('The default date is invalid. Verify the date and try again.'), + ], + ] + ); + } + + /** + * @inheritdoc + */ + protected function getFrontendInput(): string + { + return 'datetime'; + } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Date Time Attribute Update', + ], + 'frontend_input' => 'datetime', + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'default_value_datetime' => '02/4/2020 6:30 AM', + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + unset($updatePostData['default_value_datetime']); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Date Time Attribute Update', + 'attribute_code' => 'datetime_attribute', + 'default_value' => '2020-02-04 06:30:00', + 'frontend_class' => null, + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'datetime', + ] + ); + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/DropDown.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/DropDown.php index 3c1acb5a33a54..8366b13760795 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/DropDown.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/DropDown.php @@ -7,6 +7,9 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + /** * Product attribute data for attribute with input type dropdown. */ @@ -22,6 +25,27 @@ public function __construct() $this->defaultAttributePostData['swatch_input_type'] = 'dropdown'; } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'dropdown_attribute', + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -29,4 +53,54 @@ protected function getFrontendInput(): string { return 'select'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Drop-Down Attribute Update', + ], + 'frontend_input' => 'select', + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_filterable' => '2', + 'is_filterable_in_search' => '1', + 'position' => '2', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '0', + 'is_visible_on_front' => '0', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Drop-Down Attribute Update', + 'attribute_code' => 'dropdown_attribute', + 'default_value' => null, + 'frontend_class' => null, + 'is_user_defined' => '1', + 'backend_type' => 'varchar', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php index 5fb5f745aebdc..4d72f5b316ea0 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php @@ -7,11 +7,35 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + /** * Product attribute data for attribute with input type multiple select. */ class MultipleSelect extends AbstractAttributeDataWithOptions { + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'multiselect_attribute', + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -19,4 +43,54 @@ protected function getFrontendInput(): string { return 'multiselect'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Multiselect Attribute Update', + ], + 'frontend_input' => 'multiselect', + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_filterable' => '2', + 'is_filterable_in_search' => '1', + 'position' => '2', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '0', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Multiselect Attribute Update', + 'attribute_code' => 'multiselect_attribute', + 'default_value' => null, + 'frontend_class' => null, + 'used_for_sort_by' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'varchar', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Text.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Text.php index 5b37248e27361..654e31a0f4528 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Text.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/Text.php @@ -7,6 +7,9 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + /** * Product attribute data for attribute with input type text. */ @@ -64,6 +67,27 @@ public function getAttributeDataWithCheckArray(): array ); } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'varchar_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'varchar_attribute', + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -71,4 +95,56 @@ protected function getFrontendInput(): string { return 'text'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Varchar Attribute Update', + ], + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'default_value_text' => 'Varchar Attribute Default', + 'is_unique' => '1', + 'frontend_class' => 'validate-alphanum', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '0', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + unset($updatePostData['default_value_text']); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Varchar Attribute Update', + 'frontend_input' => 'text', + 'attribute_code' => 'varchar_attribute', + 'default_value' => 'Varchar Attribute Default', + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'varchar', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextArea.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextArea.php index 7588b12700272..2b9414fe01390 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextArea.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextArea.php @@ -7,6 +7,9 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + /** * Product attribute data for attribute with text area input type. */ @@ -30,6 +33,36 @@ public function getAttributeData(): array ); } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'text_attribute', + ], + ], + "{$frontendInput}_change_frontend_input" => [ + 'post_data' => [ + 'frontend_input' => 'texteditor', + ], + 'expected_data' => [ + 'frontend_input' => 'textarea', + 'is_wysiwyg_enabled' => '1' + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -37,4 +70,57 @@ protected function getFrontendInput(): string { return 'textarea'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Text Attribute Update', + ], + 'frontend_input' => 'textarea', + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'default_value_textarea' => 'Text Attribute Default', + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + unset($updatePostData['default_value_textarea']); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Text Attribute Update', + 'attribute_code' => 'text_attribute', + 'default_value' => 'Text Attribute Default', + 'frontend_class' => null, + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'used_for_sort_by' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'text', + 'is_wysiwyg_enabled' => '0', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextEditor.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextEditor.php index d7a6276c1720f..282031e4377a5 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextEditor.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/TextEditor.php @@ -7,6 +7,9 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + /** * Product attribute data for attribute with text editor input type. */ @@ -116,6 +119,36 @@ public function getAttributeDataWithCheckArray(): array ); } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'text_editor_attribute', + ], + ], + "{$frontendInput}_change_frontend_input" => [ + 'post_data' => [ + 'frontend_input' => 'textarea', + ], + 'expected_data' => [ + 'frontend_input' => 'textarea', + 'is_wysiwyg_enabled' => '0' + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -123,4 +156,58 @@ protected function getFrontendInput(): string { return 'texteditor'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Text Editor Attribute Update', + ], + 'frontend_input' => 'texteditor', + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'default_value_textarea' => 'Text Editor Attribute Default', + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '1', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '1', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + unset($updatePostData['default_value_textarea']); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Text Editor Attribute Update', + 'frontend_input' => 'textarea', + 'attribute_code' => 'text_editor_attribute', + 'default_value' => 'Text Editor Attribute Default', + 'frontend_class' => null, + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'text', + 'is_wysiwyg_enabled' => '1', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/YesNo.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/YesNo.php index 8fece70f0273c..28428b38be009 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/YesNo.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/YesNo.php @@ -7,6 +7,9 @@ namespace Magento\TestFramework\Eav\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; + /** * Product attribute data for attribute with yes/no input type. */ @@ -58,6 +61,27 @@ public function getAttributeDataWithCheckArray(): array ); } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'boolean_attribute', + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -65,4 +89,56 @@ protected function getFrontendInput(): string { return 'boolean'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Boolean Attribute Update', + ], + 'frontend_input' => 'boolean', + 'is_required' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'default_value_yesno' => '1', + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '0', + 'is_comparable' => '1', + 'is_filterable' => '2', + 'is_filterable_in_search' => '0', + 'position' => '2', + 'is_used_for_promo_rules' => '1', + 'is_html_allowed_on_front' => '0', + 'is_visible_on_front' => '0', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + unset($updatePostData['default_value_yesno']); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Boolean Attribute Update', + 'attribute_code' => 'boolean_attribute', + 'default_value' => '1', + 'frontend_class' => null, + 'is_user_defined' => '1', + 'backend_type' => 'int', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/ResourceModel/GetEntityIdByAttributeId.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/ResourceModel/GetEntityIdByAttributeId.php index edb75a5d8d1bd..76235b3392684 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/ResourceModel/GetEntityIdByAttributeId.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/ResourceModel/GetEntityIdByAttributeId.php @@ -33,16 +33,24 @@ public function __construct( * * @param int $setId * @param int $attributeId + * @param int|null $attributeGroupId * @return int|null */ - public function execute(int $setId, int $attributeId): ?int + public function execute(int $setId, int $attributeId, ?int $attributeGroupId = null): ?int { $select = $this->attributeSetResource->getConnection()->select() - ->from($this->attributeSetResource->getTable('eav_entity_attribute')) + ->from( + $this->attributeSetResource->getTable('eav_entity_attribute'), + 'entity_attribute_id' + ) ->where('attribute_set_id = ?', $setId) ->where('attribute_id = ?', $attributeId); - $result = $this->attributeSetResource->getConnection()->fetchOne($select); - return $result ? (int)$result : null; + if ($attributeGroupId !== null) { + $select->where('attribute_group_id = ?', $attributeGroupId); + } + $entityAttributeId = $this->attributeSetResource->getConnection()->fetchOne($select); + + return $entityAttributeId ? (int)$entityAttributeId : null; } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Store/ExecuteInStoreContext.php b/dev/tests/integration/framework/Magento/TestFramework/Store/ExecuteInStoreContext.php new file mode 100644 index 0000000000000..eee7b81e8bd32 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Store/ExecuteInStoreContext.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Store; + +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Execute operation in specified store + */ +class ExecuteInStoreContext +{ + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @param StoreManagerInterface $storeManager + */ + public function __construct(StoreManagerInterface $storeManager) + { + $this->storeManager = $storeManager; + } + + /** + * Execute callback in store context + * + * @param null|string|bool|int|StoreInterface $store + * @param callable $method + * @param array $arguments + * @return mixed + */ + public function execute($store, callable $method, ...$arguments) + { + $storeCode = $store instanceof StoreInterface + ? $store->getCode() + : $this->storeManager->getStore($store)->getCode(); + $currentStore = $this->storeManager->getStore(); + + try { + if ($currentStore->getCode() !== $storeCode) { + $this->storeManager->setCurrentStore($storeCode); + } + + return $method(...array_values($arguments)); + } finally { + if ($currentStore->getCode() !== $storeCode) { + $this->storeManager->setCurrentStore($currentStore); + } + } + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/TextSwatch.php b/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/TextSwatch.php index c63873469e2f8..bb705b5503c39 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/TextSwatch.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/TextSwatch.php @@ -7,7 +7,9 @@ namespace Magento\TestFramework\Swatches\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\Swatches\Model\Swatch; +use Magento\Store\Model\Store; /** * Product attribute data for attribute with input type visual swatch. @@ -90,6 +92,88 @@ public function getAttributeDataWithCheckArray(): array ); } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'text_swatch_attribute', + ], + ], + "{$frontendInput}_change_frontend_input_swatch_visual" => [ + 'post_data' => [ + 'frontend_input' => Swatch::SWATCH_TYPE_VISUAL_ATTRIBUTE_FRONTEND_INPUT, + 'update_product_preview_image' => '1', + 'use_product_image_for_swatch' => '1', + ], + 'expected_data' => [ + 'frontend_input' => 'select', + 'swatch_input_type' => Swatch::SWATCH_INPUT_TYPE_VISUAL, + 'update_product_preview_image' => '1', + 'use_product_image_for_swatch' => '1', + ], + ], + "{$frontendInput}_change_frontend_input_dropdown" => [ + 'post_data' => [ + 'frontend_input' => 'select', + ], + 'expected_data' => [ + 'frontend_input' => 'select', + 'swatch_input_type' => null, + 'update_product_preview_image' => null, + 'use_product_image_for_swatch' => null, + ], + ], + ] + ); + } + + /** + * @inheritdoc + */ + public function getUpdateOptionsProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateOptionsProvider(), + [ + "{$frontendInput}_update_options" => [ + 'post_data' => [ + 'options_array' => [ + 'option_1' => [ + 'order' => '4', + 'swatch' => [ + Store::DEFAULT_STORE_ID => 'Swatch 1 Admin', + 'default' => 'Swatch 1 Store 1', + 'fixture_second_store' => 'Swatch 1 Store 2', + 'fixture_third_store' => 'Swatch 1 Store 3', + ], + ], + 'option_2' => [ + 'order' => '5', + 'swatch' => [ + Store::DEFAULT_STORE_ID => 'Swatch 2 Admin', + 'default' => 'Swatch 2 Store 1', + 'fixture_second_store' => 'Swatch 2 Store 2', + 'fixture_third_store' => 'Swatch 2 Store 3', + ], + ], + ], + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -157,4 +241,56 @@ protected function getFrontendInput(): string { return Swatch::SWATCH_TYPE_TEXTUAL_ATTRIBUTE_FRONTEND_INPUT; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Text swatch attribute Update', + ], + 'frontend_input' => Swatch::SWATCH_TYPE_TEXTUAL_ATTRIBUTE_FRONTEND_INPUT, + 'is_required' => '1', + 'update_product_preview_image' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_filterable' => '2', + 'is_filterable_in_search' => '1', + 'position' => '2', + 'is_used_for_promo_rules' => '1', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Text swatch attribute Update', + 'frontend_input' => 'select', + 'attribute_code' => 'text_swatch_attribute', + 'default_value' => null, + 'frontend_class' => null, + 'is_html_allowed_on_front' => '1', + 'is_user_defined' => '1', + 'backend_type' => 'int', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/VisualSwatch.php b/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/VisualSwatch.php index b5e32c40ef8a1..c5195d7d1d1a4 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/VisualSwatch.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Swatches/Model/Attribute/DataProvider/VisualSwatch.php @@ -7,7 +7,9 @@ namespace Magento\TestFramework\Swatches\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\Swatches\Model\Swatch; +use Magento\Store\Model\Store; /** * Product attribute data for attribute with input type visual swatch. @@ -90,6 +92,81 @@ public function getAttributeDataWithCheckArray(): array ); } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'visual_swatch_attribute', + ], + ], + "{$frontendInput}_change_frontend_input_swatch_text" => [ + 'post_data' => [ + 'frontend_input' => Swatch::SWATCH_TYPE_TEXTUAL_ATTRIBUTE_FRONTEND_INPUT, + 'update_product_preview_image' => '1', + ], + 'expected_data' => [ + 'frontend_input' => 'select', + 'swatch_input_type' => Swatch::SWATCH_INPUT_TYPE_TEXT, + 'update_product_preview_image' => '1', + 'use_product_image_for_swatch' => 0, + ], + ], + "{$frontendInput}_change_frontend_input_dropdown" => [ + 'post_data' => [ + 'frontend_input' => 'select', + ], + 'expected_data' => [ + 'frontend_input' => 'select', + 'swatch_input_type' => null, + 'update_product_preview_image' => null, + 'use_product_image_for_swatch' => null, + ], + ], + ] + ); + } + + /** + * @inheritdoc + */ + public function getUpdateOptionsProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateOptionsProvider(), + [ + "{$frontendInput}_update_options" => [ + 'post_data' => [ + 'options_array' => [ + 'option_1' => [ + 'order' => '4', + 'swatch' => [ + Store::DEFAULT_STORE_ID => '#1a1a1a', + ], + ], + 'option_2' => [ + 'order' => '5', + 'swatch' => [ + Store::DEFAULT_STORE_ID => '#2b2b2b', + ], + ], + ], + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -148,4 +225,57 @@ protected function getFrontendInput(): string { return Swatch::SWATCH_TYPE_VISUAL_ATTRIBUTE_FRONTEND_INPUT; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Visual swatch attribute Update', + ], + 'frontend_input' => Swatch::SWATCH_TYPE_VISUAL_ATTRIBUTE_FRONTEND_INPUT, + 'is_required' => '1', + 'update_product_preview_image' => '1', + 'use_product_image_for_swatch' => '1', + 'is_global' => ScopedAttributeInterface::SCOPE_WEBSITE, + 'is_unique' => '1', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + 'is_searchable' => '1', + 'search_weight' => '2', + 'is_visible_in_advanced_search' => '1', + 'is_comparable' => '1', + 'is_filterable' => '2', + 'is_filterable_in_search' => '1', + 'position' => '2', + 'is_used_for_promo_rules' => '1', + 'is_visible_on_front' => '1', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Visual swatch attribute Update', + 'frontend_input' => 'select', + 'attribute_code' => 'visual_swatch_attribute', + 'default_value' => null, + 'frontend_class' => null, + 'is_html_allowed_on_front' => '1', + 'is_user_defined' => '1', + 'backend_type' => 'int', + ] + ); + } } diff --git a/dev/tests/integration/framework/Magento/TestFramework/Weee/Model/Attribute/DataProvider/FixedProductTax.php b/dev/tests/integration/framework/Magento/TestFramework/Weee/Model/Attribute/DataProvider/FixedProductTax.php index 2f1f625ad48ac..ab0b214ccc101 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Weee/Model/Attribute/DataProvider/FixedProductTax.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Weee/Model/Attribute/DataProvider/FixedProductTax.php @@ -7,7 +7,10 @@ namespace Magento\TestFramework\Weee\Model\Attribute\DataProvider; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\TestFramework\Eav\Model\Attribute\DataProvider\AbstractBaseAttributeData; +use Magento\Store\Model\Store; +use Magento\Weee\Model\Attribute\Backend\Weee\Tax; /** * Product attribute data for attribute with input type fixed product tax. @@ -47,6 +50,27 @@ public function getAttributeDataWithCheckArray(): array return $result; } + /** + * @inheritdoc + */ + public function getUpdateProvider(): array + { + $frontendInput = $this->getFrontendInput(); + return array_replace_recursive( + parent::getUpdateProvider(), + [ + "{$frontendInput}_other_attribute_code" => [ + 'post_data' => [ + 'attribute_code' => 'text_attribute_update', + ], + 'expected_data' => [ + 'attribute_code' => 'fixed_product_attribute', + ], + ], + ] + ); + } + /** * @inheritdoc */ @@ -54,4 +78,55 @@ protected function getFrontendInput(): string { return 'weee'; } + + /** + * @inheritdoc + */ + protected function getUpdatePostData(): array + { + return [ + 'frontend_label' => [ + Store::DEFAULT_STORE_ID => 'Fixed product tax Update', + ], + 'frontend_input' => 'weee', + 'is_used_in_grid' => '1', + 'is_visible_in_grid' => '1', + 'is_filterable_in_grid' => '1', + ]; + } + + /** + * @inheritdoc + */ + protected function getUpdateExpectedData(): array + { + $updatePostData = $this->getUpdatePostData(); + return array_merge( + $updatePostData, + [ + 'frontend_label' => 'Fixed product tax Update', + 'is_required' => '0', + 'attribute_code' => 'fixed_product_attribute', + 'is_global' => ScopedAttributeInterface::SCOPE_GLOBAL, + 'default_value' => null, + 'is_unique' => '0', + 'frontend_class' => null, + 'is_searchable' => '0', + 'search_weight' => '1', + 'is_visible_in_advanced_search' => '0', + 'is_comparable' => '0', + 'is_filterable' => '0', + 'is_filterable_in_search' => '0', + 'position' => '0', + 'is_used_for_promo_rules' => '0', + 'is_html_allowed_on_front' => '0', + 'is_visible_on_front' => '0', + 'used_in_product_listing' => '0', + 'used_for_sort_by' => '0', + 'is_user_defined' => '1', + 'backend_type' => 'static', + 'backend_model' => Tax::class, + ] + ); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/CheckProductPriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/CheckProductPriceTest.php new file mode 100644 index 0000000000000..511b2afe2e0f4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/CheckProductPriceTest.php @@ -0,0 +1,310 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Product\ListProduct; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Block\Product\ListProduct; +use Magento\Customer\Model\Session; +use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Result\PageFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Check that product price render correctly on category page. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + */ +class CheckProductPriceTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var PageFactory + */ + private $pageFactory; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var Session + */ + private $customerSession; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->pageFactory = $this->objectManager->get(PageFactory::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->customerSession = $this->objectManager->create(Session::class); + parent::setUp(); + } + + /** + * Assert that product price without additional price configurations will render as expected. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_tax_none.php + * + * @return void + */ + public function testCheckProductPriceWithoutAdditionalPriceConfigurations(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 205.00); + } + + /** + * Assert that product special price rendered correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_special_price.php + * + * @return void + */ + public function testCheckSpecialPrice(): void + { + $priceHtml = $this->getProductPriceHtml('simple'); + $this->assertFinalPrice($priceHtml, 5.99); + $this->assertRegularPrice($priceHtml, 10.00); + } + + /** + * Assert that product with fixed tier price is renders correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_fixed_tier_price.php + * + * @return void + */ + public function testCheckFixedTierPrice(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 205.00); + $this->assertAsLowAsPrice($priceHtml, 40.00); + } + + /** + * Assert that price of product with percent tier price rendered correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_percent_tier_price.php + * + * @return void + */ + public function testCheckPercentTierPrice(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 205.00); + $this->assertAsLowAsPrice($priceHtml, 102.50); + } + + /** + * Assert that price of product with fixed tier price for not logged user is renders correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_not_logged_user.php + * + * @return void + */ + public function testCheckFixedTierPriceForNotLoggedUser(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 30.00); + $this->assertRegularPrice($priceHtml, 205.00); + } + + /** + * Assert that price of product with fixed tier price for logged user is renders correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_logged_user.php + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * + * @return void + */ + public function testCheckFixedTierPriceForLoggedUser(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 205.00); + $this->assertNotRegExp('/\$10/', $priceHtml); + $this->customerSession->setCustomerId(1); + try { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 10.00); + $this->assertRegularPrice($priceHtml, 205.00); + } finally { + $this->customerSession->setCustomerId(null); + } + } + + /** + * Assert that price of product with catalog rule with action equal to "Apply as percentage of original" + * is renders correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_tax_none.php + * @magentoDataFixture Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user.php + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * + * @return void + */ + public function testCheckPriceRendersCorrectlyWithApplyAsPercentageOfOriginalRule(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 184.50); + $this->assertRegularPrice($priceHtml, 205.00); + } + + /** + * Assert that price of product with catalog rule with action equal to "Apply as fixed amount" + * is renders correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_tax_none.php + * @magentoDataFixture Magento/CatalogRule/_files/rule_apply_as_fixed_amount_not_logged_user.php + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * + * @return void + */ + public function testCheckPriceRendersCorrectlyWithApplyAsFixedAmountRule(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 195.00); + $this->assertRegularPrice($priceHtml, 205.00); + } + + /** + * Assert that price of product with catalog rule with action equal to "Adjust final price to this percentage" + * is renders correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_tax_none.php + * @magentoDataFixture Magento/CatalogRule/_files/rule_adjust_final_price_to_this_percentage_not_logged_user.php + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * + * @return void + */ + public function testCheckPriceRendersCorrectlyWithAdjustFinalPriceToThisPercentageRule(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 20.50); + $this->assertRegularPrice($priceHtml, 205.00); + } + + /** + * Assert that price of product with catalog rule with action equal to "Adjust final price to discount value" + * is renders correctly. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_tax_none.php + * @magentoDataFixture Magento/CatalogRule/_files/rule_adjust_final_price_to_discount_value_not_logged_user.php + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * + * @return void + */ + public function testCheckPriceRendersCorrectlyWithAdjustFinalPriceToDiscountValueRule(): void + { + $priceHtml = $this->getProductPriceHtml('simple-product-tax-none'); + $this->assertFinalPrice($priceHtml, 10.00); + $this->assertRegularPrice($priceHtml, 205.00); + } + + /** + * Assert that price html contain "As low as" label and expected price amount. + * + * @param string $priceHtml + * @param float $expectedPrice + * @return void + */ + private function assertAsLowAsPrice(string $priceHtml, float $expectedPrice): void + { + $this->assertRegExp( + sprintf( + '/<span class="price-label">As low as<\/span> {1,}<span.*data-price-amount="%s".*>\$%01.2f<\/span>/', + round($expectedPrice, 2), + $expectedPrice + ), + $priceHtml + ); + } + + /** + * Assert that price html contain expected final price amount. + * + * @param string $priceHtml + * @param float $expectedPrice + * @return void + */ + private function assertFinalPrice(string $priceHtml, float $expectedPrice): void + { + $this->assertRegExp( + sprintf( + '/data-price-type="finalPrice".*<span class="price">\$%01.2f<\/span><\/span>/', + $expectedPrice + ), + $priceHtml + ); + } + + /** + * Assert that price html contain "Regular price" label and expected price amount. + * + * @param string $priceHtml + * @param float $expectedPrice + * @return void + */ + private function assertRegularPrice(string $priceHtml, float $expectedPrice): void + { + $regex = '<span class="price-label">Regular Price<\/span> {1,}<span.*data-price-amount="%s".*>\$%01.2f<\/span>'; + $this->assertRegExp( + sprintf("/{$regex}/", round($expectedPrice, 2), $expectedPrice), + $priceHtml + ); + } + + /** + * Return html of product price without new line characters. + * + * @param string $sku + * @return string + */ + private function getProductPriceHtml(string $sku): string + { + $product = $this->productRepository->get($sku, false, null, true); + + return preg_replace('/[\n\r]/', '', $this->getListProductBlock()->getProductPrice($product)); + } + + /** + * Get list product block from layout. + * + * @return ListProduct + */ + private function getListProductBlock(): ListProduct + { + $page = $this->pageFactory->create(); + $page->addHandle([ + 'default', + 'catalog_category_view', + ]); + $page->getLayout()->generateXml(); + /** @var Template $categoryProductsBlock */ + $categoryProductsBlock = $page->getLayout()->getBlock('category.products'); + + return $categoryProductsBlock->getChildBlock('product_list'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/AbstractCurrencyTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/AbstractCurrencyTest.php new file mode 100644 index 0000000000000..2ae71797e52e5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/AbstractCurrencyTest.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Product\View; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\View\Result\PageFactory; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class consist of general logic for currency tests + */ +abstract class AbstractCurrencyTest extends TestCase +{ + protected const TIER_PRICE_BLOCK_NAME = 'product.price.tier'; + protected const FINAL_PRICE_BLOCK_NAME = 'product.price.final'; + + /** @var ObjectManagerInterface */ + protected $objectManager; + + /** @var Registry */ + protected $registry; + + /** @var PageFactory */ + private $pageFactory; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->pageFactory = $this->objectManager->get(PageFactory::class); + } + + /** + * @inheridoc + */ + protected function tearDown() + { + $this->registry->unregister('product'); + + parent::tearDown(); + } + + /** + * Process price view on product page + * + * @param string|ProductInterface $product + * @param string $blockName + * @return string + */ + protected function processPriceView($product, string $blockName = self::FINAL_PRICE_BLOCK_NAME): string + { + $product = is_string($product) ? $this->productRepository->get($product) : $product; + $this->registerProduct($product); + + return trim( + preg_replace('/(?:\s| )+/', ' ', strip_tags($this->getProductPriceBlockHtml($blockName))) + ); + } + + /** + * Get product price block content + * + * @param string $blockName + * @return string + */ + private function getProductPriceBlockHtml(string $blockName): string + { + $page = $this->pageFactory->create(); + $page->addHandle([ + 'default', + 'catalog_product_view', + 'catalog_product_view_type_configurable', + ]); + $page->getLayout()->generateXml(); + $block = $page->getLayout()->getBlock($blockName); + $this->assertNotFalse($block); + + return $block->toHtml(); + } + + /** + * Register the product + * + * @param ProductInterface $product + * @return void + */ + private function registerProduct(ProductInterface $product): void + { + $this->registry->unregister('product'); + $this->registry->register('product', $product); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php index 9bcdb00eebe7c..a3545e4a39e80 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php @@ -120,9 +120,23 @@ public function testGetGalleryImagesJsonWithoutImages(): void $this->assertImages(reset($result), $this->placeholderExpectation); } + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoConfigFixture default/web/url/catalog_media_url_format image_optimization_parameters + * @magentoDbIsolation enabled + * @return void + */ + public function testGetGalleryImagesJsonWithoutImagesWithImageOptimizationParametersInUrl(): void + { + $this->block->setData('product', $this->getProduct()); + $result = $this->serializer->unserialize($this->block->getGalleryImagesJson()); + $this->assertImages(reset($result), $this->placeholderExpectation); + } + /** * @dataProvider galleryDisabledImagesDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php + * @magentoConfigFixture default/web/url/catalog_media_url_format hash * @magentoDbIsolation enabled * @param array $images * @param array $expectation @@ -141,6 +155,7 @@ public function testGetGalleryImagesJsonWithDisabledImage(array $images, array $ * @dataProvider galleryDisabledImagesDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoConfigFixture default/web/url/catalog_media_url_format hash * @magentoDbIsolation disabled * @param array $images * @param array $expectation @@ -173,6 +188,8 @@ public function galleryDisabledImagesDataProvider(): array } /** + * Test default image generation format. + * * @dataProvider galleryImagesDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php * @magentoDbIsolation enabled @@ -230,10 +247,95 @@ public function galleryImagesDataProvider(): array ]; } + /** + * @dataProvider galleryImagesWithImageOptimizationParametersInUrlDataProvider + * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php + * @magentoConfigFixture default/web/url/catalog_media_url_format image_optimization_parameters + * @magentoDbIsolation enabled + * @param array $images + * @param array $expectation + * @return void + */ + public function testGetGalleryImagesJsonWithImageOptimizationParametersInUrl( + array $images, + array $expectation + ): void { + $product = $this->getProduct(); + $this->setGalleryImages($product, $images); + $this->block->setData('product', $this->getProduct()); + [$firstImage, $secondImage] = $this->serializer->unserialize($this->block->getGalleryImagesJson()); + [$firstExpectedImage, $secondExpectedImage] = $expectation; + $this->assertImages($firstImage, $firstExpectedImage); + $this->assertImages($secondImage, $secondExpectedImage); + } + + /** + * @return array + */ + public function galleryImagesWithImageOptimizationParametersInUrlDataProvider(): array + { + + $imageExpectation = [ + 'thumb' => '/m/a/magento_image.jpg?width=88&height=110&store=default&image-type=thumbnail', + 'img' => '/m/a/magento_image.jpg?width=700&height=700&store=default&image-type=image', + 'full' => '/m/a/magento_image.jpg?store=default&image-type=image', + 'caption' => 'Image Alt Text', + 'position' => '1', + 'isMain' => false, + 'type' => 'image', + 'videoUrl' => null, + ]; + + $thumbnailExpectation = [ + 'thumb' => '/m/a/magento_thumbnail.jpg?width=88&height=110&store=default&image-type=thumbnail', + 'img' => '/m/a/magento_thumbnail.jpg?width=700&height=700&store=default&image-type=image', + 'full' => '/m/a/magento_thumbnail.jpg?store=default&image-type=image', + 'caption' => 'Thumbnail Image', + 'position' => '2', + 'isMain' => false, + 'type' => 'image', + 'videoUrl' => null, + ]; + + return [ + 'with_main_image' => [ + 'images' => [ + '/m/a/magento_image.jpg' => [], + '/m/a/magento_thumbnail.jpg' => ['main' => true], + ], + 'expectation' => [ + $imageExpectation, + array_merge($thumbnailExpectation, ['isMain' => true]), + ], + ], + 'without_main_image' => [ + 'images' => [ + '/m/a/magento_image.jpg' => [], + '/m/a/magento_thumbnail.jpg' => [], + ], + 'expectation' => [ + array_merge($imageExpectation, ['isMain' => true]), + $thumbnailExpectation, + ], + ], + 'with_changed_position' => [ + 'images' => [ + '/m/a/magento_image.jpg' => ['position' => '2'], + '/m/a/magento_thumbnail.jpg' => ['position' => '1'], + ], + 'expectation' => [ + array_merge($thumbnailExpectation, ['position' => '1']), + array_merge($imageExpectation, ['position' => '2', 'isMain' => true]), + ], + ], + ]; + } + /** * @dataProvider galleryImagesOnStoreViewDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoConfigFixture default/web/url/catalog_media_url_format hash * @magentoDbIsolation disabled * @param array $images * @param array $expectation diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/MultiStoreCurrencyTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/MultiStoreCurrencyTest.php new file mode 100644 index 0000000000000..22d30fd3d9ea8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/MultiStoreCurrencyTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Product\View; + +use Magento\Store\Model\StoreManagerInterface; + +/** + * Checks currency displaying and converting on the catalog pages on multi store mode + * + * @magentoAppArea frontend + * @magentoDbIsolation disabled + */ +class MultiStoreCurrencyTest extends AbstractCurrencyTest +{ + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + } + + /** + * @magentoConfigFixture default/currency/options/base USD + * @magentoConfigFixture current_store currency/options/default CNY + * @magentoConfigFixture current_store currency/options/allow CNY,USD + * @magentoConfigFixture fixturestore_store currency/options/default UAH + * @magentoConfigFixture fixturestore_store currency/options/allow UAH,USD + * + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @magentoDataFixture Magento/Directory/_files/usd_cny_rate.php + * @magentoDataFixture Magento/Directory/_files/usd_uah_rate.php + * + * @return void + */ + public function testMultiStoreRenderPrice(): void + { + $this->assertProductStorePrice('simple2', 'CN¥70.00'); + $this->reloadProductPriceInfo(); + $this->assertProductStorePrice('simple2', '₴240.00', 'fixturestore'); + } + + /** + * @magentoConfigFixture default/currency/options/base USD + * @magentoConfigFixture current_store currency/options/default CNY + * @magentoConfigFixture current_store currency/options/allow CNY,USD + * @magentoConfigFixture fixturestore_store currency/options/default UAH + * @magentoConfigFixture fixturestore_store currency/options/allow UAH,USD + * + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/Catalog/_files/product_special_price.php + * @magentoDataFixture Magento/Directory/_files/usd_cny_rate.php + * @magentoDataFixture Magento/Directory/_files/usd_uah_rate.php + * + * @return void + */ + public function testMultiStoreRenderSpecialPrice(): void + { + $this->assertProductStorePrice('simple', 'Special Price CN¥41.93 Regular Price CN¥70.00'); + $this->reloadProductPriceInfo(); + $this->assertProductStorePrice('simple', 'Special Price ₴143.76 Regular Price ₴240.00', 'fixturestore'); + } + + /** + * @magentoConfigFixture default/currency/options/base USD + * @magentoConfigFixture current_store currency/options/default CNY + * @magentoConfigFixture current_store currency/options/allow CNY,USD + * @magentoConfigFixture fixturestore_store currency/options/default UAH + * @magentoConfigFixture fixturestore_store currency/options/allow UAH,USD + * + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_fixed_tier_price.php + * @magentoDataFixture Magento/Directory/_files/usd_cny_rate.php + * @magentoDataFixture Magento/Directory/_files/usd_uah_rate.php + * + * @return void + */ + public function testMultiStoreRenderTierPrice(): void + { + $this->assertProductStorePrice( + 'simple-product-tax-none', + 'Buy 2 for CN¥280.00 each and save 80%', + 'default', + self::TIER_PRICE_BLOCK_NAME + ); + $this->reloadProductPriceInfo(); + $this->assertProductStorePrice( + 'simple-product-tax-none', + 'Buy 2 for ₴960.00 each and save 80%', + 'fixturestore', + self::TIER_PRICE_BLOCK_NAME + ); + } + + /** + * Check price per stores + * + * @param string $productSku + * @param string $expectedData + * @param string $storeCode + * @param string $priceBlockName + * @return void + */ + private function assertProductStorePrice( + string $productSku, + string $expectedData, + string $storeCode = 'default', + string $priceBlockName = self::FINAL_PRICE_BLOCK_NAME + ): void { + $currentStore = $this->storeManager->getStore(); + try { + if ($currentStore->getCode() !== $storeCode) { + $this->storeManager->setCurrentStore($storeCode); + } + + $actualData = $this->processPriceView($productSku, $priceBlockName); + $this->assertEquals($expectedData, $actualData); + } finally { + if ($currentStore->getCode() !== $storeCode) { + $this->storeManager->setCurrentStore($currentStore); + } + } + } + + /** + * Reload product price info + * + * @return void + */ + private function reloadProductPriceInfo(): void + { + $product = $this->registry->registry('product'); + $this->assertNotNull($product); + $product->reloadPriceInfo(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/AbstractRenderCustomOptionsTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/AbstractRenderCustomOptionsTest.php new file mode 100644 index 0000000000000..659cf83407a9e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/AbstractRenderCustomOptionsTest.php @@ -0,0 +1,340 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Product\View\Options; + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; +use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterfaceFactory; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Block\Product\View\Options; +use Magento\Catalog\Model\Product\Option; +use Magento\Catalog\Model\Product\Option\Value; +use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Base logic for render custom options and check that option renders as expected. + */ +abstract class AbstractRenderCustomOptionsTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ProductCustomOptionInterfaceFactory + */ + private $productCustomOptionFactory; + + /** + * @var ProductCustomOptionValuesInterfaceFactory + */ + private $productCustomOptionValuesFactory; + + /** + * @var Page + */ + private $page; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->productCustomOptionFactory = $this->objectManager->get(ProductCustomOptionInterfaceFactory::class); + $this->productCustomOptionValuesFactory = $this->objectManager->get( + ProductCustomOptionValuesInterfaceFactory::class + ); + $this->page = $this->objectManager->create(Page::class); + parent::setUp(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->productRepository->cleanCache(); + parent::tearDown(); + } + + /** + * Add provided options from text group to product, render options block + * and check that options rendered as expected. + * + * @param string $productSku + * @param array $optionData + * @param array $checkArray + * @return void + */ + protected function assertTextOptionRenderingOnProduct( + string $productSku, + array $optionData, + array $checkArray + ): void { + $product = $this->productRepository->get($productSku); + $product = $this->addOptionToProduct($product, $optionData); + $option = $this->findOptionByTitle($product, $optionData[Option::KEY_TITLE]); + $optionHtml = $this->getOptionHtml($product); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + + if ($optionData[Option::KEY_MAX_CHARACTERS] > 0) { + $this->assertContains($checkArray['max_characters'], $optionHtml); + } else { + $this->assertNotContains('class="character-counter', $optionHtml); + } + } + + /** + * Add provided options from file group to product, render options block + * and check that options rendered as expected. + * + * @param string $productSku + * @param array $optionData + * @param array $checkArray + * @return void + */ + protected function assertFileOptionRenderingOnProduct( + string $productSku, + array $optionData, + array $checkArray + ): void { + $product = $this->productRepository->get($productSku); + $product = $this->addOptionToProduct($product, $optionData); + $option = $this->findOptionByTitle($product, $optionData[Option::KEY_TITLE]); + $optionHtml = $this->getOptionHtml($product); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + $this->assertContains($checkArray['file_extension'], $optionHtml); + + if (isset($checkArray['file_width'])) { + $checkArray['file_width'] = sprintf($checkArray['file_width'], __('Maximum image width')); + $this->assertRegExp($checkArray['file_width'], $optionHtml); + } + + if (isset($checkArray['file_height'])) { + $checkArray['file_height'] = sprintf($checkArray['file_height'], __('Maximum image height')); + $this->assertRegExp($checkArray['file_height'], $optionHtml); + } + } + + /** + * Add provided options from select group to product, render options block + * and check that options rendered as expected. + * + * @param string $productSku + * @param array $optionData + * @param array $optionValueData + * @param array $checkArray + * @return void + */ + protected function assertSelectOptionRenderingOnProduct( + string $productSku, + array $optionData, + array $optionValueData, + array $checkArray + ): void { + $product = $this->productRepository->get($productSku); + $product = $this->addOptionToProduct($product, $optionData, $optionValueData); + $option = $this->findOptionByTitle($product, $optionData[Option::KEY_TITLE]); + $optionValues = $option->getValues(); + $optionValue = reset($optionValues); + $optionHtml = $this->getOptionHtml($product); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + + if (isset($checkArray['not_contain_arr'])) { + foreach ($checkArray['not_contain_arr'] as $notContainPattern) { + $this->assertNotRegExp($notContainPattern, $optionHtml); + } + } + + if (isset($checkArray['option_value_item'])) { + $checkArray['option_value_item'] = sprintf( + $checkArray['option_value_item'], + $optionValue->getOptionTypeId(), + $optionValueData[Value::KEY_TITLE] + ); + $this->assertRegExp($checkArray['option_value_item'], $optionHtml); + } + } + + /** + * Add provided options from date group to product, render options block + * and check that options rendered as expected. + * + * @param string $productSku + * @param array $optionData + * @param array $checkArray + * @return void + */ + protected function assertDateOptionRenderingOnProduct( + string $productSku, + array $optionData, + array $checkArray + ): void { + $product = $this->productRepository->get($productSku); + $product = $this->addOptionToProduct($product, $optionData); + $option = $this->findOptionByTitle($product, $optionData[Option::KEY_TITLE]); + $optionHtml = $this->getOptionHtml($product); + $this->baseOptionAsserts($option, $optionHtml, $checkArray); + + switch ($optionData[Option::KEY_TYPE]) { + case ProductCustomOptionInterface::OPTION_TYPE_DATE: + $this->assertContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); + break; + case ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME: + $this->assertContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); + break; + case ProductCustomOptionInterface::OPTION_TYPE_TIME: + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); + $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); + $this->assertContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); + break; + } + } + + /** + * Base asserts for rendered options. + * + * @param ProductCustomOptionInterface $option + * @param string $optionHtml + * @param array $checkArray + * @return void + */ + private function baseOptionAsserts( + ProductCustomOptionInterface $option, + string $optionHtml, + array $checkArray + ): void { + $this->assertContains($checkArray['block_with_required_class'], $optionHtml); + $this->assertContains($checkArray['title'], $optionHtml); + + if (isset($checkArray['label_for_created_option'])) { + $checkArray['label_for_created_option'] = sprintf( + $checkArray['label_for_created_option'], + $option->getOptionId() + ); + $this->assertContains($checkArray['label_for_created_option'], $optionHtml); + } + + if (isset($checkArray['price'])) { + $this->assertContains($checkArray['price'], $optionHtml); + } + + if (isset($checkArray['required_element'])) { + $this->assertRegExp($checkArray['required_element'], $optionHtml); + } + } + + /** + * Add custom option to product with data. + * + * @param ProductInterface $product + * @param array $optionData + * @param array $optionValueData + * @return ProductInterface + */ + private function addOptionToProduct( + ProductInterface $product, + array $optionData, + array $optionValueData = [] + ): ProductInterface { + $optionData[Option::KEY_PRODUCT_SKU] = $product->getSku(); + + if (!empty($optionValueData)) { + $optionValueData = $this->productCustomOptionValuesFactory->create(['data' => $optionValueData]); + $optionData['values'] = [$optionValueData]; + } + + $option = $this->productCustomOptionFactory->create(['data' => $optionData]); + $product->setOptions([$option]); + + return $this->productRepository->save($product); + } + + /** + * Render custom options block. + * + * @param ProductInterface $product + * @return string + */ + private function getOptionHtml(ProductInterface $product): string + { + $optionsBlock = $this->getOptionsBlock(); + $optionsBlock->setProduct($product); + + return $optionsBlock->toHtml(); + } + + /** + * Get options block. + * + * @return Options + */ + private function getOptionsBlock(): Options + { + $this->page->addHandle($this->getHandlesList()); + $this->page->getLayout()->generateXml(); + /** @var Template $productInfoFormOptionsBlock */ + $productInfoFormOptionsBlock = $this->page->getLayout()->getBlock('product.info.form.options'); + $optionsWrapperBlock = $productInfoFormOptionsBlock->getChildBlock('product_options_wrapper'); + + return $optionsWrapperBlock->getChildBlock('product_options'); + } + + /** + * Find and return custom option. + * + * @param ProductInterface $product + * @param string $optionTitle + * @return null|Option + */ + private function findOptionByTitle(ProductInterface $product, string $optionTitle): ?Option + { + $option = null; + foreach ($product->getOptions() as $customOption) { + if ($customOption->getTitle() === $optionTitle) { + $option = $customOption; + break; + } + } + + return $option; + } + + /** + * Return all need handles for load. + * + * @return array + */ + abstract protected function getHandlesList(): array; +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/RenderOptionsTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/RenderOptionsTest.php index e83563a6ad474..da31cfc74476a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/RenderOptionsTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/Options/RenderOptionsTest.php @@ -7,74 +7,19 @@ namespace Magento\Catalog\Block\Product\View\Options; -use Magento\Catalog\Api\Data\ProductCustomOptionInterface; -use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; -use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterfaceFactory; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Catalog\Block\Product\View\Options; -use Magento\Catalog\Model\Product\Option; -use Magento\Catalog\Model\Product\Option\Value; -use Magento\Framework\View\Element\Template; -use Magento\Framework\View\Result\Page; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\Helper\CacheCleaner; -use Magento\TestFramework\ObjectManager; -use PHPUnit\Framework\TestCase; - /** - * Assert that product custom options render as expected. + * Test cases related to check that simple product custom option renders as expected. * * @magentoDbIsolation disabled * @magentoAppArea frontend */ -class RenderOptionsTest extends TestCase +class RenderOptionsTest extends AbstractRenderCustomOptionsTest { - /** - * @var ObjectManager - */ - private $objectManager; - - /** - * @var ProductRepositoryInterface - */ - private $productRepository; - - /** - * @var ProductCustomOptionInterfaceFactory - */ - private $productCustomOptionFactory; - - /** - * @var ProductCustomOptionValuesInterfaceFactory - */ - private $productCustomOptionValuesFactory; - - /** - * @var Page - */ - private $page; - - /** - * @inheritdoc - */ - protected function setUp() - { - CacheCleaner::cleanAll(); - $this->objectManager = Bootstrap::getObjectManager(); - $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); - $this->productCustomOptionFactory = $this->objectManager->get(ProductCustomOptionInterfaceFactory::class); - $this->productCustomOptionValuesFactory = $this->objectManager->get( - ProductCustomOptionValuesInterfaceFactory::class - ); - $this->page = $this->objectManager->create(Page::class); - parent::setUp(); - } - /** * Check that options from text group(field, area) render as expected. * * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php - * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\TextGroupDataProvider::getData() + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\TextGroupDataProvider::getData * * @param array $optionData * @param array $checkArray @@ -82,22 +27,14 @@ protected function setUp() */ public function testRenderCustomOptionsFromTextGroup(array $optionData, array $checkArray): void { - $option = $this->addOptionToProduct($optionData); - $optionHtml = $this->getOptionHtml(); - $this->baseOptionAsserts($option, $optionHtml, $checkArray); - - if ($optionData[Option::KEY_MAX_CHARACTERS] > 0) { - $this->assertContains($checkArray['max_characters'], $optionHtml); - } else { - $this->assertNotContains('class="character-counter', $optionHtml); - } + $this->assertTextOptionRenderingOnProduct('simple', $optionData, $checkArray); } /** * Check that options from file group(file) render as expected. * * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php - * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\FileGroupDataProvider::getData() + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\FileGroupDataProvider::getData * * @param array $optionData * @param array $checkArray @@ -105,27 +42,14 @@ public function testRenderCustomOptionsFromTextGroup(array $optionData, array $c */ public function testRenderCustomOptionsFromFileGroup(array $optionData, array $checkArray): void { - $option = $this->addOptionToProduct($optionData); - $optionHtml = $this->getOptionHtml(); - $this->baseOptionAsserts($option, $optionHtml, $checkArray); - $this->assertContains($checkArray['file_extension'], $optionHtml); - - if (isset($checkArray['file_width'])) { - $checkArray['file_width'] = sprintf($checkArray['file_width'], __('Maximum image width')); - $this->assertRegExp($checkArray['file_width'], $optionHtml); - } - - if (isset($checkArray['file_height'])) { - $checkArray['file_height'] = sprintf($checkArray['file_height'], __('Maximum image height')); - $this->assertRegExp($checkArray['file_height'], $optionHtml); - } + $this->assertFileOptionRenderingOnProduct('simple', $optionData, $checkArray); } /** * Check that options from select group(drop-down, radio buttons, checkbox, multiple select) render as expected. * * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php - * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\SelectGroupDataProvider::getData() + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\SelectGroupDataProvider::getData * * @param array $optionData * @param array $optionValueData @@ -137,33 +61,14 @@ public function testRenderCustomOptionsFromSelectGroup( array $optionValueData, array $checkArray ): void { - $option = $this->addOptionToProduct($optionData, $optionValueData); - $optionValues = $option->getValues(); - $optionValue = reset($optionValues); - $optionHtml = $this->getOptionHtml(); - $this->baseOptionAsserts($option, $optionHtml, $checkArray); - - if (isset($checkArray['not_contain_arr'])) { - foreach ($checkArray['not_contain_arr'] as $notContainPattern) { - $this->assertNotRegExp($notContainPattern, $optionHtml); - } - } - - if (isset($checkArray['option_value_item'])) { - $checkArray['option_value_item'] = sprintf( - $checkArray['option_value_item'], - $optionValue->getOptionTypeId(), - $optionValueData[Value::KEY_TITLE] - ); - $this->assertRegExp($checkArray['option_value_item'], $optionHtml); - } + $this->assertSelectOptionRenderingOnProduct('simple', $optionData, $optionValueData, $checkArray); } /** * Check that options from date group(date, date & time, time) render as expected. * * @magentoDataFixture Magento/Catalog/_files/product_without_options_with_stock_data.php - * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\DateGroupDataProvider::getData() + * @dataProvider \Magento\TestFramework\Catalog\Block\Product\View\Options\DateGroupDataProvider::getData * * @param array $optionData * @param array $checkArray @@ -171,125 +76,17 @@ public function testRenderCustomOptionsFromSelectGroup( */ public function testRenderCustomOptionsFromDateGroup(array $optionData, array $checkArray): void { - $option = $this->addOptionToProduct($optionData); - $optionHtml = $this->getOptionHtml(); - $this->baseOptionAsserts($option, $optionHtml, $checkArray); - - switch ($optionData[Option::KEY_TYPE]) { - case ProductCustomOptionInterface::OPTION_TYPE_DATE: - $this->assertContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); - $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); - $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); - $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); - break; - case ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME: - $this->assertContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); - break; - case ProductCustomOptionInterface::OPTION_TYPE_TIME: - $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][month]\"", $optionHtml); - $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][day]\"", $optionHtml); - $this->assertNotContains("<select name=\"options[{$option->getOptionId()}][year]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][hour]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][minute]\"", $optionHtml); - $this->assertContains("<select name=\"options[{$option->getOptionId()}][day_part]\"", $optionHtml); - break; - } + $this->assertDateOptionRenderingOnProduct('simple', $optionData, $checkArray); } /** - * Base asserts for rendered options. - * - * @param ProductCustomOptionInterface $option - * @param string $optionHtml - * @param array $checkArray - * @return void - */ - private function baseOptionAsserts( - ProductCustomOptionInterface $option, - string $optionHtml, - array $checkArray - ): void { - $this->assertContains($checkArray['block_with_required_class'], $optionHtml); - $this->assertContains($checkArray['title'], $optionHtml); - - if (isset($checkArray['label_for_created_option'])) { - $checkArray['label_for_created_option'] = sprintf( - $checkArray['label_for_created_option'], - $option->getOptionId() - ); - $this->assertContains($checkArray['label_for_created_option'], $optionHtml); - } - - if (isset($checkArray['price'])) { - $this->assertContains($checkArray['price'], $optionHtml); - } - - if (isset($checkArray['required_element'])) { - $this->assertRegExp($checkArray['required_element'], $optionHtml); - } - } - - /** - * Add custom option to product with data. - * - * @param array $optionData - * @param array $optionValueData - * @return ProductCustomOptionInterface - */ - private function addOptionToProduct(array $optionData, array $optionValueData = []): ProductCustomOptionInterface - { - $product = $this->productRepository->get('simple'); - $optionData[Option::KEY_PRODUCT_SKU] = $product->getSku(); - - if (!empty($optionValueData)) { - $optionValueData = $this->productCustomOptionValuesFactory->create(['data' => $optionValueData]); - $optionData['values'] = [$optionValueData]; - } - - $option = $this->productCustomOptionFactory->create(['data' => $optionData]); - $product->setOptions([$option]); - $createdOptions = $this->productRepository->save($product)->getOptions(); - - return reset($createdOptions); - } - - /** - * Render custom options block. - * - * @return string - */ - private function getOptionHtml(): string - { - $product = $this->productRepository->get('simple'); - $optionsBlock = $this->getOptionsBlock(); - $optionsBlock->setProduct($product); - - return $optionsBlock->toHtml(); - } - - /** - * Get options block. - * - * @return Options + * @inheritdoc */ - private function getOptionsBlock(): Options + protected function getHandlesList(): array { - $this->page->addHandle([ + return [ 'default', 'catalog_product_view', - ]); - $this->page->getLayout()->generateXml(); - /** @var Template $productInfoFormOptionsBlock */ - $productInfoFormOptionsBlock = $this->page->getLayout()->getBlock('product.info.form.options'); - $optionsWrapperBlock = $productInfoFormOptionsBlock->getChildBlock('product_options_wrapper'); - - return $optionsWrapperBlock->getChildBlock('product_options'); + ]; } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/SingleStoreCurrencyTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/SingleStoreCurrencyTest.php new file mode 100644 index 0000000000000..284d85ccc9ebd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/SingleStoreCurrencyTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Block\Product\View; + +/** + * Checks currency displaying and converting on the catalog pages + * + * @magentoAppArea frontend + */ +class SingleStoreCurrencyTest extends AbstractCurrencyTest +{ + /** + * @magentoConfigFixture current_store currency/options/base USD + * @magentoConfigFixture current_store currency/options/default CNY + * @magentoConfigFixture current_store currency/options/allow CNY,USD + * + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @magentoDataFixture Magento/Directory/_files/usd_cny_rate.php + * + * @return void + */ + public function testRenderPrice(): void + { + $priceHtml = $this->processPriceView('simple2'); + $this->assertEquals('CN¥70.00', $priceHtml); + } + + /** + * @magentoConfigFixture current_store currency/options/base USD + * @magentoConfigFixture current_store currency/options/default CNY + * @magentoConfigFixture current_store currency/options/allow EUR,CNY + * + * @magentoDataFixture Magento/Catalog/_files/product_special_price.php + * @magentoDataFixture Magento/Directory/_files/usd_cny_rate.php + * + * @return void + */ + public function testRenderSpecialPrice(): void + { + $priceHtml = $this->processPriceView('simple'); + $this->assertEquals('Special Price CN¥41.93 Regular Price CN¥70.00', $priceHtml); + } + + /** + * @magentoConfigFixture current_store currency/options/base USD + * @magentoConfigFixture current_store currency/options/default CNY + * @magentoConfigFixture current_store currency/options/allow CNY,USD + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_fixed_tier_price.php + * @magentoDataFixture Magento/Directory/_files/usd_cny_rate.php + * + * @return void + */ + public function testRenderTierPrice(): void + { + $priceHtml = $this->processPriceView('simple-product-tax-none', self::TIER_PRICE_BLOCK_NAME); + $this->assertEquals('Buy 2 for CN¥280.00 each and save 80%', $priceHtml); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Delete/DeleteCategoryWithEnabledFlatTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Delete/DeleteCategoryWithEnabledFlatTest.php new file mode 100644 index 0000000000000..357b7247412d9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Delete/DeleteCategoryWithEnabledFlatTest.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Category\Delete; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\Indexer\Category\Flat\State; +use Magento\Catalog\Model\ResourceModel\Category\Flat as CategoryFlatResource; +use Magento\Catalog\Model\ResourceModel\Category\Flat\CollectionFactory; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Test cases related to delete category with enabled category flat. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + */ +class DeleteCategoryWithEnabledFlatTest extends AbstractBackendController +{ + /** + * @var IndexerRegistry + */ + private $indexerRegistry; + + /** + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + + /** + * @var CategoryFlatResource + */ + private $categoryFlatResource; + + /** + * @var CollectionFactory + */ + private $categoryFlatCollectionFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->indexerRegistry = $this->_objectManager->get(IndexerRegistry::class); + $this->categoryRepository = $this->_objectManager->get(CategoryRepositoryInterface::class); + $this->categoryFlatResource = $this->_objectManager->get(CategoryFlatResource::class); + $this->categoryFlatCollectionFactory = $this->_objectManager->get(CollectionFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + $categoryFlatIndexer = $this->indexerRegistry->get(State::INDEXER_ID); + $categoryFlatIndexer->invalidate(); + $this->categoryFlatResource->getConnection()->dropTable($this->categoryFlatResource->getMainTable()); + } + + /** + * Check that product is deleted from flat table. + * + * @magentoConfigFixture current_store catalog/frontend/flat_catalog_category true + * + * @magentoDataFixture Magento/Catalog/_files/category.php + * @magentoDataFixture Magento/Catalog/_files/reindex_catalog_category_flat.php + * + * @return void + */ + public function testDeleteCategory(): void + { + $this->assertEquals(1, $this->getFlatCategoryCollectionSizeByCategoryId(333)); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue(['id' => 333]); + $this->dispatch('backend/catalog/category/delete'); + $this->assertSessionMessages($this->equalTo([(string)__('You deleted the category.')])); + $this->assertEquals(0, $this->getFlatCategoryCollectionSizeByCategoryId(333)); + $this->checkCategoryIsDeleted(333); + } + + /** + * Return collection size from category flat collection by category ID. + * + * @param int $categoryId + * @return int + */ + private function getFlatCategoryCollectionSizeByCategoryId(int $categoryId): int + { + $categoryFlatCollection = $this->categoryFlatCollectionFactory->create(); + $categoryFlatCollection->addIdFilter($categoryId); + + return $categoryFlatCollection->getSize(); + } + + /** + * Assert that category is deleted. + * + * @param int $categoryId + */ + private function checkCategoryIsDeleted(int $categoryId): void + { + $this->expectExceptionObject(new NoSuchEntityException(__("No such entity with id = {$categoryId}"))); + $this->categoryRepository->get($categoryId); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/AbstractSaveCategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/AbstractSaveCategoryTest.php new file mode 100644 index 0000000000000..e472220896af9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/AbstractSaveCategoryTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Category\Save; + +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Abstract save category. + */ +class AbstractSaveCategoryTest extends AbstractBackendController +{ + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->serializer = $this->_objectManager->get(SerializerInterface::class); + } + + /** + * Perform save category request with category POST data. + * + * @param array $data + * @return array + */ + protected function performSaveCategoryRequest(array $data): array + { + $data['return_session_messages_only'] = true; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue($data); + $this->dispatch('backend/catalog/category/save'); + + return $this->serializer->unserialize($this->getResponse()->getBody()); + } + + /** + * Assert that session has message about successfully category save. + * + * @param array $responseData + * @return void + */ + protected function assertRequestIsSuccessfullyPerformed(array $responseData): void + { + $this->assertTrue(isset($responseData['category']['entity_id'])); + $this->assertFalse($responseData['error'], 'Response message: ' . $responseData['messages']); + $message = str_replace('.', '\.', (string)__('You saved the category.')); + $this->assertRegExp("/>{$message}</", $responseData['messages']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryWithEnabledFlatTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryWithEnabledFlatTest.php new file mode 100644 index 0000000000000..376e9865b4fb8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/SaveCategoryWithEnabledFlatTest.php @@ -0,0 +1,268 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Category\Save; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Indexer\Category\Flat; +use Magento\Catalog\Model\Indexer\Category\Flat\State; +use Magento\Catalog\Model\ResourceModel\Category\Flat as CategoryFlatResource; +use Magento\Catalog\Model\ResourceModel\Category\Flat\CollectionFactory; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\UrlRewrite\Model\ResourceModel\UrlRewrite as UrlRewriteResource; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; + +/** + * Test cases related to save category with enabled category flat. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + */ +class SaveCategoryWithEnabledFlatTest extends AbstractSaveCategoryTest +{ + /** + * @var IndexerRegistry + */ + private $indexerRegistry; + + /** + * @var UrlRewriteResource + */ + private $urlRewriteResource; + + /** + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + + /** + * @var Flat + */ + private $categoryFlatIndexer; + + /** + * @var CategoryFlatResource + */ + private $categoryFlatResource; + + /** + * @var CollectionFactory + */ + private $categoryFlatCollectionFactory; + + /** + * @var string + */ + private $createdCategoryId; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->indexerRegistry = $this->_objectManager->get(IndexerRegistry::class); + $this->urlRewriteResource = $this->_objectManager->get(UrlRewriteResource::class); + $this->categoryRepository = $this->_objectManager->get(CategoryRepositoryInterface::class); + $this->categoryFlatIndexer = $this->_objectManager->get(Flat::class); + $this->categoryFlatResource = $this->_objectManager->get(CategoryFlatResource::class); + $this->categoryFlatCollectionFactory = $this->_objectManager->get(CollectionFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + $categoryFlatIndexer = $this->indexerRegistry->get(State::INDEXER_ID); + $categoryFlatIndexer->invalidate(); + $this->categoryFlatResource->getConnection()->dropTable($this->categoryFlatResource->getMainTable()); + $this->deleteAllCategoryUrlRewrites(); + try { + $this->categoryRepository->deleteByIdentifier($this->createdCategoryId); + } catch (NoSuchEntityException $e) { + //Category already deleted. + } + $this->createdCategoryId = null; + } + + /** + * Assert that category flat table is created and flat table contain category with created child category. + * + * @magentoDataFixture Magento/Catalog/_files/category.php + * + * @magentoConfigFixture current_store catalog/frontend/flat_catalog_category true + * + * @return void + */ + public function testAddChildCategory(): void + { + $parentCategory = $this->categoryRepository->get(333); + $postData = [ + 'name' => 'Custom category name', + 'parent' => 333, + 'is_active' => 1, + 'include_in_menu' => 1, + 'display_mode' => 'PRODUCTS', + 'is_anchor' => true, + 'use_config' => [ + 'available_sort_by' => 1, + 'default_sort_by' => 1, + 'filter_price_range' => 1, + ], + ]; + $responseData = $this->performSaveCategoryRequest($postData); + $this->assertRequestIsSuccessfullyPerformed($responseData); + $this->createdCategoryId = $responseData['category']['entity_id']; + $this->categoryFlatIndexer->executeFull(); + $this->assertTrue( + $this->categoryFlatResource->getConnection()->isTableExists($this->categoryFlatResource->getMainTable()) + ); + $this->assertEquals(1, $parentCategory->getChildrenCategories()->getSize()); + $categoryFlatCollection = $this->categoryFlatCollectionFactory->create(); + $categoryFlatCollection->addIdFilter([333, $this->createdCategoryId]); + $this->assertCount(2, $categoryFlatCollection->getItems()); + /** @var Category $createdCategory */ + $createdCategory = $categoryFlatCollection->getItemByColumnValue('entity_id', $this->createdCategoryId); + $this->assertEquals($parentCategory->getPath() . '/' . $this->createdCategoryId, $createdCategory->getPath()); + $this->assertEquals($parentCategory->getEntityId(), $createdCategory->getParentId()); + $this->assertEquals($parentCategory->getLevel() + 1, $createdCategory->getLevel()); + } + + /** + * Assert that category flat table is created and flat table contains category with expected data. + * + * @dataProvider enableCategoryDataProvider + * + * @magentoConfigFixture current_store catalog/frontend/flat_catalog_category true + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testSaveCategoryWithData(array $postData, array $expectedData): void + { + $responseData = $this->performSaveCategoryRequest($postData); + $this->assertRequestIsSuccessfullyPerformed($responseData); + $this->createdCategoryId = $responseData['category']['entity_id']; + $this->categoryFlatIndexer->executeFull(); + $this->assertTrue( + $this->categoryFlatResource->getConnection()->isTableExists($this->categoryFlatResource->getMainTable()) + ); + $categoryFlatCollection = $this->categoryFlatCollectionFactory->create(); + $categoryFlatCollection->addAttributeToSelect(array_keys($expectedData)); + $categoryFlatCollection->addIdFilter($this->createdCategoryId); + $this->assertCount(1, $categoryFlatCollection->getItems()); + /** @var Category $createdCategory */ + $createdCategory = $categoryFlatCollection->getFirstItem(); + foreach ($expectedData as $fieldName => $value) { + $this->assertEquals($value, $createdCategory->getDataByKey($fieldName)); + } + } + + /** + * Data provider with create category POST data. + * + * @return array + */ + public function enableCategoryDataProvider(): array + { + return [ + 'category_is_enabled' => [ + [ + 'name' => 'Custom category name', + 'parent' => 2, + 'is_active' => 1, + 'include_in_menu' => 1, + 'display_mode' => 'PRODUCTS', + 'is_anchor' => true, + 'use_config' => [ + 'available_sort_by' => 1, + 'default_sort_by' => 1, + 'filter_price_range' => 1, + ], + ], + [ + 'is_active' => '1', + ], + ], + 'category_is_disabled' => [ + [ + 'name' => 'Custom category name', + 'parent' => 2, + 'is_active' => 0, + 'include_in_menu' => 1, + 'display_mode' => 'PRODUCTS', + 'is_anchor' => true, + 'use_config' => [ + 'available_sort_by' => 1, + 'default_sort_by' => 1, + 'filter_price_range' => 1, + ], + ], + [ + 'is_active' => '0' + ] + ], + 'include_in_menu_is_enabled' => [ + [ + 'name' => 'Custom category name', + 'parent' => 2, + 'is_active' => 1, + 'include_in_menu' => 1, + 'display_mode' => 'PRODUCTS', + 'is_anchor' => true, + 'use_config' => [ + 'available_sort_by' => 1, + 'default_sort_by' => 1, + 'filter_price_range' => 1, + ], + ], + [ + 'include_in_menu' => '1', + ], + ], + 'include_in_menu_is_disabled' => [ + [ + 'name' => 'Custom category name', + 'parent' => 2, + 'is_active' => 1, + 'include_in_menu' => 0, + 'display_mode' => 'PRODUCTS', + 'is_anchor' => true, + 'use_config' => [ + 'available_sort_by' => 1, + 'default_sort_by' => 1, + 'filter_price_range' => 1, + ], + ], + [ + 'include_in_menu' => '0', + ], + ], + ]; + } + + /** + * Delete all URL rewrite with entity type equal to "category". + * + * @return void + */ + private function deleteAllCategoryUrlRewrites(): void + { + $deleteCondition = $this->urlRewriteResource->getConnection() + ->quoteInto(UrlRewrite::ENTITY_TYPE . ' = ?', DataCategoryUrlRewriteDatabaseMap::ENTITY_TYPE); + $this->urlRewriteResource->getConnection()->delete( + $this->urlRewriteResource->getMainTable(), + $deleteCondition + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UrlRewriteTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UrlRewriteTest.php index 90f354d90f17a..e9354d7116ae6 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UrlRewriteTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Category/Save/UrlRewriteTest.php @@ -8,9 +8,6 @@ namespace Magento\Catalog\Controller\Adminhtml\Category\Save; use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; -use Magento\Framework\App\Request\Http as HttpRequest; -use Magento\Framework\Serialize\Serializer\Json; -use Magento\TestFramework\TestCase\AbstractBackendController; use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollectionFactory; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -20,23 +17,20 @@ * @magentoAppArea adminhtml * @magentoDbIsolation enabled */ -class UrlRewriteTest extends AbstractBackendController +class UrlRewriteTest extends AbstractSaveCategoryTest { - /** @var $urlRewriteCollectionFactory */ + /** + * @var UrlRewriteCollectionFactory + */ private $urlRewriteCollectionFactory; - /** @var Json */ - private $jsonSerializer; - /** - * @inheritDoc + * @inheritdoc */ protected function setUp() { parent::setUp(); - $this->urlRewriteCollectionFactory = $this->_objectManager->get(UrlRewriteCollectionFactory::class); - $this->jsonSerializer = $this->_objectManager->get(Json::class); } /** @@ -47,19 +41,14 @@ protected function setUp() */ public function testUrlRewrite(array $data): void { - $this->getRequest()->setMethod(HttpRequest::METHOD_POST); - $this->getRequest()->setPostValue($data); - $this->dispatch('backend/catalog/category/save'); - $categoryId = $this->jsonSerializer->unserialize($this->getResponse()->getBody())['category']['entity_id']; + $responseData = $this->performSaveCategoryRequest($data); + $this->assertRequestIsSuccessfullyPerformed($responseData); + $categoryId = $responseData['category']['entity_id']; $this->assertNotNull($categoryId, 'The category was not created'); $urlRewriteCollection = $this->urlRewriteCollectionFactory->create(); $urlRewriteCollection->addFieldToFilter(UrlRewrite::ENTITY_ID, ['eq' => $categoryId]) ->addFieldToFilter(UrlRewrite::ENTITY_TYPE, ['eq' => DataCategoryUrlRewriteDatabaseMap::ENTITY_TYPE]); - $this->assertCount( - 1, - $urlRewriteCollection->getItems(), - 'Wrong count of url rewrites was created' - ); + $this->assertEquals(1, $urlRewriteCollection->getSize(), 'Wrong count of url rewrites was created'); } /** @@ -77,7 +66,6 @@ public function categoryDataProvider(): array 'include_in_menu' => '1', 'display_mode' => 'PRODUCTS', 'is_anchor' => true, - 'return_session_messages_only' => true, 'use_config' => [ 'available_sort_by' => 1, 'default_sort_by' => 1, diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/AbstractSaveAttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/AbstractSaveAttributeTest.php similarity index 86% rename from dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/AbstractSaveAttributeTest.php rename to dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/AbstractSaveAttributeTest.php index d0f1256f1fdb7..91650d4b7444e 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/AbstractSaveAttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/AbstractSaveAttributeTest.php @@ -5,11 +5,10 @@ */ declare(strict_types=1); -namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType; +namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save; -use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Api\ProductAttributeOptionManagementInterface; -use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\App\Request\Http as HttpRequest; @@ -22,27 +21,21 @@ /** * Base create and assert attribute data. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ abstract class AbstractSaveAttributeTest extends AbstractBackendController { - /** - * @var AttributeRepositoryInterface - */ - protected $attributeRepository; + /** @var ProductAttributeRepositoryInterface */ + protected $productAttributeRepository; - /** - * @var Escaper - */ + /** @var Escaper */ protected $escaper; - /** - * @var Json - */ + /** @var Json */ protected $jsonSerializer; - /** - * @var ProductAttributeOptionManagementInterface - */ + /** @var ProductAttributeOptionManagementInterface */ protected $productAttributeOptionManagement; /** @@ -51,12 +44,12 @@ abstract class AbstractSaveAttributeTest extends AbstractBackendController protected function setUp() { parent::setUp(); - $this->attributeRepository = $this->_objectManager->get(AttributeRepositoryInterface::class); $this->escaper = $this->_objectManager->get(Escaper::class); $this->jsonSerializer = $this->_objectManager->get(Json::class); $this->productAttributeOptionManagement = $this->_objectManager->get( ProductAttributeOptionManagementInterface::class ); + $this->productAttributeRepository = $this->_objectManager->get(ProductAttributeRepositoryInterface::class); } /** @@ -73,15 +66,15 @@ protected function createAttributeUsingDataAndAssert(array $attributeData, array if (isset($attributeData['serialized_options_arr'])) { $attributeData['serialized_options'] = $this->serializeOptions($attributeData['serialized_options_arr']); } - $this->createAttributeViaController($attributeData); + $this->dispatchAttributeSave($attributeData); $this->assertSessionMessages( $this->equalTo([(string)__('You saved the product attribute.')]), MessageInterface::TYPE_SUCCESS ); try { - $attribute = $this->attributeRepository->get(ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode); + $attribute = $this->productAttributeRepository->get($attributeCode); $this->assertAttributeData($attribute, $attributeData, $checkArray); - $this->attributeRepository->delete($attribute); + $this->productAttributeRepository->delete($attribute); } catch (NoSuchEntityException $e) { $this->fail("Attribute with code {$attributeCode} was not created."); } @@ -101,15 +94,15 @@ protected function createAttributeUsingDataWithErrorAndAssert(array $attributeDa ) { $attributeData['serialized_options'] = $this->serializeOptions($attributeData['serialized_options_arr']); } - $this->createAttributeViaController($attributeData); + $this->dispatchAttributeSave($attributeData); $this->assertSessionMessages( $this->equalTo([$this->escaper->escapeHtml($errorMessage)]), MessageInterface::TYPE_ERROR ); $attributeCode = $this->getAttributeCodeFromAttributeData($attributeData); try { - $attribute = $this->attributeRepository->get(ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode); - $this->attributeRepository->delete($attribute); + $attribute = $this->productAttributeRepository->get($attributeCode); + $this->productAttributeRepository->delete($attribute); } catch (NoSuchEntityException $e) { //Attribute already deleted. } @@ -191,7 +184,7 @@ private function getAttributeCodeFromAttributeData(array $attributeData): string * @param array $attributeData * @return void */ - private function createAttributeViaController(array $attributeData): void + private function dispatchAttributeSave(array $attributeData): void { $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($attributeData); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/PriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/DecimalTest.php similarity index 79% rename from dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/PriceTest.php rename to dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/DecimalTest.php index fb71f0a4d9d76..943f33b9c1800 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/DecimalTest.php @@ -7,17 +7,20 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; + /** * Test cases related to create attribute with input type price. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ -class PriceTest extends AbstractSaveAttributeTest +class DecimalTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\Price::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\Decimal::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -31,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\Price::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\Decimal::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/MediaImageTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/MediaImageTest.php index f8adac2872773..c6500e03fa327 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/MediaImageTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save/InputType/MediaImageTest.php @@ -7,17 +7,20 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; + /** * Test cases related to create attribute with input type media image. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class MediaImageTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\MediaImage::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\MediaImage::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -31,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\MediaImage::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\MediaImage::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/AbstractUpdateAttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/AbstractUpdateAttributeTest.php new file mode 100644 index 0000000000000..60702d83bf4f7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/AbstractUpdateAttributeTest.php @@ -0,0 +1,472 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update; + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Attribute as AttributeResource; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option as OptionResource; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory as OptionCollectionFactory; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Eav\Model\GetAttributeGroupByName; +use Magento\TestFramework\Eav\Model\GetAttributeSetByName; +use Magento\TestFramework\Eav\Model\ResourceModel\GetEntityIdByAttributeId; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Base update and assert attribute data. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +abstract class AbstractUpdateAttributeTest extends AbstractBackendController +{ + /** @var ProductAttributeRepositoryInterface */ + protected $productAttributeRepository; + + /** @var Escaper */ + protected $escaper; + + /** @var Json */ + protected $jsonSerializer; + + /** @var GetAttributeSetByName */ + private $getAttributeSetByName; + + /** @var GetAttributeGroupByName */ + private $getAttributeGroupByName; + + /** @var GetEntityIdByAttributeId */ + private $getEntityIdByAttributeId; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** @var OptionCollectionFactory */ + private $optionCollectionFactory; + + /** @var OptionResource */ + private $attributeOptionResource; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->escaper = $this->_objectManager->get(Escaper::class); + $this->jsonSerializer = $this->_objectManager->get(Json::class); + $this->productAttributeRepository = $this->_objectManager->get(ProductAttributeRepositoryInterface::class); + $this->getAttributeSetByName = $this->_objectManager->get(GetAttributeSetByName::class); + $this->getAttributeGroupByName = $this->_objectManager->get(GetAttributeGroupByName::class); + $this->getEntityIdByAttributeId = $this->_objectManager->get(GetEntityIdByAttributeId::class); + $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class); + $this->optionCollectionFactory = $this->_objectManager->get(OptionCollectionFactory::class); + $this->attributeOptionResource = $this->_objectManager->get(OptionResource::class); + } + + /** + * Updates attribute frontend labels on stores for a given attribute type. + * + * @param string $attributeCode + * @param array $postData + * @param array $expectedData + * @return void + */ + protected function processUpdateFrontendLabelOnStores( + string $attributeCode, + array $postData, + array $expectedData + ): void { + $this->setAttributeStorelabels($attributeCode); + if (is_array($postData['frontend_label'])) { + $postData['frontend_label'] = $this->prepareStoresData($postData['frontend_label']); + } + $expectedData['store_labels'] = $this->prepareStoresData($expectedData['store_labels']); + + $this->_objectManager->removeSharedInstance(AttributeResource::class); + $this->updateAttributeUsingData($attributeCode, $postData); + $this->assertUpdateAttributeProcess($attributeCode, $postData, $expectedData); + } + + /** + * Updates attribute options on stores for a given attribute type. + * + * @param string $attributeCode + * @param array $postData + * @return void + */ + protected function processUpdateOptionsOnStores(string $attributeCode, array $postData): void + { + $optionsData = $this->prepareStoreOptionsArray($attributeCode, $postData['options_array']); + $optionsPostData = $this->prepareStoreOptionsPostData($optionsData); + $postData['serialized_options'] = $this->serializeOptions($optionsPostData); + $expectedData = $this->prepareStoreOptionsExpectedData($optionsData); + + $this->_objectManager->removeSharedInstance(AttributeResource::class); + $this->updateAttributeUsingData($attributeCode, $postData); + $this->assertUpdateAttributeProcess($attributeCode, $postData, $expectedData); + } + + /** + * Prepare an array of values by store - replace store code with store identifier. + * + * @param array $storesData + * @return array + */ + protected function prepareStoresData(array $storesData): array + { + $storeIdsData = []; + foreach ($storesData as $storeId => $label) { + $store = $this->storeManager->getStore($storeId); + $storeIdsData[$store->getId()] = $label; + } + + return $storeIdsData; + } + + /** + * Update attribute via save product attribute controller. + * + * @param string $attributeCode + * @param array $postData + * @return void + */ + protected function updateAttributeUsingData(string $attributeCode, array $postData): void + { + $attributeId = $postData['attribute_id'] ?? $this->productAttributeRepository->get($attributeCode)->getId(); + $this->dispatchAttributeSave($postData, (int)$attributeId); + } + + /** + * Replace the store code with an identifier in the array of option values + * + * @param array $optionsArray + * @return array + */ + protected function replaceStoreCodeWithId(array $optionsArray): array + { + foreach ($optionsArray as $key => $option) { + $optionsArray[$key]['value'] = $this->prepareStoresData($option['value']); + } + + return $optionsArray; + } + + /** + * Prepare an array of attribute option values that will be saved. + * + * @param string $attributeCode + * @param array $optionsArray + * @return array + */ + protected function prepareStoreOptionsArray(string $attributeCode, array $optionsArray): array + { + $attribute = $this->productAttributeRepository->get($attributeCode); + $replacedOptionsArray = $this->replaceStoreCodeWithId($optionsArray); + $actualOptionsData = $this->getActualOptionsData($attribute->getId()); + $labeledOptionsData = []; + $optionLabelIds = []; + $i = 1; + foreach ($actualOptionsData as $optionId => $optionData) { + $optionLabelIds['option_' . $i] = $optionId; + $labeledOptionsData['option_' . $i] = $optionData; + $i++; + } + + $combineOptionsData = array_replace_recursive($labeledOptionsData, $replacedOptionsArray); + $optionsData = []; + foreach ($optionLabelIds as $optionLabel => $optionId) { + $optionsData[$optionId] = $combineOptionsData[$optionLabel]; + } + + return $optionsData; + } + + /** + * Get actual attribute options data. + * + * @param string $attributeId + * @return array + */ + protected function getActualOptionsData(string $attributeId): array + { + $attributeOptions = $this->getAttributeOptions($attributeId); + $actualOptionsData = []; + foreach ($attributeOptions as $optionId => $option) { + $actualOptionsData[$optionId] = [ + 'order' => $option->getSortOrder(), + 'value' => $this->getAttributeOptionValues($optionId), + ]; + } + + return $actualOptionsData; + } + + /** + * Prepare an array of attribute option values for sending via post parameters. + * + * @param array $optionsData + * @return array + */ + protected function prepareStoreOptionsPostData(array $optionsData): array + { + $optionsPostData = []; + foreach ($optionsData as $optionId => $option) { + $optionsPostData[$optionId]['option'] = [ + 'order' => [ + $optionId => $option['order'], + ], + 'value' => [ + $optionId => $option['value'], + ], + 'delete' => [ + $optionId => $option['delete'] ?? '', + ], + ]; + if (isset($option['default'])) { + $optionsPostData[$optionId]['default'][] = $optionId; + } + } + + return $optionsPostData; + } + + /** + * Prepare an array of attribute option values for verification after saving the attribute. + * + * @param array $optionsData + * @return array + */ + protected function prepareStoreOptionsExpectedData(array $optionsData): array + { + $optionsArray = []; + $defaultValue = ''; + + foreach ($optionsData as $optionId => $option) { + if (!empty($option['delete'])) { + continue; + } + $optionsArray[$optionId] = [ + 'order' => $option['order'], + 'value' => $option['value'], + ]; + if (isset($option['default'])) { + $defaultValue = $optionId; + } + } + + return [ + 'options_array' => $optionsArray, + 'default_value' => $defaultValue, + ]; + } + + /** + * Assert that attribute update correctly. + * + * @param string $attributeCode + * @param array $postData + * @param array $expectedData + * @return void + */ + protected function assertUpdateAttributeProcess(string $attributeCode, array $postData, array $expectedData): void + { + $this->assertSessionMessages( + $this->equalTo([(string)__('You saved the product attribute.')]), + MessageInterface::TYPE_SUCCESS + ); + $updatedAttribute = $this->productAttributeRepository->get($attributeCode); + if (isset($postData['new_attribute_set_name'])) { + $this->assertUpdateAttributeSet($updatedAttribute, $postData); + } elseif (isset($postData['options_array'])) { + $this->assertUpdateAttributeOptions($updatedAttribute, $expectedData['options_array']); + unset($expectedData['options_array']); + $this->assertUpdateAttributeData($updatedAttribute, $expectedData); + } else { + $this->assertUpdateAttributeData($updatedAttribute, $expectedData); + } + } + + /** + * Check that attribute property values match expected values. + * + * @param ProductAttributeInterface $attribute + * @param array $expectedData + * @return void + */ + protected function assertUpdateAttributeData( + ProductAttributeInterface $attribute, + array $expectedData + ): void { + foreach ($expectedData as $key => $expectedValue) { + $this->assertEquals( + $expectedValue, + $attribute->getDataUsingMethod($key), + "Invalid expected value for $key field." + ); + } + } + + /** + * Checks that appropriate error message appears. + * + * @param string $errorMessage + * @return void + */ + protected function assertErrorSessionMessages(string $errorMessage): void + { + $this->assertSessionMessages( + $this->equalTo([$this->escaper->escapeHtml($errorMessage)]), + MessageInterface::TYPE_ERROR + ); + } + + /** + * Create or update attribute using catalog/product_attribute/save action. + * + * @param array $attributeData + * @param int|null $attributeId + * @return void + */ + private function dispatchAttributeSave(array $attributeData, ?int $attributeId = null): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue($attributeData); + if ($attributeId) { + $this->getRequest()->setParam('attribute_id', $attributeId); + } + $this->dispatch('backend/catalog/product_attribute/save'); + } + + /** + * Create serialized options string. + * + * @param array $optionsArr + * @return string + */ + private function serializeOptions(array $optionsArr): string + { + $resultArr = []; + + foreach ($optionsArr as $option) { + $resultArr[] = http_build_query($option); + } + + return $this->jsonSerializer->serialize($resultArr); + } + + /** + * Set default values of attribute store labels and save. + * + * @param string $attributeCode + * @return void + */ + private function setAttributeStoreLabels(string $attributeCode): void + { + $stores = $this->storeManager->getStores(); + $storeLabels = []; + foreach ($stores as $storeId => $store) { + $storeLabels[$storeId] = $store->getName(); + } + $attribute = $this->productAttributeRepository->get($attributeCode); + $attribute->setStoreLabels($storeLabels); + $this->productAttributeRepository->save($attribute); + } + + /** + * Check that the attribute update was successful after adding it to the + * new attribute set and new attribute group. + * + * @param ProductAttributeInterface|Attribute $attribute + * @param array $postData + * @return void + */ + private function assertUpdateAttributeSet( + ProductAttributeInterface $attribute, + array $postData + ): void { + $attributeSet = $this->getAttributeSetByName->execute($postData['new_attribute_set_name']); + $this->assertNotNull( + $attributeSet, + 'The attribute set ' . $postData['new_attribute_set_name'] . 'was not created' + ); + + $attributeGroup = $this->getAttributeGroupByName->execute((int)$attributeSet->getId(), $postData['groupName']); + $this->assertNotNull( + $attributeGroup, + 'The attribute group ' . $postData['groupName'] . 'was not created' + ); + + $entityAttributeId = $this->getEntityIdByAttributeId->execute( + (int)$attributeSet->getId(), + (int)$attribute->getId(), + (int)$attributeGroup->getId() + ); + + $this->assertNotNull( + $entityAttributeId, + 'The attribute set and attribute group for the current attribute have not been updated.' + ); + } + + /** + * Check that attribute options are saved correctly. + * + * @param ProductAttributeInterface|Attribute $attribute + * @param array $expectedData + * @return void + */ + private function assertUpdateAttributeOptions( + ProductAttributeInterface $attribute, + array $expectedData + ): void { + $actualOptionsData = $this->getActualOptionsData($attribute->getId()); + + $this->assertEquals($expectedData, $actualOptionsData, 'Expected attribute options does not match.'); + } + + /** + * Get attribute options by attribute id and store id. + * + * @param string $attributeId + * @param int|null $storeId + * @return array + */ + private function getAttributeOptions(string $attributeId, ?int $storeId = null): array + { + $attributeOptionCollection = $this->optionCollectionFactory->create(); + $attributeOptionCollection->setAttributeFilter($attributeId); + $attributeOptionCollection->setStoreFilter($storeId); + + return $attributeOptionCollection->getItems(); + } + + /** + * Get attribute option values by option id. + * + * @param int $optionId + * @return array + */ + private function getAttributeOptionValues(int $optionId): array + { + $connection = $this->attributeOptionResource->getConnection(); + $select = $connection->select() + ->from( + ['main_table' => $this->attributeOptionResource->getTable('eav_attribute_option_value')], + ['store_id','value'] + ) + ->where('main_table.option_id = ?', $optionId); + + return $connection->fetchPairs($select); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/InputType/DecimalTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/InputType/DecimalTest.php new file mode 100644 index 0000000000000..0febc033592a6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/InputType/DecimalTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type price. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class DecimalTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\Decimal::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/product_decimal_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('decimal_attribute', $postData); + $this->assertUpdateAttributeProcess('decimal_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\Decimal::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_decimal_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('decimal_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\Decimal::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_decimal_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('decimal_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/InputType/MediaImageTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/InputType/MediaImageTest.php new file mode 100644 index 0000000000000..806e690dfd5b7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Update/InputType/MediaImageTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type media image. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class MediaImageTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\MediaImage::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/product_image_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('image_attribute', $postData); + $this->assertUpdateAttributeProcess('image_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\MediaImage::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_image_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('image_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Catalog\Model\Product\Attribute\DataProvider\MediaImage::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_image_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('image_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Save/CategoryIndexTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Save/CategoryIndexTest.php new file mode 100644 index 0000000000000..4d44afe831029 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Save/CategoryIndexTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Controller\Adminhtml\Product\Save; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Helper\DefaultCategory; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\App\Request\Http; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Message\MessageInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Checks category product index in cases when category unassigned from product + * + * @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php + * @magentoAppArea adminhtml + * @magentoDbIsolation disabled + */ +class CategoryIndexTest extends AbstractBackendController +{ + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var ProductInterface */ + private $product; + + /** @var TableMaintainer */ + private $tableMaintainer; + + /** @var ProductResource */ + private $productResource; + + /** @var AdapterInterface */ + private $connection; + + /** @var DefaultCategory */ + private $defaultCategoryHelper; + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->product = $this->productRepository->get('product_with_category'); + $this->tableMaintainer = $this->_objectManager->create(TableMaintainer::class); + $this->productResource = $this->_objectManager->get(ProductResource::class); + $this->connection = $this->productResource->getConnection(); + $this->defaultCategoryHelper = $this->_objectManager->get(DefaultCategory::class); + } + + /** + * @return void + */ + public function testUnassignCategory(): void + { + $postData = $this->preparePostData(); + $this->dispatchSaveProductRequest($postData); + $this->assertEmpty($this->fetchIndexData()); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php + * @magentoDataFixture Magento/Catalog/_files/category.php + * + * @return void + */ + public function testReassignCategory(): void + { + $postData = $this->preparePostData(333); + $this->dispatchSaveProductRequest($postData); + $result = $this->fetchIndexData(); + $this->assertNotEmpty($result); + $this->assertEquals(333, reset($result)['category_id']); + } + + /** + * Perform request + * + * @param array $postData + * @return void + */ + private function dispatchSaveProductRequest(array $postData): void + { + $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod(Http::METHOD_POST); + $this->dispatch('backend/catalog/product/save/id/' . $this->product->getEntityId()); + $this->assertSessionMessages($this->equalTo(['You saved the product.']), MessageInterface::TYPE_SUCCESS); + } + + /** + * Prepare data to request + * + * @param int|null $newCategoryId + * @return array + */ + private function preparePostData(?int $newCategoryId = null): array + { + $this->product->getWebsiteIds(); + $data = $this->product->getData(); + unset($data['entity_id'], $data['category_ids']); + if ($newCategoryId) { + $data['category_ids'] = [$newCategoryId]; + } + + return ['product' => $data]; + } + + /** + * Fetch data from category product index table + * + * @return array + */ + private function fetchIndexData(): array + { + $tableName = $this->tableMaintainer->getMainTable(Store::DISTRO_STORE_ID); + $select = $this->connection->select(); + $select->from(['index_table' => $tableName], 'index_table.category_id') + ->where('index_table.product_id = ?', $this->product->getId()) + ->where('index_table.category_id != ?', $this->defaultCategoryHelper->getId()); + + return $this->connection->fetchAll($select); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php index 0c3e81fd52e81..c303fb1fe6e0c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php @@ -96,22 +96,6 @@ public function testAddActionForDisabledProduct(): void $this->_assertCompareListEquals([]); } - /** - * Test comparing a product. - * - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - public function testIndexActionAddProducts() - { - $this->_requireVisitorWithNoProducts(); - $product = $this->productRepository->get('simple_product_2'); - $this->dispatch('catalog/product_compare/index/items/' . $product->getEntityId()); - - $this->assertRedirect($this->stringStartsWith('http://localhost/index.php/catalog/product_compare/index/')); - - $this->_assertCompareListEquals([$product->getEntityId()]); - } - /** * Test removing a product from compare list. * diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompareTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompareTest.php index f99344904f68e..6615815569fcf 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompareTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Helper/Product/CompareTest.php @@ -23,24 +23,11 @@ protected function setUp() $this->_helper = $this->_objectManager->get(\Magento\Catalog\Helper\Product\Compare::class); } - /** - * @magentoDataFixture Magento/Catalog/_files/multiple_products.php - * @magentoDbIsolation disabled - */ public function testGetListUrl() { /** @var $empty \Magento\Catalog\Helper\Product\Compare */ $empty = $this->_objectManager->create(\Magento\Catalog\Helper\Product\Compare::class); $this->assertContains('/catalog/product_compare/index/', $empty->getListUrl()); - - $this->_populateCompareList(); - $productRepository = $this->_objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $id1 = $productRepository->get('simple1')->getId(); - $id2 = $productRepository->get('simple2')->getId(); - $this->assertRegExp( - '#/catalog/product_compare/index/items/(?:' . $id1 . '%2C' . $id2 . '|' . $id2 . '%2C' . $id1. ')/#', - $this->_helper->getListUrl() - ); } public function testGetAddUrl() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryLinkManagementTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryLinkManagementTest.php new file mode 100644 index 0000000000000..50d0ddbf5dccf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryLinkManagementTest.php @@ -0,0 +1,227 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model; + +use Magento\Catalog\Api\CategoryLinkManagementInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Category as CategoryResourceModel; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollectionFactory; +use PHPUnit\Framework\TestCase; + +/** + * Test cases related to assign/unassign product to/from category. + */ +class CategoryLinkManagementTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + + /** + * @var StoreRepositoryInterface + */ + private $storeRepository; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var CategoryResourceModel + */ + private $categoryResourceModel; + + /** + * @var CategoryLinkManagementInterface + */ + private $categoryLinkManagement; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->tableMaintainer = $this->objectManager->get(TableMaintainer::class); + $this->storeRepository = $this->objectManager->get(StoreRepositoryInterface::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->categoryResourceModel = $this->objectManager->get(CategoryResourceModel::class); + $this->categoryLinkManagement = $this->objectManager->create(CategoryLinkManagementInterface::class); + $this->productRepository->cleanCache(); + parent::setUp(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->objectManager->removeSharedInstance(CategoryLinkRepository::class); + $this->objectManager->removeSharedInstance(CategoryRepository::class); + parent::tearDown(); + } + + /** + * Assert that product correctly assigned to category and index table contain indexed data. + * + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @magentoDataFixture Magento/Catalog/_files/category.php + * + * @magentoDbIsolation disabled + * + * @return void + */ + public function testAssignProductToCategory(): void + { + $product = $this->productRepository->get('simple2'); + $this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333])); + $this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333])); + $this->categoryLinkManagement->assignProductToCategories('simple2', [333]); + $this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333])); + $this->assertEquals(1, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333])); + } + + /** + * Assert that product correctly unassigned from category and index table not contain indexed data. + * + * @magentoDataFixture Magento/Catalog/_files/product_with_category.php + * + * @magentoDbIsolation disabled + * + * @return void + */ + public function testUnassignProductFromCategory(): void + { + $product = $this->productRepository->get('in-stock-product'); + $this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333])); + $this->assertEquals(1, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333])); + $this->categoryLinkManagement->assignProductToCategories('in-stock-product', []); + $this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [333])); + $this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [333])); + } + + /** + * Assert that product correctly assigned to category and index table contain index data. + * + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @magentoDataFixture Magento/Catalog/_files/categories_no_products.php + * + * @magentoDbIsolation disabled + * + * @return void + */ + public function testAssignProductToCategoryWhichHasParentCategories(): void + { + $product = $this->productRepository->get('simple2'); + $this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5])); + $this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5])); + $this->categoryLinkManagement->assignProductToCategories('simple2', [5]); + $this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5])); + $this->assertEquals(3, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5])); + } + + /** + * Assert that product correctly unassigned from category and index table doesn't contain index data. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_category_which_has_parent_category.php + * + * @magentoDbIsolation disabled + * + * @return void + */ + public function testUnassignProductFromCategoryWhichHasParentCategories(): void + { + $product = $this->productRepository->get('simple_with_child_category'); + $this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5])); + $this->assertEquals(3, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5])); + $this->categoryLinkManagement->assignProductToCategories('simple_with_child_category', []); + $this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5])); + $this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5])); + } + + /** + * Assert that product correctly reassigned to another category. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_category_which_has_parent_category.php + * + * @magentoDbIsolation disabled + * + * @return void + */ + public function testReassignProductToOtherCategory(): void + { + $product = $this->productRepository->get('simple_with_child_category'); + $this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5])); + $this->assertEquals(3, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5])); + $this->categoryLinkManagement->assignProductToCategories('simple_with_child_category', [6]); + $this->assertEquals(1, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [6])); + $this->assertEquals(1, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [6])); + $this->assertEquals(0, $this->getCategoryProductRelationRecordsCount((int)$product->getId(), [5])); + $this->assertEquals(0, $this->getCategoryProductIndexRecordsCount((int)$product->getId(), [3, 4, 5])); + } + + /** + * Return count of product which assigned to provided categories. + * + * @param int $productId + * @param int[] $categoryIds + * @return int + */ + private function getCategoryProductRelationRecordsCount(int $productId, array $categoryIds): int + { + $select = $this->categoryResourceModel->getConnection()->select(); + $select->from( + $this->categoryResourceModel->getCategoryProductTable(), + [ + 'row_count' => new \Zend_Db_Expr('COUNT(*)') + ] + ); + $select->where('product_id = ?', $productId); + $select->where('category_id IN (?)', $categoryIds); + + return (int)$this->categoryResourceModel->getConnection()->fetchOne($select); + } + + /** + * Return count of products which added to index table with all provided category ids. + * + * @param int $productId + * @param array $categoryIds + * @param string $storeCode + * @return int + */ + private function getCategoryProductIndexRecordsCount( + int $productId, + array $categoryIds, + string $storeCode = 'default' + ): int { + $storeId = (int)$this->storeRepository->get($storeCode)->getId(); + $select = $this->categoryResourceModel->getConnection()->select(); + $select->from( + $this->tableMaintainer->getMainTable($storeId), + [ + 'row_count' => new \Zend_Db_Expr('COUNT(*)') + ] + ); + $select->where('product_id = ?', $productId); + $select->where('category_id IN (?)', $categoryIds); + + return (int)$this->categoryResourceModel->getConnection()->fetchOne($select); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/CategoryIndexTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/CategoryIndexTest.php new file mode 100644 index 0000000000000..06f083781aa26 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/CategoryIndexTest.php @@ -0,0 +1,203 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Helper\DefaultCategory; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Category as CategoryResource; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Catalog\Model\GetCategoryByName; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Indexer\TestCase; + +/** + * Checks category products indexing + * + * @magentoAppArea adminhtml + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CategoryIndexTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** @var AdapterInterface */ + private $connection; + + /** @var TableMaintainer */ + private $tableMaintainer; + + /** @var ProductResource */ + private $productResource; + + /** @var CategoryRepositoryInterface */ + private $categoryRepository; + + /** @var CategoryResource */ + private $categoryResource; + + /** @var GetCategoryByName */ + private $getCategoryByName; + + /** @var DefaultCategory */ + private $defaultCategoryHelper; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productResource = $this->objectManager->get(ProductResource::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->connection = $this->productResource->getConnection(); + $this->tableMaintainer = $this->objectManager->get(TableMaintainer::class); + $this->categoryRepository = $this->objectManager->get(CategoryRepositoryInterface::class); + $this->categoryResource = $this->objectManager->get(CategoryResource::class); + $this->getCategoryByName = $this->objectManager->create(GetCategoryByName::class); + $this->defaultCategoryHelper = $this->objectManager->get(DefaultCategory::class); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_with_parent_anchor.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @dataProvider assignCategoriesDataProvider + * + * @param string $categoryName + * @param int $expectedItemsCount + * @return void + */ + public function testProductAssignCategory(string $categoryName, int $expectedItemsCount): void + { + $product = $this->productRepository->get('simple2'); + $category = $this->getCategoryByName->execute($categoryName); + $product->setCategoryIds(array_merge($product->getCategoryIds(), [$category->getId()])); + $this->productResource->save($product); + $result = $this->getIndexRecordsByProductId((int)$product->getId()); + $this->assertEquals($expectedItemsCount, $result); + } + + /** + * @return array + */ + public function assignCategoriesDataProvider(): array + { + return [ + 'assign_to_category' => [ + 'category_name' => 'Parent category', + 'expected_records_count' => 1, + ], + 'assign_to_category_with_parent_anchor_category' => [ + 'category_name' => 'Child category', + 'expected_records_count' => 2, + ], + ]; + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_with_parent_anchor.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @dataProvider assignProductsDataProvider + * + * @param string $categoryName + * @param int $expectedCount + * @return void + */ + public function testCategoryAssignProduct(string $categoryName, int $expectedCount): void + { + $product = $this->productRepository->get('simple2'); + $category = $this->getCategoryByName->execute($categoryName); + $data = ['posted_products' => [$product->getId() => 0]]; + $category->addData($data); + $this->categoryResource->save($category); + $result = $this->getIndexRecordsByProductId((int)$product->getId()); + $this->assertEquals($expectedCount, $result); + } + + /** + * @return array + */ + public function assignProductsDataProvider(): array + { + return [ + 'assign_product_to_category' => [ + 'category_name' => 'Parent category', + 'expected_records_count' => 1, + ], + 'assign_product_to_category_with_parent_anchor_category' => [ + 'category_name' => 'Child category', + 'expected_records_count' => 2, + ], + ]; + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php + * @magentoDataFixture Magento/Catalog/_files/category_with_parent_anchor.php + * + * @return void + */ + public function testCategoryMove(): void + { + $product = $this->productRepository->get('product_with_category'); + $category = $this->getCategoryByName->execute('Category with product'); + $newParentCategory = $this->getCategoryByName->execute('Parent category'); + $afterCategory = $this->getCategoryByName->execute('Child category'); + $category->move($newParentCategory->getId(), $afterCategory->getId()); + $result = $this->getIndexRecordsByProductId((int)$product->getId()); + $this->assertEquals(2, $result); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_product_assigned_to_website.php + * + * @return void + */ + public function testDeleteProduct(): void + { + $product = $this->productRepository->get('product_with_category'); + $this->productRepository->delete($product); + $result = $this->getIndexRecordsByProductId((int)$product->getId()); + $this->assertEmpty($result); + } + + /** + * Fetch data from category product index table + * + * @param int $productId + * @return int + */ + private function getIndexRecordsByProductId(int $productId): int + { + $tableName = $this->tableMaintainer->getMainTable((int)$this->storeManager->getStore()->getId()); + $select = $this->connection->select(); + $select->from(['index_table' => $tableName], new \Zend_Db_Expr('COUNT(*)')) + ->where('index_table.product_id = ?', $productId) + ->where('index_table.category_id != ?', $this->defaultCategoryHelper->getId()); + + return (int)$this->connection->fetchOne($select); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ProductFrontendAction/SynchronizerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ProductFrontendAction/SynchronizerTest.php new file mode 100644 index 0000000000000..3ea30005e9f6c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/ProductFrontendAction/SynchronizerTest.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Product\ProductFrontendAction; + +use Magento\Catalog\Model\ProductRepository; + +/** + * Test for \Magento\Catalog\Model\Product\ProductFrontendAction\Synchronizer. + */ +class SynchronizerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Synchronizer + */ + private $synchronizer; + + /** + * @var ProductRepository + */ + private $productRepository; + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + $this->synchronizer = $objectManager->get(Synchronizer::class); + $this->productRepository = $objectManager->get(ProductRepository::class); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testSyncActions(): void + { + $actionsType = 'recently_viewed_product'; + $product1 = $this->productRepository->get('simple'); + $product2 = $this->productRepository->get('simple2'); + $product1Id = $product1->getId(); + $product2Id = $product2->getId(); + $productsData = [ + $product1Id => [ + 'added_at' => '1576582660', + 'product_id' => $product1Id, + ], + $product2Id => [ + 'added_at' => '1576587153', + 'product_id' => $product2Id, + ], + ]; + + $this->synchronizer->syncActions($productsData, $actionsType); + + $synchronizedCollection = $this->synchronizer->getActionsByType($actionsType); + $synchronizedCollection->addFieldToFilter( + 'product_id', + [ + $product1Id, + $product2Id, + ] + ); + + foreach ($synchronizedCollection as $item) { + $this->assertArrayHasKey($item->getProductId(), $productsData); + $this->assertEquals($productsData[$item->getProductId()]['added_at'], $item->getAddedAt()); + } + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testSyncActionsWithoutActionsType(): void + { + $product1 = $this->productRepository->get('simple'); + $product2 = $this->productRepository->get('simple2'); + $product1Id = $product1->getId(); + $product2Id = $product2->getId(); + $productsData = [ + $product1Id => [ + 'id' => $product1Id, + 'name' => $product1->getName(), + 'type' => $product1->getTypeId(), + ], + $product2Id => [ + 'id' => $product2Id, + 'name' => $product2->getName(), + 'type' => $product2->getTypeId(), + ], + ]; + + $this->synchronizer->syncActions($productsData, ''); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/CombinationWithDifferentTypePricesTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/CombinationWithDifferentTypePricesTest.php new file mode 100644 index 0000000000000..6baaf4940f94f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/CombinationWithDifferentTypePricesTest.php @@ -0,0 +1,608 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Pricing\Render; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Api\Data\RuleInterface; +use Magento\CatalogRule\Api\Data\RuleInterfaceFactory; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\Customer\Model\Group; +use Magento\Customer\Model\Session; +use Magento\Framework\Registry; +use Magento\Framework\View\Result\Page; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Assertions related to check product price rendering with combination of different price types. + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CombinationWithDifferentTypePricesTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var Page + */ + private $page; + + /** + * @var Registry + */ + private $registry; + + /** + * @var IndexBuilder + */ + private $indexBuilder; + + /** + * @var Session + */ + private $customerSession; + + /** + * @var WebsiteRepositoryInterface + */ + private $websiteRepository; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var RuleInterfaceFactory + */ + private $catalogRuleFactory; + + /** + * @var CatalogRuleRepositoryInterface + */ + private $catalogRuleRepository; + + /** + * @var ProductTierPriceInterfaceFactory + */ + private $productTierPriceFactory; + + /** + * @var ProductTierPriceExtensionFactory + */ + private $productTierPriceExtensionFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->page = $this->objectManager->create(Page::class); + $this->registry = $this->objectManager->get(Registry::class); + $this->indexBuilder = $this->objectManager->get(IndexBuilder::class); + $this->customerSession = $this->objectManager->get(Session::class); + $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->catalogRuleFactory = $this->objectManager->get(RuleInterfaceFactory::class); + $this->catalogRuleRepository = $this->objectManager->get(CatalogRuleRepositoryInterface::class); + $this->productTierPriceFactory = $this->objectManager->get(ProductTierPriceInterfaceFactory::class); + $this->productTierPriceExtensionFactory = $this->objectManager->get(ProductTierPriceExtensionFactory::class); + $this->productRepository->cleanCache(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + $this->registry->unregister('product'); + } + + /** + * Assert that product price rendered with expected special and regular prices if + * product has special price which lower than regular and tier prices. + * + * @magentoDataFixture Magento/Catalog/_files/product_special_price.php + * + * @dataProvider tierPricesForAllCustomerGroupsDataProvider + * + * @param float $specialPrice + * @param float $regularPrice + * @param array $tierPrices + * @param array|null $tierMessageConfig + * @return void + */ + public function testRenderSpecialPriceInCombinationWithTierPrice( + float $specialPrice, + float $regularPrice, + array $tierPrices, + ?array $tierMessageConfig + ): void { + $this->assertRenderedPrices($specialPrice, $regularPrice, $tierPrices, $tierMessageConfig); + } + + /** + * Data provider with tier prices which are for all customers groups. + * + * @return array + */ + public function tierPricesForAllCustomerGroupsDataProvider(): array + { + return [ + 'fixed_tier_price_with_qty_1' => [ + 5.99, + 10, + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 1, 'value' => 9], + ], + null + ], + 'fixed_tier_price_with_qty_2' => [ + 5.99, + 10, + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 2, 'value' => 5], + ], + ['qty' => 2, 'price' => 5.00, 'percent' => 17], + ], + 'percent_tier_price_with_qty_2' => [ + 5.99, + 10, + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 2, 'percent_value' => 70], + ], + ['qty' => 2, 'price' => 3.00, 'percent' => 70], + ], + 'fixed_tier_price_with_qty_1_is_lower_than_special' => [ + 5, + 10, + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 1, 'value' => 5], + ], + null + ], + 'percent_tier_price_with_qty_1_is_lower_than_special' => [ + 3, + 10, + [ + ['customer_group_id' => Group::NOT_LOGGED_IN_ID, 'qty' => 1, 'percent_value' => 70], + ], + null + ], + ]; + } + + /** + * Assert that product price rendered with expected special and regular prices if + * product has special price which lower than regular and tier prices and customer is logged. + * + * @magentoDataFixture Magento/Catalog/_files/product_special_price.php + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @magentoAppIsolation enabled + * + * @dataProvider tierPricesForLoggedCustomerGroupDataProvider + * + * @param float $specialPrice + * @param float $regularPrice + * @param array $tierPrices + * @param array|null $tierMessageConfig + * @return void + */ + public function testRenderSpecialPriceInCombinationWithTierPriceForLoggedInUser( + float $specialPrice, + float $regularPrice, + array $tierPrices, + ?array $tierMessageConfig + ): void { + try { + $this->customerSession->setCustomerId(1); + $this->assertRenderedPrices($specialPrice, $regularPrice, $tierPrices, $tierMessageConfig); + } finally { + $this->customerSession->setCustomerId(null); + } + } + + /** + * Data provider with tier prices which are for logged customers group. + * + * @return array + */ + public function tierPricesForLoggedCustomerGroupDataProvider(): array + { + return [ + 'fixed_tier_price_with_qty_1' => [ + 5.99, + 10, + [ + ['customer_group_id' => 1, 'qty' => 1, 'value' => 9], + ], + null + ], + 'percent_tier_price_with_qty_1' => [ + 5.99, + 10, + [ + ['customer_group_id' => 1, 'qty' => 1, 'percent_value' => 30], + ], + null + ], + ]; + } + + /** + * Assert that product price rendered with expected special and regular prices if + * product has catalog rule price with different type of prices. + * + * @magentoDataFixture Magento/Catalog/_files/product_special_price.php + * @magentoDataFixture Magento/CatalogRule/_files/delete_catalog_rule_data.php + * + * @dataProvider catalogRulesDataProvider + * + * @param float $specialPrice + * @param float $regularPrice + * @param array $catalogRules + * @param array $tierPrices + * @param array|null $tierMessageConfig + * @return void + */ + public function testRenderCatalogRulePriceInCombinationWithDifferentPriceTypes( + float $specialPrice, + float $regularPrice, + array $catalogRules, + array $tierPrices, + ?array $tierMessageConfig + ): void { + $this->createCatalogRulesForProduct($catalogRules); + $this->indexBuilder->reindexFull(); + $this->assertRenderedPrices($specialPrice, $regularPrice, $tierPrices, $tierMessageConfig); + } + + /** + * Data provider with expect special and regular price, catalog rule data and tier price. + * + * @return array + */ + public function catalogRulesDataProvider(): array + { + return [ + 'fixed_catalog_rule_price_more_than_special_price' => [ + 5.99, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 2], + ], + [], + null + ], + 'fixed_catalog_rule_price_lower_than_special_price' => [ + 2, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 8], + ], + [], + null + ], + 'fixed_catalog_rule_price_more_than_tier_price' => [ + 4, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 6], + ], + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 2, 'percent_value' => 70], + ], + ['qty' => 2, 'price' => 3.00, 'percent' => 70], + ], + 'fixed_catalog_rule_price_lower_than_tier_price' => [ + 2, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 7], + ], + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 1, 'value' => 2], + ], + null + ], + 'adjust_percent_catalog_rule_price_lower_than_special_price' => [ + 4.50, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 45, RuleInterface::SIMPLE_ACTION => 'to_percent'], + ], + [], + null + ], + 'adjust_percent_catalog_rule_price_lower_than_tier_price' => [ + 3, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 30, RuleInterface::SIMPLE_ACTION => 'to_percent'], + ], + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 1, 'value' => 3.50], + ], + null + ], + 'percent_catalog_rule_price_lower_than_special_price' => [ + 2, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 2, RuleInterface::SIMPLE_ACTION => 'to_fixed'], + ], + [], + null + ], + 'percent_catalog_rule_price_lower_than_tier_price' => [ + 1, + 10, + [ + [RuleInterface::DISCOUNT_AMOUNT => 1, RuleInterface::SIMPLE_ACTION => 'to_fixed'], + ], + [ + ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 1, 'value' => 3], + ], + null + ], + ]; + } + + /** + * Check that price html contain all provided prices. + * + * @param string $priceHtml + * @param float $specialPrice + * @param float $regularPrice + * @return void + */ + private function checkPrices(string $priceHtml, float $specialPrice, float $regularPrice): void + { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($this->getSpecialPriceXpath($specialPrice), $priceHtml), + "Special price {$specialPrice} is not as expected. Rendered html: {$priceHtml}" + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($this->getRegularPriceLabelXpath(), $priceHtml), + "Regular price label 'Regular Price' not founded. Rendered html: {$priceHtml}" + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($this->getRegularPriceXpath($regularPrice), $priceHtml), + "Regular price {$regularPrice} is not as expected. Rendered html: {$priceHtml}" + ); + } + + /** + * Assert that tier price message. + * + * @param string $priceHtml + * @param array $tierMessageConfig + * @return void + */ + private function checkTierPriceMessage(string $priceHtml, array $tierMessageConfig): void + { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($this->getTierPriceMessageXpath($tierMessageConfig), $priceHtml), + "Tier price message not founded. Rendered html: {$priceHtml}" + ); + } + + /** + * Render price render template with product. + * + * @param ProductInterface $product + * @return string + */ + private function getPriceHtml(ProductInterface $product): string + { + $this->registerProduct($product); + $this->page->addHandle([ + 'default', + 'catalog_product_view', + ]); + $this->page->getLayout()->generateXml(); + $priceHtml = ''; + $availableChildNames = [ + 'product.info.price', + 'product.price.tier' + ]; + foreach ($this->page->getLayout()->getChildNames('product.info.main') as $childName) { + if (in_array($childName, $availableChildNames, true)) { + $priceHtml .= $this->page->getLayout()->renderElement($childName, false); + } + } + + return $priceHtml; + } + + /** + * Add product to the registry. + * + * @param ProductInterface $product + * @return void + */ + private function registerProduct(ProductInterface $product): void + { + $this->registry->unregister('product'); + $this->registry->register('product', $product); + } + + /** + * Create provided tier prices for product. + * + * @param ProductInterface $product + * @param array $tierPrices + * @return ProductInterface + */ + private function createTierPricesForProduct(ProductInterface $product, array $tierPrices): ProductInterface + { + if (empty($tierPrices)) { + return $product; + } + + $createdTierPrices = []; + foreach ($tierPrices as $tierPrice) { + $tierPriceExtensionAttribute = $this->productTierPriceExtensionFactory->create(); + $tierPriceExtensionAttribute->setWebsiteId(0); + + if (isset($tierPrice['percent_value'])) { + $tierPriceExtensionAttribute->setPercentageValue($tierPrice['percent_value']); + unset($tierPrice['percent_value']); + } + + $createdTierPrices[] = $this->productTierPriceFactory->create( + [ + 'data' => $tierPrice + ] + )->setExtensionAttributes($tierPriceExtensionAttribute); + } + $product->setTierPrices($createdTierPrices); + + return $this->productRepository->save($product); + } + + /** + * @param float $specialPrice + * @return string + */ + private function getSpecialPriceXpath(float $specialPrice): string + { + $pathsForSearch = [ + "//div[contains(@class, 'price-box') and contains(@class, 'price-final_price')]", + "//span[contains(@class, 'special-price')]", + sprintf("//span[contains(@class, 'price') and text()='$%01.2f']", $specialPrice), + ]; + + return implode('', $pathsForSearch); + } + + /** + * @param float $regularPrice + * @return string + */ + private function getRegularPriceXpath(float $regularPrice): string + { + $pathsForSearch = [ + "//div[contains(@class, 'price-box') and contains(@class, 'price-final_price')]", + "//span[contains(@class, 'old-price')]", + "//span[contains(@class, 'price-container')]", + sprintf("//span[contains(@class, 'price') and text()='$%01.2f']", $regularPrice), + ]; + + return implode('', $pathsForSearch); + } + + /** + * @return string + */ + private function getRegularPriceLabelXpath(): string + { + $pathsForSearch = [ + "//div[contains(@class, 'price-box') and contains(@class, 'price-final_price')]", + "//span[contains(@class, 'old-price')]", + "//span[contains(@class, 'price-container')]", + "//span[text()='Regular Price']", + ]; + + return implode('', $pathsForSearch); + } + + /** + * Return tier price message xpath. Message must contain expected quantity, + * price and discount percent. + * + * @param array $expectedMessage + * @return string + */ + private function getTierPriceMessageXpath(array $expectedMessage): string + { + [$qty, $price, $percent] = array_values($expectedMessage); + $liPaths = [ + "contains(@class, 'item') and contains(text(), 'Buy {$qty} for')", + sprintf("//span[contains(@class, 'price') and text()='$%01.2f']", $price), + "//span[contains(@class, 'percent') and contains(text(), '{$percent}')]", + ]; + + return sprintf( + "//ul[contains(@class, 'prices-tier') and contains(@class, 'items')]//li[%s]", + implode(' and ', $liPaths) + ); + } + + /** + * Process test with combination of special and tier price. + * + * @param float $specialPrice + * @param float $regularPrice + * @param array $tierPrices + * @param array|null $tierMessageConfig + * @return void + */ + private function assertRenderedPrices( + float $specialPrice, + float $regularPrice, + array $tierPrices, + ?array $tierMessageConfig + ): void { + $product = $this->productRepository->get('simple', false, null, true); + $product = $this->createTierPricesForProduct($product, $tierPrices); + $priceHtml = $this->getPriceHtml($product); + $this->checkPrices($priceHtml, $specialPrice, $regularPrice); + if (null !== $tierMessageConfig) { + $this->checkTierPriceMessage($priceHtml, $tierMessageConfig); + } + } + + /** + * Create provided catalog rules. + * + * @param array $catalogRules + * @return void + */ + private function createCatalogRulesForProduct(array $catalogRules): void + { + $baseWebsite = $this->websiteRepository->get('base'); + $staticRuleData = [ + RuleInterface::IS_ACTIVE => 1, + RuleInterface::NAME => 'Test rule name.', + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + RuleInterface::SIMPLE_ACTION => 'by_fixed', + RuleInterface::STOP_RULES_PROCESSING => false, + RuleInterface::SORT_ORDER => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + 'website_ids' => [$baseWebsite->getId()] + ]; + + foreach ($catalogRules as $catalogRule) { + $catalogRule = array_replace($staticRuleData, $catalogRule); + $catalogRule = $this->catalogRuleFactory->create(['data' => $catalogRule]); + $this->catalogRuleRepository->save($catalogRule); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php index 25bb55ffbc32c..4255d7d3c98e5 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php @@ -20,7 +20,7 @@ ] ); -/** @var Magento\Catalog\Api\CategoryLinkManagementInterface $linkManagement */ +/** @var Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ $categoryLinkManagement = $objectManager->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); $reflectionClass = new \ReflectionClass(get_class($categoryLinkManagement)); $properties = [ @@ -115,6 +115,7 @@ ->setName('Inactive') ->setParentId(2) ->setPath('1/2/8') + ->setLevel(2) ->setAvailableSortBy('name') ->setDefaultSortBy('name') ->setIsActive(false) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_product_assigned_to_website.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_product_assigned_to_website.php new file mode 100644 index 0000000000000..b4fd3d997c924 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_product_assigned_to_website.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\CategoryFactory; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var CategoryFactory $categoryFactory */ +$categoryFactory = $objectManager->get(CategoryFactory::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var CategoryRepositoryInterface $categoryRepository */ +$categoryRepository = $objectManager->get(CategoryRepositoryInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$defaultWebsiteId = $websiteRepository->get('base')->getId(); +$category = $categoryFactory->create(); +$categoryData = [ + 'name' => 'Category with product', + 'attribute_set_id' => $category->getDefaultAttributeSetId(), + 'parent_id' => 2, + 'is_active' => true, +]; +$category->setData($categoryData); +$category = $categoryRepository->save($category); + +$product = $productFactory->create(); +$productData = [ + 'type_id' => Type::TYPE_SIMPLE, + 'attribute_set_id' => $product->getDefaultAttributeSetId(), + 'sku' => 'product_with_category', + 'website_ids' => [$defaultWebsiteId], + 'name' => 'Product with category', + 'price' => 10, + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ], + 'category_ids' => [2, $category->getId()], + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, +]; +$product->setData($productData); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_product_assigned_to_website_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_product_assigned_to_website_rollback.php new file mode 100644 index 0000000000000..aab3c6f938248 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_product_assigned_to_website_rollback.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Catalog\Model\GetCategoryByName; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var CategoryRepositoryInterface $categoryRepository */ +$categoryRepository = $objectManager->create(CategoryRepositoryInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var GetCategoryByName $getCategoryByName */ +$getCategoryByName = $objectManager->create(GetCategoryByName::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $productRepository->deleteById('product_with_category'); +} catch (NoSuchEntityException $e) { + // product already deleted +} + +$category = $getCategoryByName->execute('Category with product'); + +if ($category->getId()) { + $categoryRepository->delete($category); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_parent_anchor.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_parent_anchor.php new file mode 100644 index 0000000000000..abdec4954135b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_parent_anchor.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Model\CategoryFactory; +use Magento\Catalog\Model\CategoryRepository; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var CategoryFactory $categoryFactory */ +$categoryFactory = $objectManager->get(CategoryFactory::class); +/** @var CategoryRepository $categoryRepository */ +$categoryRepository = $objectManager->create(CategoryRepository::class); +$parentCategory = $categoryFactory->create(); +$attributeSetId = $parentCategory->getDefaultAttributeSetId(); +$parentCategory->isObjectNew(true); +$parentCategoryData = [ + 'name' => 'Parent category', + 'attribute_set_id' => $attributeSetId, + 'parent_id' => 2, + 'is_active' => true, + 'is_anchor' => true, +]; +$parentCategory->setData($parentCategoryData); +$parentCategoryId = $categoryRepository->save($parentCategory)->getId(); + +$category = $categoryFactory->create(); +$category->isObjectNew(true); +$categoryData = [ + 'name' => 'Child category', + 'attribute_set_id' => $attributeSetId, + 'parent_id' => $parentCategoryId, + 'is_active' => true, +]; +$category->setData($categoryData); +$categoryRepository->save($category); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_parent_anchor_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_parent_anchor_rollback.php new file mode 100644 index 0000000000000..35a0ee38c8cfe --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_parent_anchor_rollback.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\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var CollectionFactory $categoryCollectionFactory */ +$categoryCollectionFactory = $objectManager->get(CollectionFactory::class); +/** @var CategoryRepositoryInterface $categoryRepository */ +$categoryRepository = $objectManager->create(CategoryRepositoryInterface::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$categoryCollection = $categoryCollectionFactory->create(); +$categoryCollection->addAttributeToFilter( + CategoryInterface::KEY_NAME, + ['in' => ['Parent category', 'Child category']] +); + +foreach ($categoryCollection as $category) { + $categoryRepository->delete($category); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_three_products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_three_products.php new file mode 100644 index 0000000000000..b29c17e392ed9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_three_products.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Store\Model\Store; + +require __DIR__ . '/category_with_different_price_products.php'; + +$product = $productFactory->create(); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setStoreId(Store::DEFAULT_STORE_ID) + ->setWebsiteIds([1]) + ->setName('Simple Product2') + ->setSku('simple1002') + ->setPrice(10) + ->setWeight(1) + ->setStockData(['use_config_manage_stock' => 0]) + ->setCategoryIds([$category->getId()]) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_three_products_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_three_products_rollback.php new file mode 100644 index 0000000000000..a90b9e732e827 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_with_three_products_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +try { + $productRepository->deleteById('simple1002'); +} catch (NoSuchEntityException $e) { + //Already deleted. +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/category_with_different_price_products_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_datetime_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_datetime_attribute.php new file mode 100644 index 0000000000000..c1e788861266c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_datetime_attribute.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; +use Magento\Catalog\Setup\CategorySetup; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CategorySetup $installer */ +$installer = $objectManager->create(CategorySetup::class); +$entityType = $installer->getEntityTypeId(ProductAttributeInterface::ENTITY_TYPE_CODE); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +/** @var Attribute $attribute */ +$attribute = $objectManager->get(AttributeFactory::class)->create(); +if (!$attribute->loadByCode($entityType, 'datetime_attribute')->getAttributeId()) { + $attribute->setData( + [ + 'attribute_code' => 'datetime_attribute', + 'entity_type_id' => $entityType, + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'datetime', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 1, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Date Time Attribute'], + 'backend_type' => 'datetime', + ] + ); + $attributeRepository->save($attribute); + $installer->addAttributeToGroup( + ProductAttributeInterface::ENTITY_TYPE_CODE, + 'Default', + 'General', + $attribute->getId() + ); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_datetime_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_datetime_attribute_rollback.php new file mode 100644 index 0000000000000..51b1ea5418ca9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_datetime_attribute_rollback.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Registry; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; + +$objectManager = Bootstrap::getObjectManager(); +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); + +try { + $attributeRepository->deleteById('datetime_attribute'); +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_in_nested_anchor_categories.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_in_nested_anchor_categories.php new file mode 100644 index 0000000000000..39d939575f5bf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_in_nested_anchor_categories.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +$categories = [ + [ + 'id' => 444, + 'parentId' => 2, + 'level' => 2, + 'path' => '1/2/3' + ], + [ + 'id' => 445, + 'parentId' => 444, + 'level' => 3, + 'path' => '1/2/3/4' + ], + [ + 'id' => 446, + 'parentId' => 445, + 'level' => 4, + 'path' => '1/2/3/4/5' + ], +]; + +$products = [ + [ + 'id' => 444, + 'categoryIDs' => [446] + ], + [ + 'id' => 445, + 'categoryIDs' => [446] + ] +]; + +foreach ($categories as $category) { + $categoryModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Model\Category::class); + $categoryModel->isObjectNew(true); + $categoryModel->setId($category['id']) + ->setName('Category ' . $category['id']) + ->setParentId($category['parentId']) + ->setPath($category['path']) + ->setLevel($category['level']) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(1) + ->setAvailableSortBy(['position']) + ->save(); +} + +foreach ($products as $product) { + /** @var $product \Magento\Catalog\Model\Product */ + $productModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Model\Product::class); + $productModel->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId($product['id']) + ->setAttributeSetId(4) + ->setStoreId(1) + ->setWebsiteIds([1]) + ->setName('Simple Product ' . $product['id']) + ->setSku('simple' . $product['id']) + ->setPrice(10) + ->setWeight(18) + ->setStockData(['use_config_manage_stock' => 0]) + ->setCategoryIds($product['categoryIDs']) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->save(); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_in_nested_anchor_categories_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_in_nested_anchor_categories_rollback.php new file mode 100644 index 0000000000000..f735caa801794 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_in_nested_anchor_categories_rollback.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$categoryIDs = [444, 445, 446]; +$productIDs = [444, 445]; + +foreach ($productIDs as $productID) { + /** @var $product \Magento\Catalog\Model\Product */ + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Model\Product::class); + $product->load($productID); + if ($product->getId()) { + $product->delete(); + } +} + +foreach ($categoryIDs as $categoryID) { + /** @var $category \Magento\Catalog\Model\Category */ + $category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Model\Category::class); + $category->load($categoryID); + if ($category->getId()) { + $category->delete(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_tax_none_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_tax_none_rollback.php index ceffb1c87d970..79245d255a7e6 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_tax_none_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_tax_none_rollback.php @@ -5,9 +5,9 @@ */ declare(strict_types=1); -use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\ObjectManager; @@ -15,19 +15,16 @@ $objectManager = Bootstrap::getObjectManager(); /** @var ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->get(ProductRepositoryInterface::class); -/** @var \Magento\Framework\Registry $registry */ -$registry =$objectManager->get(\Magento\Framework\Registry::class); - +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); - try { - /** @var ProductInterface $product */ $product = $productRepository->get('simple-product-tax-none', false, null, true); $productRepository->delete($product); } catch (NoSuchEntityException $e) { // isolation on } - +$productRepository->cleanCache(); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_category_which_has_parent_category.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_category_which_has_parent_category.php new file mode 100644 index 0000000000000..4d3ad8fcbf2f4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_category_which_has_parent_category.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/categories_no_products.php'; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var ProductInterfaceFactory $productFactory */ +$productFactory = $objectManager->get(ProductInterfaceFactory::class); +$defaultWebsiteId = $websiteRepository->get('base')->getId(); +$product = $productFactory->create(); +$product->setData( + [ + 'attribute_set_id' => $product->getDefaultAttributeSetId(), + 'website_ids' => [ + $defaultWebsiteId + ], + 'name' => 'Simple product with child category', + 'sku' => 'simple_with_child_category', + 'price' => 10, + 'description' => 'Description product with category which has parent category', + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, + 'category_ids' => [5], + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1 + ], + 'url_key' => 'simple-with-child-category' + ] +); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_category_which_has_parent_category_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_category_which_has_parent_category_rollback.php new file mode 100644 index 0000000000000..58acf8e35129e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_category_which_has_parent_category_rollback.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; + +require __DIR__ . '/categories_no_products_rollback.php'; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +try { + $productRepository->deleteById('simple_with_child_category'); +} catch (NoSuchEntityException $exception) { + //Product already deleted. +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price.php new file mode 100644 index 0000000000000..31576f2baf55b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; +use Magento\Customer\Model\Group; +use Magento\Store\Api\WebsiteRepositoryInterface; + +require __DIR__ . '/product_simple_tax_none.php'; + +$product = $productRepository->get('simple-product-tax-none'); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(ProductTierPriceInterfaceFactory::class); +/** @var ProductTierPriceExtensionFactory $tpExtensionAttributeFactory */ +$tpExtensionAttributeFactory = $objectManager->get(ProductTierPriceExtensionFactory::class); +$adminWebsite = $websiteRepository->get('admin'); +$tierPriceExtensionAttribute = $tpExtensionAttributeFactory->create( + [ + 'data' => [ + 'website_id' => $adminWebsite->getId(), + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => Group::CUST_GROUP_ALL, + 'qty' => 2, + 'value' => 40 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttribute); +$product->setTierPrices($tierPrices); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_logged_user.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_logged_user.php new file mode 100644 index 0000000000000..44a4c82c277d4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_logged_user.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; +use Magento\Store\Api\WebsiteRepositoryInterface; + +require __DIR__ . '/product_simple_tax_none.php'; + +$product = $productRepository->get('simple-product-tax-none'); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(ProductTierPriceInterfaceFactory::class); +/** @var ProductTierPriceExtensionFactory $tpExtensionAttributeFactory */ +$tpExtensionAttributeFactory = $objectManager->get(ProductTierPriceExtensionFactory::class); +$adminWebsite = $websiteRepository->get('admin'); +$tierPriceExtensionAttribute = $tpExtensionAttributeFactory->create( + [ + 'data' => [ + 'website_id' => $adminWebsite->getId(), + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => 1, + 'qty' => 1, + 'value' => 10 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttribute); +$product->setTierPrices($tierPrices); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_logged_user_rollback.php new file mode 100644 index 0000000000000..554f953580a5c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_logged_user_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_simple_tax_none_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_not_logged_user.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_not_logged_user.php new file mode 100644 index 0000000000000..68afbe529808e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_not_logged_user.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; +use Magento\Customer\Model\Group; +use Magento\Store\Api\WebsiteRepositoryInterface; + +require __DIR__ . '/product_simple_tax_none.php'; + +$product = $productRepository->get('simple-product-tax-none'); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(ProductTierPriceInterfaceFactory::class); +/** @var ProductTierPriceExtensionFactory $tpExtensionAttributeFactory */ +$tpExtensionAttributeFactory = $objectManager->get(ProductTierPriceExtensionFactory::class); +$adminWebsite = $websiteRepository->get('admin'); +$tierPriceExtensionAttribute = $tpExtensionAttributeFactory->create( + [ + 'data' => [ + 'website_id' => $adminWebsite->getId(), + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => Group::NOT_LOGGED_IN_ID, + 'qty' => 1, + 'value' => 30 + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttribute); +$product->setTierPrices($tierPrices); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_not_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_not_logged_user_rollback.php new file mode 100644 index 0000000000000..554f953580a5c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_for_not_logged_user_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_simple_tax_none_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_rollback.php new file mode 100644 index 0000000000000..554f953580a5c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_fixed_tier_price_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_simple_tax_none_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_percent_tier_price.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_percent_tier_price.php new file mode 100644 index 0000000000000..0bef251a254f3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_percent_tier_price.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; +use Magento\Customer\Model\Group; +use Magento\Store\Api\WebsiteRepositoryInterface; + +require __DIR__ . '/product_simple_tax_none.php'; + +$product = $productRepository->get('simple-product-tax-none'); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(ProductTierPriceInterfaceFactory::class); +/** @var ProductTierPriceExtensionFactory $tpExtensionAttributeFactory */ +$tpExtensionAttributeFactory = $objectManager->get(ProductTierPriceExtensionFactory::class); +$adminWebsite = $websiteRepository->get('admin'); +$tierPriceExtensionAttribute = $tpExtensionAttributeFactory->create( + [ + 'data' => [ + 'website_id' => $adminWebsite->getId(), + 'percentage_value' => 50, + ] + ] +); +$tierPrices[] = $tierPriceFactory->create( + [ + 'data' => [ + 'customer_group_id' => Group::CUST_GROUP_ALL, + 'qty' => 2, + ] + ] +)->setExtensionAttributes($tierPriceExtensionAttribute); +$product->setTierPrices($tierPrices); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_percent_tier_price_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_percent_tier_price_rollback.php new file mode 100644 index 0000000000000..554f953580a5c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_percent_tier_price_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_simple_tax_none_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_editor_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_editor_attribute.php new file mode 100644 index 0000000000000..2b62f8a78252a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_editor_attribute.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; +use Magento\Catalog\Setup\CategorySetup; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CategorySetup $installer */ +$installer = $objectManager->create(CategorySetup::class); +$entityType = $installer->getEntityTypeId(ProductAttributeInterface::ENTITY_TYPE_CODE); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +/** @var Attribute $attribute */ +$attribute = $objectManager->get(AttributeFactory::class)->create(); +if (!$attribute->loadByCode($entityType, 'text_editor_attribute')->getAttributeId()) { + $attribute->setData( + [ + 'attribute_code' => 'text_editor_attribute', + 'entity_type_id' => $entityType, + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'textarea', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Text Editor Attribute'], + 'backend_type' => 'text', + 'is_wysiwyg_enabled' => '1', + ] + ); + $attributeRepository->save($attribute); + $installer->addAttributeToGroup( + ProductAttributeInterface::ENTITY_TYPE_CODE, + 'Default', + 'General', + $attribute->getId() + ); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_editor_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_editor_attribute_rollback.php new file mode 100644 index 0000000000000..09d3c1ea392ba --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_editor_attribute_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\Exception\NoSuchEntityException; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Registry; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); + +try { + $attributeRepository->deleteById('text_editor_attribute'); +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_category.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_category.php new file mode 100644 index 0000000000000..ebc6bce655198 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_category.php @@ -0,0 +1,47 @@ +<?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\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/category.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$defaultWebsiteId = $websiteRepository->get('base')->getId(); +$product = $productFactory->create(); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$defaultWebsiteId]) + ->setName('Simple Product In Stock') + ->setSku('in-stock-product') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription('Short description') + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([333]) + ->setStockData(['use_config_manage_stock' => 0]) + ->setCanSaveCustomOptions(true) + ->setHasOptions(true); +/** @var ProductRepositoryInterface $productRepositoryFactory */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_category_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_category_rollback.php new file mode 100644 index 0000000000000..12d8c720d10d1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_category_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\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/category_rollback.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); + +try { + $productRepository->deleteById('in-stock-product'); +} catch (NoSuchEntityException $e) { + //already removed +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/reindex_catalog_category_flat.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/reindex_catalog_category_flat.php new file mode 100644 index 0000000000000..03a6bd7735422 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/reindex_catalog_category_flat.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Model\Indexer\Category\Flat\State; +use Magento\Indexer\Model\Indexer; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Indexer $indexer */ +$indexer = $objectManager->get(Indexer::class); +$indexer->load(State::INDEXER_ID); +$indexer->reindexAll(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php index d3a2e4c53f246..eecdcdf038cf8 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php @@ -7,6 +7,7 @@ use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\ImportExport\Model\Export\Adapter\AbstractAdapter; use Magento\Store\Model\Store; /** @@ -62,6 +63,11 @@ abstract class AbstractProductExportImportTestCase extends \PHPUnit\Framework\Te 'tax_class_id', ]; + /** + * @var AbstractAdapter + */ + private $writer; + /** * @inheritdoc */ @@ -367,7 +373,7 @@ protected function executeImportReplaceTest( * Export products in the system. * * @param \Magento\CatalogImportExport\Model\Export\Product|null $exportProduct - * @return string Return exported file name + * @return string Return exported file */ private function exportProducts(\Magento\CatalogImportExport\Model\Export\Product $exportProduct = null) { @@ -376,12 +382,11 @@ private function exportProducts(\Magento\CatalogImportExport\Model\Export\Produc $exportProduct = $exportProduct ?: $this->objectManager->create( \Magento\CatalogImportExport\Model\Export\Product::class ); - $exportProduct->setWriter( - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\ImportExport\Model\Export\Adapter\Csv::class, - ['fileSystem' => $this->fileSystem, 'destination' => $csvfile] - ) + $this->writer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\ImportExport\Model\Export\Adapter\Csv::class, + ['fileSystem' => $this->fileSystem, 'destination' => $csvfile] ); + $exportProduct->setWriter($this->writer); $this->assertNotEmpty($exportProduct->export()); return $csvfile; diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php index 4753d947e9d3c..508560d000271 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php @@ -535,4 +535,84 @@ public function testExportProductWithTwoWebsites() $reinitiableConfig->setValue('catalog/price/scope', \Magento\Store\Model\Store::PRICE_SCOPE_GLOBAL); $switchPriceScope->execute($observer); } + + /** + * Verify that "stock status" filter correctly applies to export result + * + * @param string $value + * @param array $productsIncluded + * @param array $productsNotIncluded + * @magentoDataFixture Magento/Catalog/_files/multiple_products_with_few_out_of_stock.php + * @dataProvider filterByQuantityAndStockStatusDataProvider + */ + public function testFilterByQuantityAndStockStatus( + string $value, + array $productsIncluded, + array $productsNotIncluded + ) { + $exportData = $this->doExport(['quantity_and_stock_status' => $value]); + foreach ($productsIncluded as $productName) { + $this->assertContains($productName, $exportData); + } + foreach ($productsNotIncluded as $productName) { + $this->assertNotContains($productName, $exportData); + } + } + /** + * @return array + */ + public function filterByQuantityAndStockStatusDataProvider(): array + { + return [ + [ + '', + [ + 'Simple Product OOS', + 'Simple Product Not Visible', + 'Simple Product Visible and InStock' + ], + [ + ] + ], + [ + '1', + [ + 'Simple Product Not Visible', + 'Simple Product Visible and InStock' + ], + [ + 'Simple Product OOS' + ] + ], + [ + '0', + [ + 'Simple Product OOS' + ], + [ + 'Simple Product Not Visible', + 'Simple Product Visible and InStock' + ] + ] + ]; + } + + /** + * @param array $filters + * @return string + */ + private function doExport(array $filters = []): string + { + $this->model->setWriter( + $this->objectManager->create( + \Magento\ImportExport\Model\Export\Adapter\Csv::class + ) + ); + $this->model->setParameters( + [ + \Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP => $filters + ] + ); + return $this->model->export(); + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 4e2b73de301a3..f24981ca40156 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -19,6 +19,9 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product as ProductResource; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; +use Magento\CatalogInventory\Model\Stock; +use Magento\CatalogInventory\Model\StockRegistry; +use Magento\CatalogInventory\Model\StockRegistryStorage; use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; @@ -30,9 +33,9 @@ use Magento\ImportExport\Model\Import\Source\Csv; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper; use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection; use Psr\Log\LoggerInterface; -use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper; /** * Class ProductTest @@ -230,9 +233,9 @@ public function testSaveStockItemQty() $existingProductIds = [$id1, $id2, $id3]; $stockItems = []; foreach ($existingProductIds as $productId) { - /** @var $stockRegistry \Magento\CatalogInventory\Model\StockRegistry */ + /** @var $stockRegistry StockRegistry */ $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\CatalogInventory\Model\StockRegistry::class + StockRegistry::class ); $stockItem = $stockRegistry->getStockItem($productId, 1); @@ -261,9 +264,9 @@ public function testSaveStockItemQty() /** @var $stockItmBeforeImport \Magento\CatalogInventory\Model\Stock\Item */ foreach ($stockItems as $productId => $stockItmBeforeImport) { - /** @var $stockRegistry \Magento\CatalogInventory\Model\StockRegistry */ + /** @var $stockRegistry StockRegistry */ $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\CatalogInventory\Model\StockRegistry::class + StockRegistry::class ); $stockItemAfterImport = $stockRegistry->getStockItem($productId, 1); @@ -400,7 +403,7 @@ public function testSaveCustomOptionsWithMultipleStoreViews() $pathToFile = __DIR__ . '/_files/' . $importFile; $importModel = $this->createImportModel($pathToFile); $errors = $importModel->validateData(); - $this->assertTrue($errors->getErrorsCount() == 0); + $this->assertTrue($errors->getErrorsCount() == 0, 'Import File Validation Failed'); $importModel->importData(); /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( @@ -419,20 +422,41 @@ public function testSaveCustomOptionsWithMultipleStoreViews() $actualOptions = $actualData['options']; sort($expectedOptions); sort($actualOptions); - $this->assertEquals($expectedOptions, $actualOptions); + $this->assertEquals( + $expectedOptions, + $actualOptions, + 'Expected and actual options arrays does not match' + ); // assert of options data - $this->assertCount(count($expectedData['data']), $actualData['data']); - $this->assertCount(count($expectedData['values']), $actualData['values']); + $this->assertCount( + count($expectedData['data']), + $actualData['data'], + 'Expected and actual data count does not match' + ); + $this->assertCount( + count($expectedData['values']), + $actualData['values'], + 'Expected and actual values count does not match' + ); + foreach ($expectedData['options'] as $expectedId => $expectedOption) { $elementExist = false; // find value in actual options and values foreach ($actualData['options'] as $actualId => $actualOption) { if ($actualOption == $expectedOption) { $elementExist = true; - $this->assertEquals($expectedData['data'][$expectedId], $actualData['data'][$actualId]); + $this->assertEquals( + $expectedData['data'][$expectedId], + $actualData['data'][$actualId], + 'Expected data does not match actual data' + ); if (array_key_exists($expectedId, $expectedData['values'])) { - $this->assertEquals($expectedData['values'][$expectedId], $actualData['values'][$actualId]); + $this->assertEquals( + $expectedData['values'][$expectedId], + $actualData['values'][$actualId], + 'Expected values does not match actual data' + ); } unset($actualData['options'][$actualId]); // remove value in case of duplicating key values @@ -445,7 +469,11 @@ public function testSaveCustomOptionsWithMultipleStoreViews() // Make sure that after importing existing options again, option IDs and option value IDs are not changed $customOptionValues = $this->getCustomOptionValues($sku); $this->createImportModel($pathToFile)->importData(); - $this->assertEquals($customOptionValues, $this->getCustomOptionValues($sku)); + $this->assertEquals( + $customOptionValues, + $this->getCustomOptionValues($sku), + 'Option IDs changed after second import' + ); } } @@ -2031,9 +2059,9 @@ public function testProductWithUseConfigSettings() $this->_model->importData(); foreach ($products as $sku => $manageStockUseConfig) { - /** @var \Magento\CatalogInventory\Model\StockRegistry $stockRegistry */ + /** @var StockRegistry $stockRegistry */ $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\CatalogInventory\Model\StockRegistry::class + StockRegistry::class ); $stockItem = $stockRegistry->getStockItemBySku($sku); $this->assertEquals($manageStockUseConfig, $stockItem->getUseConfigManageStock()); @@ -2941,4 +2969,58 @@ public function testImportConfigurableProductImages() } $this->assertEquals($expected, $actual); } + + /** + * Test that product stock status is updated after import + * + * @magentoDataFixture mediaImportImageFixture + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testProductStockStatusShouldBeUpdated() + { + /** @var $stockRegistry StockRegistry */ + $stockRegistry = $this->objectManager->create(StockRegistry::class); + /** @var StockRegistryStorage $stockRegistryStorage */ + $stockRegistryStorage = $this->objectManager->get(StockRegistryStorage::class); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_IN_STOCK, $status->getStockStatus()); + $this->importDataForMediaTest('disable_product.csv'); + $stockRegistryStorage->clean(); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_OUT_OF_STOCK, $status->getStockStatus()); + $this->importDataForMediaTest('enable_product.csv'); + $stockRegistryStorage->clean(); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_IN_STOCK, $status->getStockStatus()); + } + + /** + * Test that product stock status is updated after import on schedule + * + * @magentoDataFixture mediaImportImageFixture + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/CatalogImportExport/_files/cataloginventory_stock_item_update_by_schedule.php + * @magentoDbIsolation disabled + */ + public function testProductStockStatusShouldBeUpdatedOnSchedule() + { + /** * @var $indexProcessor \Magento\Indexer\Model\Processor */ + $indexProcessor = $this->objectManager->create(\Magento\Indexer\Model\Processor::class); + /** @var $stockRegistry StockRegistry */ + $stockRegistry = $this->objectManager->create(StockRegistry::class); + /** @var StockRegistryStorage $stockRegistryStorage */ + $stockRegistryStorage = $this->objectManager->get(StockRegistryStorage::class); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_IN_STOCK, $status->getStockStatus()); + $this->importDataForMediaTest('disable_product.csv'); + $indexProcessor->updateMview(); + $stockRegistryStorage->clean(); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_OUT_OF_STOCK, $status->getStockStatus()); + $this->importDataForMediaTest('enable_product.csv'); + $indexProcessor->updateMview(); + $stockRegistryStorage->clean(); + $status = $stockRegistry->getStockStatusBySku('simple'); + $this->assertEquals(Stock::STOCK_IN_STOCK, $status->getStockStatus()); + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/disable_product.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/disable_product.csv new file mode 100644 index 0000000000000..b366fb63afd92 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/disable_product.csv @@ -0,0 +1,2 @@ +"sku", "product_online" +"simple", "0" diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/enable_product.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/enable_product.csv new file mode 100644 index 0000000000000..eb36621bc61e1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/enable_product.csv @@ -0,0 +1,2 @@ +"sku", "product_online" +"simple", "1" diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv index d4c4130e946a4..17858531993db 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv @@ -2,3 +2,7 @@ sku,website_code,store_view_code,attribute_set_code,product_type,name,descriptio simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1,sku=1-text,price=100|name=Test Date and Time Title,type=date_time,required=1,sku=2-date,price=200|name=Test Select,type=drop_down,required=1,sku=3-1-select,price=310,option_title=Select Option 1|name=Test Select,type=drop_down,required=1,sku=3-2-select,price=320,option_title=Select Option 2|name=Test Checkbox,type=checkbox,required=1,sku=4-1-select,price=410,option_title=Checkbox Option 1|name=Test Checkbox,type=checkbox,required=1,sku=4-2-select,price=420,option_title=Checkbox Option 2|name=Test Radio,type=radio,required=1,sku=5-1-radio,price=510,option_title=Radio Option 1|name=Test Radio,type=radio,required=1,sku=5-2-radio,price=520,option_title=Radio Option 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, simple,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_default,type=field,sku=1-text|name=Test Date and Time Title_default,type=date_time,sku=2-date|name=Test Select_default,type=drop_down,sku=3-1-select,option_title=Select Option 1_default|name=Test Select_default,type=drop_down,sku=3-2-select,option_title=Select Option 2_default|name=Test Checkbox_default,type=checkbox,sku=4-1-select,option_title=Checkbox Option 1_default|name=Test Checkbox_default,type=checkbox,sku=4-2-select,option_title=Checkbox Option 2_default|name=Test Radio_default,type=radio,sku=5-1-radio,option_title=Radio Option 1_default|name=Test Radio_default,type=radio,sku=5-2-radio,option_title=Radio Option 2_default",,,,,,,,,,,,,,,,,,,,,,,,,,, simple,,secondstore,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_fixture_second_store,type=field,sku=1-text,price=101|name=Test Date and Time Title_fixture_second_store,type=date_time,sku=2-date,price=201|name=Test Select_fixture_second_store,type=drop_down,sku=3-1-select,price=311,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,sku=3-2-select,price=321,option_title=Select Option 2_fixture_second_store|name=Test Checkbox_second_store,type=checkbox,sku=4-1-select,price=411,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,sku=4-2-select,price=421,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,sku=5-1-radio,price=511,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,sku=5-2-radio,price=521,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, +newprod2,base,secondstore,Default,configurable,New Product 2,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-2,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +newprod3,base,,Default,configurable,New Product 3,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-3,,,,,,,,,,,,,,,,,,,,,,,,"name=Line 1,type=field,max_characters=30,required=1,option_title=Line 1|name=Line 2,type=field,max_characters=30,required=0,option_title=Line 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +newprod4,base,secondstore,Default,configurable,New Product 4,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-4,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +newprod5,base,,Default,configurable,New Product 5,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-5,,,,,,,,,,,,,,,,,,,,,,,,"name=Line 3,type=field,max_characters=30,required=1,option_title=Line 3|name=Line 4,type=field,max_characters=30,required=0,option_title=Line 4",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/cataloginventory_stock_item_update_by_schedule.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/cataloginventory_stock_item_update_by_schedule.php new file mode 100644 index 0000000000000..94e700a778d97 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/cataloginventory_stock_item_update_by_schedule.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogInventory\Model\Indexer\Stock\Processor; +use Magento\TestFramework\Helper\Bootstrap; + +/** * @var $indexerProcessor Processor */ +$indexerProcessor = Bootstrap::getObjectManager()->get(Processor::class); +$indexerProcessor->getIndexer()->setScheduled(true); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/cataloginventory_stock_item_update_by_schedule_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/cataloginventory_stock_item_update_by_schedule_rollback.php new file mode 100644 index 0000000000000..bd9a07529e8e3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/cataloginventory_stock_item_update_by_schedule_rollback.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogInventory\Model\Indexer\Stock\Processor; +use Magento\TestFramework\Helper\Bootstrap; + +/** * @var $indexerProcessor Processor */ +$indexerProcessor = Bootstrap::getObjectManager()->get(Processor::class); +$indexerProcessor->getIndexer()->setScheduled(false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php index ce182f56898ef..71ea03b1d362b 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php @@ -8,21 +8,49 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\ProductRepository; use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; use Magento\CatalogRule\Model\ResourceModel\Rule; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Api\SortOrder; +use Magento\Store\Api\WebsiteRepositoryInterface; use Magento\TestFramework\Helper\Bootstrap; class PriceTest extends \PHPUnit\Framework\TestCase { + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; /** * @var Rule */ private $resourceRule; + /** + * @var WebsiteRepositoryInterface + */ + private $websiteRepository; + + /** + * @var ProductRepository + */ + private $productRepository; + + /** + * @var IndexBuilder + */ + private $indexerBuilder; + + /** + * @inheritdoc + */ protected function setUp() { - $this->resourceRule = Bootstrap::getObjectManager()->get(Rule::class); + $this->objectManager = Bootstrap::getObjectManager(); + $this->resourceRule = $this->objectManager->get(Rule::class); + $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); + $this->productRepository = $this->objectManager->create(ProductRepository::class); + $this->indexerBuilder = $this->objectManager->get(IndexBuilder::class); } /** @@ -58,41 +86,23 @@ public function testPriceApplying() } /** - * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/simple_products.php - * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/catalog_rule_50_percent_off.php + * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off.php * @magentoDbIsolation enabled * @magentoAppIsolation enabled + * @return void */ - public function testPriceForSecondStore() + public function testPriceForSecondStore():void { - $customerGroupId = 1; - $websiteId = 2; - /** @var ProductRepository $productRepository */ - $productRepository = Bootstrap::getObjectManager()->create( - ProductRepository::class - ); - $simpleProduct = $productRepository->get('simple3'); + $websiteId = $this->websiteRepository->get('test')->getId(); + $simpleProduct = $this->productRepository->get('simple'); $simpleProduct->setPriceCalculation(true); - $this->assertEquals('simple3', $simpleProduct->getSku()); + $this->assertEquals('simple', $simpleProduct->getSku()); $this->assertFalse( - $this->resourceRule->getRulePrice( - new \DateTime(), - $websiteId, - $customerGroupId, - $simpleProduct->getId() - ) - ); - $indexerBuilder = Bootstrap::getObjectManager()->get( - \Magento\CatalogRule\Model\Indexer\IndexBuilder::class + $this->resourceRule->getRulePrice(new \DateTime(), $websiteId, 1, $simpleProduct->getId()) ); - $indexerBuilder->reindexById($simpleProduct->getId()); + $this->indexerBuilder->reindexById($simpleProduct->getId()); $this->assertEquals( - $this->resourceRule->getRulePrice( - new \DateTime(), - $websiteId, - $customerGroupId, - $simpleProduct->getId() - ), + $this->resourceRule->getRulePrice(new \DateTime(), $websiteId, 1, $simpleProduct->getId()), 25 ); } diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_50_percent_off.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_50_percent_off.php deleted file mode 100644 index ca5c8ecbbd59f..0000000000000 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_50_percent_off.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -use Magento\TestFramework\Helper\Bootstrap; - -/** - * Creates simple Catalog Rule with the following data: - * active, applied to all products, without time limits, with 50% off for all customers - */ -/** @var \Magento\CatalogRule\Model\Rule $rule */ -$catalogRule = Bootstrap::getObjectManager()->get(\Magento\CatalogRule\Model\RuleFactory::class)->create(); -$catalogRule->loadPost( - [ - 'name' => 'Test Catalog Rule 50% off', - 'is_active' => '1', - 'stop_rules_processing' => 0, - 'website_ids' => [2], - 'customer_group_ids' => [0, 1], - 'discount_amount' => 50, - 'simple_action' => 'by_percent', - 'from_date' => '', - 'to_date' => '', - 'sort_order' => 0, - 'sub_is_enable' => 0, - 'sub_discount_amount' => 0, - 'conditions' => [], - ] -); -$catalogRule->save(); -/** @var \Magento\CatalogRule\Model\Indexer\IndexBuilder $indexBuilder */ -$indexBuilder = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->get(\Magento\CatalogRule\Model\Indexer\IndexBuilder::class); -$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/delete_catalog_rule_data.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/delete_catalog_rule_data.php new file mode 100644 index 0000000000000..37121092d0ba0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/delete_catalog_rule_data.php @@ -0,0 +1,6 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/delete_catalog_rule_data_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/delete_catalog_rule_data_rollback.php new file mode 100644 index 0000000000000..77f0d1d3781f0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/delete_catalog_rule_data_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\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Api\Data\RuleInterface; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\CatalogRule\Model\ResourceModel\Rule\Product\Price; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CollectionFactory $catalogRuleCollectionFactory */ +$catalogRuleCollectionFactory = $objectManager->get(CollectionFactory::class); +/** @var CatalogRuleRepositoryInterface $catalogRuleRepository */ +$catalogRuleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var Price $catalogRuleProductPriceResource */ +$catalogRuleProductPriceResource = $objectManager->get(Price::class); +$catalogRuleCollection = $catalogRuleCollectionFactory->create(); +/** @var RuleInterface $catalogRule */ +foreach ($catalogRuleCollection->getItems() as $catalogRule) { + $catalogRuleRepository->delete($catalogRule); +} +$catalogRuleProductPriceResource->getConnection()->delete($catalogRuleProductPriceResource->getMainTable()); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_discount_value_not_logged_user.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_discount_value_not_logged_user.php new file mode 100644 index 0000000000000..55824abe28ee9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_discount_value_not_logged_user.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Api\Data\RuleInterface; +use Magento\CatalogRule\Api\Data\RuleInterfaceFactory; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\Customer\Model\Group; +use Magento\Store\Model\WebsiteRepository; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepository $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepository::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $catalogRuleRepository */ +$catalogRuleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var RuleInterfaceFactory $catalogRuleFactory */ +$catalogRuleFactory = $objectManager->get(RuleInterfaceFactory::class); +$catalogRule = $catalogRuleFactory->create( + [ + 'data' => [ + RuleInterface::IS_ACTIVE => 1, + RuleInterface::NAME => 'Rule adjust final price to discount value. Not logged user.', + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + RuleInterface::DISCOUNT_AMOUNT => 10, + 'website_ids' => [$baseWebsite->getId()], + RuleInterface::SIMPLE_ACTION => 'to_fixed', + RuleInterface::STOP_RULES_PROCESSING => false, + RuleInterface::SORT_ORDER => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + ] + ] +); +$catalogRuleRepository->save($catalogRule); +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_discount_value_not_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_discount_value_not_logged_user_rollback.php new file mode 100644 index 0000000000000..8b77787d40f14 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_discount_value_not_logged_user_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\CatalogRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->create(CatalogRuleRepositoryInterface::class); +/** @var CollectionFactory $ruleCollectionFactory */ +$ruleCollectionFactory = $objectManager->get(CollectionFactory::class); +$ruleCollection = $ruleCollectionFactory->create(); +$ruleCollection->addFieldToFilter('name', ['eq' => 'Rule adjust final price to discount value. Not logged user.']); +$ruleCollection->setPageSize(1); +/** @var Rule $rule */ +$rule = $ruleCollection->getFirstItem(); +if ($rule->getId()) { + $ruleRepository->delete($rule); +} +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_this_percentage_not_logged_user.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_this_percentage_not_logged_user.php new file mode 100644 index 0000000000000..233e231d7cac4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_this_percentage_not_logged_user.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Api\Data\RuleInterface; +use Magento\CatalogRule\Api\Data\RuleInterfaceFactory; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\Customer\Model\Group; +use Magento\Store\Model\WebsiteRepository; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepository $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepository::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $catalogRuleRepository */ +$catalogRuleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var RuleInterfaceFactory $catalogRuleFactory */ +$catalogRuleFactory = $objectManager->get(RuleInterfaceFactory::class); +$catalogRule = $catalogRuleFactory->create( + [ + 'data' => [ + RuleInterface::IS_ACTIVE => 1, + RuleInterface::NAME => 'Rule adjust final price to this percentage. Not logged user.', + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + RuleInterface::DISCOUNT_AMOUNT => 10, + 'website_ids' => [$baseWebsite->getId()], + RuleInterface::SIMPLE_ACTION => 'to_percent', + RuleInterface::STOP_RULES_PROCESSING => false, + RuleInterface::SORT_ORDER => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + ] + ] +); +$catalogRuleRepository->save($catalogRule); +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_this_percentage_not_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_this_percentage_not_logged_user_rollback.php new file mode 100644 index 0000000000000..5b1b6501019b8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_adjust_final_price_to_this_percentage_not_logged_user_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\CatalogRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->create(CatalogRuleRepositoryInterface::class); +/** @var CollectionFactory $ruleCollectionFactory */ +$ruleCollectionFactory = $objectManager->get(CollectionFactory::class); +$ruleCollection = $ruleCollectionFactory->create(); +$ruleCollection->addFieldToFilter('name', ['eq' => 'Rule adjust final price to this percentage. Not logged user.']); +$ruleCollection->setPageSize(1); +/** @var Rule $rule */ +$rule = $ruleCollection->getFirstItem(); +if ($rule->getId()) { + $ruleRepository->delete($rule); +} +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_fixed_amount_not_logged_user.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_fixed_amount_not_logged_user.php new file mode 100644 index 0000000000000..c37e74de0054c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_fixed_amount_not_logged_user.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Api\Data\RuleInterface; +use Magento\CatalogRule\Api\Data\RuleInterfaceFactory; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\Customer\Model\Group; +use Magento\Store\Model\WebsiteRepository; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepository $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepository::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $catalogRuleRepository */ +$catalogRuleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var RuleInterfaceFactory $catalogRuleFactory */ +$catalogRuleFactory = $objectManager->get(RuleInterfaceFactory::class); +$catalogRule = $catalogRuleFactory->create( + [ + 'data' => [ + RuleInterface::IS_ACTIVE => 1, + RuleInterface::NAME => 'Rule apply as fixed amount. Not logged user.', + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + RuleInterface::DISCOUNT_AMOUNT => 10, + 'website_ids' => [$baseWebsite->getId()], + RuleInterface::SIMPLE_ACTION => 'by_fixed', + RuleInterface::STOP_RULES_PROCESSING => false, + RuleInterface::SORT_ORDER => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + ] + ] +); +$catalogRuleRepository->save($catalogRule); +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_fixed_amount_not_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_fixed_amount_not_logged_user_rollback.php new file mode 100644 index 0000000000000..33b72dac08924 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_fixed_amount_not_logged_user_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\CatalogRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->create(CatalogRuleRepositoryInterface::class); +/** @var CollectionFactory $ruleCollectionFactory */ +$ruleCollectionFactory = $objectManager->get(CollectionFactory::class); +$ruleCollection = $ruleCollectionFactory->create(); +$ruleCollection->addFieldToFilter('name', ['eq' => 'Rule apply as fixed amount. Not logged user.']); +$ruleCollection->setPageSize(1); +/** @var Rule $rule */ +$rule = $ruleCollection->getFirstItem(); +if ($rule->getId()) { + $ruleRepository->delete($rule); +} +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user.php new file mode 100644 index 0000000000000..633a265241e33 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Api\Data\RuleInterface; +use Magento\CatalogRule\Api\Data\RuleInterfaceFactory; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\Customer\Model\Group; +use Magento\Store\Model\WebsiteRepository; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepository $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepository::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $catalogRuleRepository */ +$catalogRuleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var RuleInterfaceFactory $catalogRuleFactory */ +$catalogRuleFactory = $objectManager->get(RuleInterfaceFactory::class); +$catalogRule = $catalogRuleFactory->create( + [ + 'data' => [ + RuleInterface::IS_ACTIVE => 1, + RuleInterface::NAME => 'Rule apply as percentage of original. Not logged user.', + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + RuleInterface::DISCOUNT_AMOUNT => 10, + 'website_ids' => [$baseWebsite->getId()], + RuleInterface::SIMPLE_ACTION => 'by_percent', + RuleInterface::STOP_RULES_PROCESSING => false, + RuleInterface::SORT_ORDER => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + ] + ] +); +$catalogRuleRepository->save($catalogRule); +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user_rollback.php new file mode 100644 index 0000000000000..d9a1e61b7022e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\CatalogRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->create(CatalogRuleRepositoryInterface::class); +/** @var CollectionFactory $ruleCollectionFactory */ +$ruleCollectionFactory = $objectManager->get(CollectionFactory::class); +$ruleCollection = $ruleCollectionFactory->create(); +$ruleCollection->addFieldToFilter('name', ['eq' => 'Rule apply as percentage of original. Not logged user.']); +$ruleCollection->setPageSize(1); +/** @var Rule $rule */ +$rule = $ruleCollection->getFirstItem(); +if ($rule->getId()) { + $ruleRepository->delete($rule); +} +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_50_percent_off_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_by_category_ids_rollback.php similarity index 92% rename from dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_50_percent_off_rollback.php rename to dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_by_category_ids_rollback.php index 404bfd021492d..67584a300eacd 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/catalog_rule_50_percent_off_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/rule_by_category_ids_rollback.php @@ -9,10 +9,10 @@ /** @var \Magento\CatalogRule\Model\ResourceModel\Rule $catalogRuleResource */ $catalogRuleResource = $objectManager->create(\Magento\CatalogRule\Model\ResourceModel\Rule::class); -//Retrieve second rule by name +//Retrieve rule id by name $select = $catalogRuleResource->getConnection()->select(); $select->from($catalogRuleResource->getMainTable(), 'rule_id'); -$select->where('name = ?', 'Test Catalog Rule 50% off'); +$select->where('name = ?', 'test_category_rule'); $ruleId = $catalogRuleResource->getConnection()->fetchOne($select); try { @@ -22,7 +22,6 @@ } catch (\Exception $ex) { //Nothing to remove } - /** @var \Magento\CatalogRule\Model\Indexer\IndexBuilder $indexBuilder */ $indexBuilder = $objectManager->get(\Magento\CatalogRule\Model\Indexer\IndexBuilder::class); $indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off.php new file mode 100644 index 0000000000000..cad11e38ac8f3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\Rule; +use Magento\CatalogRule\Model\RuleFactory; +use Magento\Customer\Model\Group; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../Store/_files/second_website_with_two_stores.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CatalogRuleRepositoryInterface $catalogRuleRepository */ +$catalogRuleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var ProductInterfaceFactory $productFactory */ +$productFactory = $objectManager->get(ProductInterfaceFactory::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var RuleFactory $ruleFactory */ +$ruleFactory = $objectManager->get(RuleFactory::class); + +$secondWebsite = $websiteRepository->get('test'); +$product = $productFactory->create(); +$product->setTypeId('simple') + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$secondWebsite->getId()]) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice(50) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ] + ); +$productRepository->save($product); +/** @var Rule $rule */ +$catalogRule = $ruleFactory->create(); +$catalogRule->loadPost( + [ + 'name' => 'Test Catalog Rule 50% off', + 'is_active' => '1', + 'stop_rules_processing' => 0, + 'website_ids' => [$secondWebsite->getId()], + 'customer_group_ids' => [Group::NOT_LOGGED_IN_ID, 1], + 'discount_amount' => 50, + 'simple_action' => 'by_percent', + 'from_date' => '', + 'to_date' => '', + 'sort_order' => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + 'conditions' => [], + ] +); +$catalogRuleRepository->save($catalogRule); +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off_rollback.php new file mode 100644 index 0000000000000..a0df9b0daea9f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_product_with_catalog_rule_50_percent_off_rollback.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule; +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +try { + $productRepository->deleteById('simple'); +} catch (NoSuchEntityException $e) { + //already removed +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +/** @var Rule $catalogRuleResource */ +$catalogRuleResource = $objectManager->create(Rule::class); +//Retrieve rule by name +$select = $catalogRuleResource->getConnection() + ->select() + ->from($catalogRuleResource->getMainTable(), 'rule_id') + ->where('name = ?', 'Test Catalog Rule 50% off'); +$ruleId = $catalogRuleResource->getConnection()->fetchOne($select); + +try { + $ruleRepository->deleteById($ruleId); +} catch (CouldNotDeleteException $ex) { + //Nothing to remove +} + +$indexBuilder->reindexFull(); + +require __DIR__ . '/../../Store/_files/second_website_with_two_stores_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products.php index c40b641e58b1d..84ce4e1bca87c 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products.php @@ -26,14 +26,12 @@ ->setPrice(10) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) - ->setStockData( - [ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 1, - ] - ); + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ]); $productRepository->save($product); $productAction = $objectManager->get(\Magento\Catalog\Model\Product\Action::class); $productAction->updateAttributes([$product->getId()], ['test_attribute' => 'test_attribute_value'], $store->getId()); @@ -48,52 +46,10 @@ ->setPrice(9.9) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) - ->setStockData( - [ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 1, - ] - ); + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ]); $productRepository->save($product); -$store = $objectManager->create(\Magento\Store\Model\Store::class); -$store->load('second_store_view', 'code'); -/** - * @var Website $website - */ -$website2 = $objectManager->get(\Magento\Store\Model\Website::class); -$website2->load('second_website', 'code'); -if (!$website2->getId()) { - /** @var \Magento\Store\Model\Website $website */ - $website2->setData( - [ - 'code' => 'second_website', - 'name' => 'Second Website', - - ] - ); - - $website2->save(); -} -$product = $objectManager->create(\Magento\Catalog\Model\Product::class) - ->setTypeId('simple') - ->setId(3) - ->setAttributeSetId($attributeSetId) - ->setWebsiteIds([$website2->getId()]) - ->setName('Simple Product 3') - ->setSku('simple3') - ->setPrice(50) - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) - ->setStockData( - [ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 1, - ] - ); -$productRepository->save($product); -$productAction = $objectManager->get(\Magento\Catalog\Model\Product\Action::class); -$productAction->updateAttributes([$product->getId()], ['test_attribute' => 'test_attribute_value'], $store->getId()); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products_rollback.php index e641f9f32df40..6625b1926fc10 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products_rollback.php @@ -18,7 +18,7 @@ /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); -foreach (['simple1', 'simple2','simple3'] as $sku) { +foreach (['simple1', 'simple2'] as $sku) { try { $product = $productRepository->get($sku, false, null, true); $productRepository->delete($product); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/Model/Product/Type/Configurable/PriceTest.php b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/Model/Product/Type/Configurable/PriceTest.php new file mode 100644 index 0000000000000..1d8264522a2c4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/Model/Product/Type/Configurable/PriceTest.php @@ -0,0 +1,193 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogRuleConfigurable\Model\Product\Type\Configurable; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price; +use Magento\Customer\Model\Group; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Catalog\Model\Product\Price\GetPriceIndexDataByProductId; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Provides tests for configurable product pricing with catalog rules. + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + */ +class PriceTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var WebsiteRepositoryInterface + */ + private $websiteRepository; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var Price + */ + private $priceModel; + + /** + * @var GetPriceIndexDataByProductId + */ + private $getPriceIndexDataByProductId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->priceModel = $this->objectManager->create(Price::class); + $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->getPriceIndexDataByProductId = $this->objectManager->get(GetPriceIndexDataByProductId::class); + } + + /** + * @magentoDataFixture Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rule.php + * @return void + */ + public function testGetFinalPriceWithCustomOptionAndCatalogRule(): void + { + $indexPrices = [ + 'simple_10' => [ + 'price' => 10, + 'final_price' => 9, + 'min_price' => 9, + 'max_price' => 9, + 'tier_price' => null + ], + 'simple_20' => [ + 'price' => 20, + 'final_price' => 15, + 'min_price' => 15, + 'max_price' => 15, + 'tier_price' => 15 + ], + 'configurable' => [ + 'price' => 0, + 'final_price' => 0, + 'min_price' => 9, + 'max_price' => 30, + 'tier_price' => 15 + ], + ]; + $this->assertConfigurableProductPrice(20, 25, $indexPrices); + } + + /** + * @magentoDataFixture Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rules_for_children.php + * @return void + */ + public function testGetFinalPriceWithCustomOptionAndCatalogRulesForChildren(): void + { + $indexPrices = [ + 'simple_10' => [ + 'price' => 10, + 'final_price' => 4.5, + 'min_price' => 4.5, + 'max_price' => 9, + 'tier_price' => null + ], + 'simple_20' => [ + 'price' => 20, + 'final_price' => 8, + 'min_price' => 8, + 'max_price' => 15, + 'tier_price' => 15 + ], + 'configurable' => [ + 'price' => 0, + 'final_price' => 0, + 'min_price' => 4.5, + 'max_price' => 23, + 'tier_price' => 15 + ], + ]; + $this->assertConfigurableProductPrice(19.5, 23, $indexPrices); + } + + /** + * Asserts configurable product prices. + * + * @param float $priceWithFirstSimple + * @param float $priceWithSecondSimple + * @param array $indexPrices + * @return void + */ + private function assertConfigurableProductPrice( + float $priceWithFirstSimple, + float $priceWithSecondSimple, + array $indexPrices + ): void { + foreach ($indexPrices as $sku => $prices) { + $this->assertIndexTableData($sku, $prices); + } + $configurable = $this->productRepository->get('configurable'); + //Add tier price option + $optionId = $configurable->getOptions()[0]->getId(); + $configurable->addCustomOption(AbstractType::OPTION_PREFIX . $optionId, 'text'); + $configurable->addCustomOption('option_ids', $optionId); + //First simple rule price + Option price + $this->assertFinalPrice($configurable, $priceWithFirstSimple); + $configurable->addCustomOption('simple_product', 20, $this->productRepository->get('simple_20')); + //Second simple rule price + Option price + $this->assertFinalPrice($configurable, $priceWithSecondSimple); + } + + /** + * Asserts product final price. + * + * @param ProductInterface $product + * @param float $expectedPrice + * @return void + */ + private function assertFinalPrice(ProductInterface $product, float $expectedPrice): void + { + $this->assertEquals( + round($expectedPrice, 2), + round($this->priceModel->getFinalPrice(1, $product), 2) + ); + } + + /** + * Asserts price data in index table. + * + * @param string $sku + * @param array $expectedPrices + * @return void + */ + private function assertIndexTableData(string $sku, array $expectedPrices): void + { + $data = $this->getPriceIndexDataByProductId->execute( + (int)$this->productRepository->get($sku)->getId(), + Group::NOT_LOGGED_IN_ID, + (int)$this->websiteRepository->get('base')->getId() + ); + $data = reset($data); + foreach ($expectedPrices as $column => $price) { + $this->assertEquals($price, $data[$column]); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rule.php b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rule.php new file mode 100644 index 0000000000000..abdf785c447a3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rule.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Rule; +use Magento\CatalogRule\Model\Rule\Condition\Combine; +use Magento\CatalogRule\Model\Rule\Condition\Product; +use Magento\CatalogRule\Model\RuleFactory; +use Magento\Customer\Model\Group; +use Magento\Framework\App\Area; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price.php'; +Bootstrap::getInstance()->loadArea(Area::AREA_ADMINHTML); + +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var Rule $rule */ +$rule = $objectManager->get(RuleFactory::class)->create(); +$rule->loadPost( + [ + 'name' => 'Percent rule for configurable product', + 'is_active' => '1', + 'stop_rules_processing' => 0, + 'website_ids' => [$websiteRepository->get('base')->getId()], + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + 'discount_amount' => 50, + 'simple_action' => 'by_percent', + 'from_date' => '', + 'to_date' => '', + 'sort_order' => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + 'conditions' => [ + '1' => ['type' => Combine::class, 'aggregator' => 'all', 'value' => '1', 'new_child' => ''], + '1--1' => ['type' => Product::class, 'attribute' => 'sku', 'operator' => '==', 'value' => 'configurable'], + ], + ] +); +$ruleRepository->save($rule); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rule_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rule_rollback.php new file mode 100644 index 0000000000000..5b23d1918e394 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rule_rollback.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\CatalogRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ + . '/../../ConfigurableProduct/_files/' + . 'configurable_product_with_custom_option_and_simple_tier_price_rollback.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->create(CatalogRuleRepositoryInterface::class); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CollectionFactory $ruleCollectionFactory */ +$ruleCollectionFactory = $objectManager->get(CollectionFactory::class); +$ruleCollection = $ruleCollectionFactory->create() + ->addFieldToFilter('name', ['eq' => 'Percent rule for configurable product']) + ->setPageSize(1); +/** @var Rule $rule */ +$rule = $ruleCollection->getFirstItem(); +if ($rule->getId()) { + $ruleRepository->delete($rule); +} +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rules_for_children.php b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rules_for_children.php new file mode 100644 index 0000000000000..79a66c69ae618 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rules_for_children.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Rule; +use Magento\CatalogRule\Model\Rule\Condition\Combine; +use Magento\CatalogRule\Model\Rule\Condition\Product; +use Magento\CatalogRule\Model\RuleFactory; +use Magento\Customer\Model\Group; + +require __DIR__ . '/configurable_product_with_percent_rule.php'; + +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->get(CatalogRuleRepositoryInterface::class); +/** @var Rule $firstRule */ +$ruleFactory = $objectManager->get(RuleFactory::class); + +$firstRule = $ruleFactory->create(); +$firstRule->loadPost( + [ + 'name' => 'Percent rule for first simple product', + 'is_active' => '1', + 'stop_rules_processing' => 0, + 'website_ids' => [$websiteRepository->get('base')->getId()], + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + 'discount_amount' => 10, + 'simple_action' => 'by_percent', + 'from_date' => '', + 'to_date' => '', + 'sort_order' => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + 'conditions' => [ + '1' => ['type' => Combine::class, 'aggregator' => 'all', 'value' => '1', 'new_child' => ''], + '1--1' => ['type' => Product::class, 'attribute' => 'sku', 'operator' => '==', 'value' => 'simple_10'], + ], + ] +); +$ruleRepository->save($firstRule); + +$secondRule = $ruleFactory->create(); +$secondRule->loadPost( + [ + 'name' => 'Percent rule for second simple product', + 'is_active' => '1', + 'stop_rules_processing' => 0, + 'website_ids' => [$websiteRepository->get('base')->getId()], + 'customer_group_ids' => Group::NOT_LOGGED_IN_ID, + 'discount_amount' => 20, + 'simple_action' => 'by_percent', + 'from_date' => '', + 'to_date' => '', + 'sort_order' => 0, + 'sub_is_enable' => 0, + 'sub_discount_amount' => 0, + 'conditions' => [ + '1' => ['type' => Combine::class, 'aggregator' => 'all', 'value' => '1', 'new_child' => ''], + '1--1' => ['type' => Product::class, 'attribute' => 'sku', 'operator' => '==', 'value' => 'simple_20'], + ], + ] +); +$ruleRepository->save($secondRule); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rules_for_children_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rules_for_children_rollback.php new file mode 100644 index 0000000000000..773abe6236785 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRuleConfigurable/_files/configurable_product_with_percent_rules_for_children_rollback.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\CatalogRule\Api\CatalogRuleRepositoryInterface; +use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/configurable_product_with_percent_rule_rollback.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CatalogRuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->create(CatalogRuleRepositoryInterface::class); +/** @var IndexBuilder $indexBuilder */ +$indexBuilder = $objectManager->get(IndexBuilder::class); +/** @var CollectionFactory $ruleCollectionFactory */ +$ruleCollectionFactory = $objectManager->get(CollectionFactory::class); +$ruleCollection = $ruleCollectionFactory->create() + ->addFieldToFilter( + 'name', + [ + 'in' => [ + 'Percent rule for first simple product', + 'Percent rule for second simple product', + ] + ] + ); +foreach ($ruleCollection as $rule) { + $ruleRepository->delete($rule); +} +$indexBuilder->reindexFull(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/AttributeSearchWeightTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/AttributeSearchWeightTest.php index 4ca8e0b0726d4..bfde20950d105 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/AttributeSearchWeightTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/AttributeSearchWeightTest.php @@ -3,18 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\CatalogSearch\Model\Search; use Magento\Catalog\Api\ProductAttributeRepositoryInterface; -use Magento\Catalog\Model\Layer\Search as CatalogLayerSearch; -use Magento\Catalog\Model\Product; -use Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory; -use Magento\Framework\Search\Request\Builder; -use Magento\Framework\Search\Request\Config as RequestConfig; -use Magento\Search\Model\Search; +use Magento\TestFramework\Catalog\Model\Layer\QuickSearchByQuery; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\ObjectManager; use PHPUnit\Framework\TestCase; @@ -42,9 +36,9 @@ class AttributeSearchWeightTest extends TestCase private $collectedAttributesWeight = []; /** - * @var CatalogLayerSearch + * @var QuickSearchByQuery */ - private $catalogLayerSearch; + private $quickSearchByQuery; /** * @inheritdoc @@ -53,7 +47,7 @@ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); $this->productAttributeRepository = $this->objectManager->get(ProductAttributeRepositoryInterface::class); - $this->catalogLayerSearch = $this->objectManager->get(CatalogLayerSearch::class); + $this->quickSearchByQuery = $this->objectManager->get(QuickSearchByQuery::class); $this->collectCurrentProductAttributesWeights(); } @@ -85,9 +79,7 @@ public function testAttributeSearchWeight( array $expectedProductNames ): void { $this->updateAttributesWeight($attributeWeights); - $this->removeInstancesCache(); - $products = $this->findProducts($searchQuery); - $actualProductNames = $this->collectProductsName($products); + $actualProductNames = $this->quickSearchByQuery->execute($searchQuery)->getColumnValues('name'); $this->assertEquals($expectedProductNames, $actualProductNames, 'Products order is not as expected.'); } @@ -164,58 +156,11 @@ protected function updateAttributesWeight(array $attributeWeights): void { foreach ($attributeWeights as $attributeCode => $weight) { $attribute = $this->productAttributeRepository->get($attributeCode); - - if ($attribute) { - $attribute->setSearchWeight($weight); - $this->productAttributeRepository->save($attribute); - } + $attribute->setSearchWeight($weight); + $this->productAttributeRepository->save($attribute); } } - /** - * Get all names from founded products. - * - * @param Product[] $products - * @return array - */ - protected function collectProductsName(array $products): array - { - $result = []; - foreach ($products as $product) { - $result[] = $product->getName(); - } - - return $result; - } - - /** - * Reindex catalogsearch fulltext index. - * - * @return void - */ - protected function removeInstancesCache(): void - { - $this->objectManager->removeSharedInstance(RequestConfig::class); - $this->objectManager->removeSharedInstance(Builder::class); - $this->objectManager->removeSharedInstance(Search::class); - $this->objectManager->removeSharedInstance(CatalogLayerSearch::class); - } - - /** - * Find products by search query. - * - * @param string $query - * @return Product[] - */ - protected function findProducts(string $query): array - { - $testProductCollection = $this->catalogLayerSearch->getProductCollection(); - $testProductCollection->addSearchFilter($query); - $testProductCollection->setOrder('relevance', 'desc'); - - return $testProductCollection->getItems(); - } - /** * Collect weight of attributes which use in test. * diff --git a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php index 85cd5331a29c4..f11083dd2ba91 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php @@ -6,27 +6,39 @@ namespace Magento\CatalogWidget\Block\Product; +use Magento\Catalog\Model\Indexer\Product\Eav\Processor; +use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + /** * Tests for @see \Magento\CatalogWidget\Block\Product\ProductsList */ -class ProductListTest extends \PHPUnit\Framework\TestCase +class ProductListTest extends TestCase { /** - * @var \Magento\CatalogWidget\Block\Product\ProductsList + * @var ProductsList */ protected $block; /** - * @var \Magento\Framework\ObjectManagerInterface + * @var CategoryCollection; + + */ + private $categoryCollection; + + /** + * @var ObjectManagerInterface */ protected $objectManager; protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->block = $this->objectManager->create( - \Magento\CatalogWidget\Block\Product\ProductsList::class - ); + $this->objectManager = Bootstrap::getObjectManager(); + $this->block = $this->objectManager->create(ProductsList::class); + $this->categoryCollection = $this->objectManager->create(CategoryCollection::class); } /** @@ -44,16 +56,16 @@ protected function setUp() public function testCreateCollection() { // Reindex EAV attributes to enable products filtration by created multiselect attribute - /** @var \Magento\Catalog\Model\Indexer\Product\Eav\Processor $eavIndexerProcessor */ + /** @var Processor $eavIndexerProcessor */ $eavIndexerProcessor = $this->objectManager->get( - \Magento\Catalog\Model\Indexer\Product\Eav\Processor::class + Processor::class ); $eavIndexerProcessor->reindexAll(); // Prepare conditions - /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ - $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + /** @var $attribute Attribute */ + $attribute = Bootstrap::getObjectManager()->create( + Attribute::class ); $attribute->load('multiselect_attribute', 'attribute_code'); $multiselectAttributeOptionIds = []; @@ -87,9 +99,9 @@ public function testCreateCollection() */ public function testCreateCollectionWithDropdownAttribute() { - /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ - $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + /** @var $attribute Attribute */ + $attribute = Bootstrap::getObjectManager()->create( + Attribute::class ); $attribute->load('dropdown_attribute', 'attribute_code'); $dropdownAttributeOptionIds = []; @@ -119,6 +131,7 @@ public function testCreateCollectionWithDropdownAttribute() * * @param int $count * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ private function performAssertions(int $count) { @@ -142,6 +155,7 @@ private function performAssertions(int $count) * @param string $encodedConditions * @param string $sku * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ public function testCreateCollectionForSku($encodedConditions, $sku) { @@ -179,6 +193,7 @@ public function createCollectionForSkuDataProvider() * @magentoDbIsolation disabled * @magentoDataFixture Magento/Catalog/_files/product_simple_with_date_attribute.php * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ public function testProductListWithDateAttribute() { @@ -197,4 +212,48 @@ public function testProductListWithDateAttribute() "Product collection was not filtered according to the widget condition." ); } + + /** + * Make sure CatalogWidget would display anchor category products recursively from children categories. + * + * 1. Create an anchor root category and a sub category inside it + * 2. Create 2 new products and assign them to the sub categories + * 3. Create product list widget condition to display products from the anchor root category + * 4. Load collection for product list widget and make sure that number of loaded products is correct + * + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/Catalog/_files/product_in_nested_anchor_categories.php + */ + public function testCreateAnchorCollection() + { + // Reindex EAV attributes to enable products filtration by created multiselect attribute + /** @var Processor $eavIndexerProcessor */ + $eavIndexerProcessor = $this->objectManager->get( + Processor::class + ); + $eavIndexerProcessor->reindexAll(); + + $this->categoryCollection->addNameToResult()->load(); + $rootCategoryId = $this + ->categoryCollection + ->getItemByColumnValue('name', 'Default Category') + ->getId(); + + $encodedConditions = '^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`, + `aggregator`:`all`,`value`:`1`,`new_child`:``^], + `1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`, + `attribute`:`category_ids`, + `operator`:`==`,`value`:`' . $rootCategoryId . '`^]^]'; + + $this->block->setData('conditions_encoded', $encodedConditions); + + $productCollection = $this->block->createCollection(); + $productCollection->load(); + + $this->assertEquals( + 2, + $productCollection->count(), + "Anchor root category does not contain products of it's children." + ); + } } diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php index 1fc07d32c77b9..c135a89a00bc7 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php @@ -75,22 +75,47 @@ protected function setUp() * Execute method with correct directory path and file name to check that files under WYSIWYG media directory * can be removed. * + * @param string $filename * @return void + * @dataProvider executeDataProvider */ - public function testExecute() + public function testExecute(string $filename) { + $filePath = $this->fullDirectoryPath . DIRECTORY_SEPARATOR . $filename; + $fixtureDir = realpath(__DIR__ . '/../../../../../Catalog/_files'); + copy($fixtureDir . '/' . $this->fileName, $filePath); + $this->model->getRequest()->setMethod('POST') - ->setPostValue('files', [$this->imagesHelper->idEncode($this->fileName)]); + ->setPostValue('files', [$this->imagesHelper->idEncode($filename)]); $this->model->getStorage()->getSession()->setCurrentPath($this->fullDirectoryPath); $this->model->execute(); $this->assertFalse( $this->mediaDirectory->isExist( - $this->mediaDirectory->getRelativePath($this->fullDirectoryPath . '/' . $this->fileName) + $this->mediaDirectory->getRelativePath($this->fullDirectoryPath . '/' . $filename) ) ); } + /** + * DataProvider for testExecute + * + * @return array + */ + public function executeDataProvider(): array + { + return [ + ['name with spaces.jpg'], + ['name with, comma.jpg'], + ['name with* asterisk.jpg'], + ['name with[ bracket.jpg'], + ['magento_small_image.jpg'], + ['_.jpg'], + [' - .jpg'], + ['-.jpg'], + ]; + } + /** * Check that htaccess file couldn't be removed via * \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images\DeleteFiles::execute method diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/StorageTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/StorageTest.php index 5d256f2234a53..c77646ca5be1c 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/StorageTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/StorageTest.php @@ -9,9 +9,12 @@ use Magento\Framework\App\Filesystem\DirectoryList; /** + * Test methods of class Storage * * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyPublicMethods) */ class StorageTest extends \PHPUnit\Framework\TestCase { @@ -260,4 +263,87 @@ public function testUploadFileWithWrongFile(): void $this->assertFalse(is_file(self::$_baseDir . DIRECTORY_SEPARATOR . $fileName)); // phpcs:enable } + + /** + * Test that getThumbnailUrl() returns correct URL for root folder or sub-folders images + * + * @param string $directory + * @param string $filename + * @param string $expectedUrl + * @return void + * @magentoAppIsolation enabled + * @magentoAppArea adminhtml + * @dataProvider getThumbnailUrlDataProvider + */ + public function testGetThumbnailUrl(string $directory, string $filename, string $expectedUrl): void + { + $root = $this->storage->getCmsWysiwygImages()->getStorageRoot(); + $directory = implode('/', array_filter([rtrim($root, '/'), trim($directory, '/')])); + $path = $directory . '/' . $filename; + $this->generateImage($path); + $this->storage->resizeFile($path); + $collection = $this->storage->getFilesCollection($directory, 'image'); + $paths = []; + foreach ($collection as $item) { + $paths[] = parse_url($item->getThumbUrl(), PHP_URL_PATH); + } + $this->assertEquals([$expectedUrl], $paths); + $this->storage->deleteFile($path); + } + + /** + * Provide scenarios for testing getThumbnailUrl() + * + * @return array + */ + public function getThumbnailUrlDataProvider(): array + { + return [ + [ + '/', + 'image1.png', + '/pub/media/.thumbs/image1.png' + ], + [ + '/cms', + 'image2.png', + '/pub/media/.thumbscms/image2.png' + ], + [ + '/cms/pages', + 'image3.png', + '/pub/media/.thumbscms/pages/image3.png' + ] + ]; + } + + /** + * Generate a dummy image of the given width and height. + * + * @param string $path + * @param int $width + * @param int $height + * @return string + */ + private function generateImage(string $path, int $width = 1024, int $height = 768) + { + $dir = dirname($path); + if (!file_exists($dir)) { + mkdir($dir, 0777, true); + } + $file = fopen($path, 'wb'); + $filename = basename($path); + ob_start(); + $image = imagecreatetruecolor($width, $height); + switch (substr($filename, strrpos($filename, '.'))) { + case '.jpeg': + imagejpeg($image); + break; + case '.png': + imagepng($image); + break; + } + fwrite($file, ob_get_clean()); + return $path; + } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/CustomOptions/RenderOptionsTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/CustomOptions/RenderOptionsTest.php new file mode 100644 index 0000000000000..55f8b91f07093 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/CustomOptions/RenderOptionsTest.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Block\Product\View\CustomOptions; + +use Magento\Catalog\Block\Product\View\Options\AbstractRenderCustomOptionsTest; + +/** + * Test cases related to check that configurable product custom option renders as expected. + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + */ +class RenderOptionsTest extends AbstractRenderCustomOptionsTest +{ + /** + * Check that options from text group(field, area) render on configurable product as expected. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @dataProvider \Magento\TestFramework\ConfigurableProduct\Block\CustomOptions\TextGroupDataProvider::getData + * + * @param array $optionData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromTextGroup(array $optionData, array $checkArray): void + { + $this->assertTextOptionRenderingOnProduct('Configurable product', $optionData, $checkArray); + } + + /** + * Check that options from file group(file) render on configurable product as expected. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @dataProvider \Magento\TestFramework\ConfigurableProduct\Block\CustomOptions\FileGroupDataProvider::getData + * + * @param array $optionData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromFileGroup(array $optionData, array $checkArray): void + { + $this->assertFileOptionRenderingOnProduct('Configurable product', $optionData, $checkArray); + } + + /** + * Check that options from select group(drop-down, radio buttons, checkbox, multiple select) render + * on configurable product as expected. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @dataProvider \Magento\TestFramework\ConfigurableProduct\Block\CustomOptions\SelectGroupDataProvider::getData + * + * @param array $optionData + * @param array $optionValueData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromSelectGroup( + array $optionData, + array $optionValueData, + array $checkArray + ): void { + $this->assertSelectOptionRenderingOnProduct('Configurable product', $optionData, $optionValueData, $checkArray); + } + + /** + * Check that options from date group(date, date & time, time) render on configurable product as expected. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @dataProvider \Magento\TestFramework\ConfigurableProduct\Block\CustomOptions\DateGroupDataProvider::getData + * + * @param array $optionData + * @param array $checkArray + * @return void + */ + public function testRenderCustomOptionsFromDateGroup(array $optionData, array $checkArray): void + { + $this->assertDateOptionRenderingOnProduct('Configurable product', $optionData, $checkArray); + } + + /** + * @inheritdoc + */ + protected function getHandlesList(): array + { + return [ + 'default', + 'catalog_product_view', + 'catalog_product_view_type_configurable', + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableProductPriceTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableProductPriceTest.php new file mode 100644 index 0000000000000..977a130eff838 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableProductPriceTest.php @@ -0,0 +1,211 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Block\Product\View\Type; + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Check configurable product price displaying + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoAppArea frontend + */ +class ConfigurableProductPriceTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var Page */ + private $page; + + /** @var ProductCustomOptionInterface */ + private $productCustomOption; + + /** @var SerializerInterface */ + private $json; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->page = $this->objectManager->get(Page::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->productCustomOption = $this->objectManager->get(ProductCustomOptionInterface::class); + $this->json = $this->objectManager->get(SerializerInterface::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->registry->unregister('product'); + $this->registry->unregister('current_product'); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * + * @return void + */ + public function testConfigurablePrice(): void + { + $this->assertPrice($this->processPriceView('configurable'), 10.00); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_disable_first_child.php + * + * @return void + */ + public function testConfigurablePriceWithDisabledFirstChild(): void + { + $this->assertPrice($this->processPriceView('configurable'), 20.00); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_zero_qty_first_child.php + * + * @return void + */ + public function testConfigurablePriceWithOutOfStockFirstChild(): void + { + $this->assertPrice($this->processPriceView('configurable'), 20.00); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoDataFixture Magento/CatalogRule/_files/rule_apply_as_percentage_of_original_not_logged_user.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testConfigurablePriceWithCatalogRule(): void + { + $this->assertPrice($this->processPriceView('configurable'), 9.00); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text.php + * + * @return void + */ + public function testConfigurablePriceWithCustomOption(): void + { + $product = $this->productRepository->get('configurable'); + $this->registerProduct($product); + $this->preparePageLayout(); + $customOptionsBlock = $this->page->getLayout() + ->getChildBlock('product.info.options.wrapper', 'product_options'); + $option = $product->getOptions()[0] ?? null; + $this->assertNotNull($option); + $this->assertJsonConfig($customOptionsBlock->getJsonConfig(), '15', (int)$option->getId()); + $optionBlock = $customOptionsBlock->getChildBlock($this->productCustomOption->getGroupByType('area')); + $optionPrice = $optionBlock->setProduct($product)->setOption($option)->getFormattedPrice(); + $this->assertEquals('+$15.00', preg_replace('/[\n\s]/', '', strip_tags($optionPrice))); + } + + /** + * Register the product. + * + * @param ProductInterface $product + * @return void + */ + private function registerProduct(ProductInterface $product): void + { + $this->registry->unregister('product'); + $this->registry->register('product', $product); + $this->registry->unregister('current_product'); + $this->registry->register('current_product', $product); + } + + /** + * Prepare configurable product page. + * + * @return void + */ + private function preparePageLayout(): void + { + $this->page->addHandle([ + 'default', + 'catalog_product_view', + 'catalog_product_view_type_configurable', + ]); + $this->page->getLayout()->generateXml(); + } + + /** + * Process view product final price block html. + * + * @param string $sku + * @return string + */ + private function processPriceView(string $sku): string + { + $product = $this->productRepository->get($sku); + $this->registerProduct($product); + $this->preparePageLayout(); + + return $this->page->getLayout()->getBlock('product.price.final')->toHtml(); + } + + /** + * Assert that html contain price label and expected final price amount. + * + * @param string $priceBlockHtml + * @param float $expectedPrice + * @return void + */ + private function assertPrice(string $priceBlockHtml, float $expectedPrice): void + { + $regexp = '/<span class="price-label">As low as<\/span>.*'; + $regexp .= '<span.*data-price-amount="%s".*<span class="price">\$%.2f<\/span><\/span>/'; + $this->assertRegExp( + sprintf($regexp, round($expectedPrice, 2), $expectedPrice), + preg_replace('/[\n\r]/', '', $priceBlockHtml) + ); + } + + /** + * Assert custom option price json config. + * + * @param string $config + * @param string $expectedPrice + * @param int $optionId + * @return void + */ + private function assertJsonConfig(string $config, string $expectedPrice, int $optionId): void + { + $price = $this->json->unserialize($config)[$optionId]['prices']['finalPrice']['amount'] ?? null; + $this->assertNotNull($price); + $this->assertEquals($expectedPrice, $price); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php index feca63015ca7c..ee1f4d25e88da 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableTest.php @@ -3,79 +3,120 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Block\Product\View\Type; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + /** - * Test class for \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable. + * Test class to check configurable product view behaviour. + * + * @see \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable * * @magentoAppIsolation enabled + * @magentoDbIsolation enabled * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php */ -class ConfigurableTest extends \PHPUnit\Framework\TestCase +class ConfigurableTest extends TestCase { /** - * @var \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var SearchCriteriaBuilder + */ + private $searchBuilder; + + /** + * @var Configurable + */ + private $block; + + /** + * @var ProductRepositoryInterface */ - protected $_block; + private $productRepository; /** - * @var \Magento\Catalog\Model\Product + * @var ProductResource */ - protected $_product; + private $productResource; + /** + * @var ProductInterface + */ + private $product; + + /** + * @inheritdoc + */ protected function setUp() { - $this->_product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Product::class - ); - $this->_product->load(1); - $this->_block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\View\LayoutInterface::class - )->createBlock( - \Magento\ConfigurableProduct\Block\Product\View\Type\Configurable::class - ); - $this->_block->setProduct($this->_product); + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->serializer = $this->objectManager->get(SerializerInterface::class); + $this->searchBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->productResource = $this->objectManager->create(ProductResource::class); + $this->product = $this->productRepository->get('configurable'); + $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(Configurable::class); + $this->block->setProduct($this->product); } /** - * @magentoAppIsolation enabled + * @return void */ - public function testGetAllowAttributes() + public function testGetAllowAttributes(): void { - $attributes = $this->_block->getAllowAttributes(); - $this->assertInstanceOf( - \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class, - $attributes - ); + $attributes = $this->block->getAllowAttributes(); + $this->assertInstanceOf(Collection::class, $attributes); $this->assertGreaterThanOrEqual(1, $attributes->getSize()); } /** - * @magentoAppIsolation enabled + * @return void */ - public function testHasOptions() + public function testHasOptions(): void { - $this->assertTrue($this->_block->hasOptions()); + $this->assertTrue($this->block->hasOptions()); } /** - * @magentoAppIsolation enabled + * @return void */ - public function testGetAllowProducts() + public function testGetAllowProducts(): void { - $products = $this->_block->getAllowProducts(); + $products = $this->block->getAllowProducts(); $this->assertGreaterThanOrEqual(2, count($products)); foreach ($products as $product) { - $this->assertInstanceOf(\Magento\Catalog\Model\Product::class, $product); + $this->assertInstanceOf(ProductInterface::class, $product); } } /** - * @magentoAppIsolation enabled + * @return void */ - public function testGetJsonConfig() + public function testGetJsonConfig(): void { - $config = json_decode($this->_block->getJsonConfig(), true); + $config = $this->serializer->unserialize($this->block->getJsonConfig()); $this->assertNotEmpty($config); $this->assertArrayHasKey('productId', $config); $this->assertEquals(1, $config['productId']); @@ -83,5 +124,115 @@ public function testGetJsonConfig() $this->assertArrayHasKey('template', $config); $this->assertArrayHasKey('prices', $config); $this->assertArrayHasKey('basePrice', $config['prices']); + $this->assertArrayHasKey('images', $config); + $this->assertCount(0, $config['images']); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_child_products_with_images.php + * @return void + */ + public function testGetJsonConfigWithChildProductsImages(): void + { + $config = $this->serializer->unserialize($this->block->getJsonConfig()); + $this->assertNotEmpty($config); + $this->assertArrayHasKey('images', $config); + $this->assertCount(2, $config['images']); + $products = $this->getProducts( + $this->product->getExtensionAttributes()->getConfigurableProductLinks() + ); + $i = 0; + foreach ($products as $simpleProduct) { + $i++; + $resultImage = reset($config['images'][$simpleProduct->getId()]); + $this->assertContains($simpleProduct->getImage(), $resultImage['thumb']); + $this->assertContains($simpleProduct->getImage(), $resultImage['img']); + $this->assertContains($simpleProduct->getImage(), $resultImage['full']); + $this->assertTrue($resultImage['isMain']); + $this->assertEquals('image', $resultImage['type']); + $this->assertEquals($i, $resultImage['position']); + $this->assertNull($resultImage['videoUrl']); + } + } + + /** + * @dataProvider expectedDataProvider + * + * @param string $label + * @param array $expectedConfig + * @return void + */ + public function testConfigurableProductView(string $label, array $expectedConfig): void + { + $attributes = $this->block->decorateArray($this->block->getAllowAttributes()); + $this->assertCount(1, $attributes); + $attribute = $attributes->getFirstItem(); + $this->assertEquals($label, $attribute->getLabel()); + $config = $this->serializer->unserialize($this->block->getJsonConfig())['attributes'] ?? null; + $this->assertNotNull($config); + $this->assertConfig(reset($config), $expectedConfig); + } + + /** + * @return array + */ + public function expectedDataProvider(): array + { + return [ + [ + 'label' => 'Test Configurable', + 'config_data' => [ + 'label' => 'Test Configurable', + 'options' => [ + [ + 'label' => 'Option 1', + 'sku' => 'simple_10', + ], + [ + 'label' => 'Option 2', + 'sku' => 'simple_20', + ], + ], + ], + ], + ]; + } + + /** + * Assert that data was generated + * + * @param array $data + * @param array $expectedData + * @return void + */ + private function assertConfig(array $data, array $expectedData): void + { + $this->assertEquals($expectedData['label'], $data['label']); + $skus = array_column($expectedData['options'], 'sku'); + $idBySkuMap = $this->productResource->getProductsIdsBySkus($skus); + foreach ($expectedData['options'] as &$option) { + $sku = $option['sku']; + unset($option['sku']); + $option['products'] = [$idBySkuMap[$sku]]; + foreach ($data['options'] as $actualOption) { + if ($option['label'] === $actualOption['label']) { + unset($actualOption['id']); + $this->assertEquals($option, $actualOption); + } + } + } + } + + /** + * Returns products by ids list. + * + * @param array $productIds + * @return ProductInterface[] + */ + private function getProducts(array $productIds): array + { + $criteria = $this->searchBuilder->addFilter('entity_id', $productIds, 'in') + ->create(); + return $this->productRepository->getList($criteria)->getItems(); } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php new file mode 100644 index 0000000000000..94aa958b44c26 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Block\Product\View\Type; + +use Magento\Catalog\Block\Product\ListProduct; +use Magento\Eav\Model\Entity\Collection\AbstractCollection; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class checks configurable product displaying on category view page + * + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @magentoAppArea frontend + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_out_of_stock_children.php + */ +class ConfigurableViewOnCategoryPageTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var LayoutInterface */ + private $layout; + + /** @var ListProduct $listingBlock */ + private $listingBlock; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->listingBlock = $this->layout->createBlock(ListProduct::class); + $this->listingBlock->setCategoryId(333); + } + + /** + * @magentoConfigFixture current_store cataloginventory/options/show_out_of_stock 1 + * + * @return void + */ + public function testOutOfStockProductWithEnabledConfigView(): void + { + $collection = $this->listingBlock->getLoadedProductCollection(); + $this->assertCollectionSize(1, $collection); + } + + /** + * @magentoConfigFixture current_store cataloginventory/options/show_out_of_stock 0 + * + * @return void + */ + public function testOutOfStockProductWithDisabledConfigView(): void + { + $collection = $this->listingBlock->getLoadedProductCollection(); + $this->assertCollectionSize(0, $collection); + } + + /** + * Check collection size + * + * @param int $expectedSize + * @param AbstractCollection $collection + * @return void + */ + private function assertCollectionSize(int $expectedSize, AbstractCollection $collection): void + { + $this->assertEquals($expectedSize, $collection->getSize()); + $this->assertCount($expectedSize, $collection->getItems()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnProductPageTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnProductPageTest.php new file mode 100644 index 0000000000000..21ba9d2764b91 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnProductPageTest.php @@ -0,0 +1,290 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Block\Product\View\Type; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\CatalogInventory\Api\Data\StockStatusInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class checks configurable product view with out of stock children + * + * @magentoAppArea frontend + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ConfigurableViewOnProductPageTest extends TestCase +{ + private const STOCK_DISPLAY_TEMPLATE = 'Magento_Catalog::product/view/type/default.phtml'; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var LayoutInterface */ + private $layout; + + /** @var Configurable */ + private $block; + + /** @var SerializerInterface */ + private $json; + + /** @var ProductResource */ + private $productResource; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->block = $this->layout->createBlock(Configurable::class); + $this->json = $this->objectManager->get(SerializerInterface::class); + $this->productResource = $this->objectManager->get(ProductResource::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + } + + /** + * @dataProvider oneChildNotVisibleDataProvider + * @magentoDbIsolation disabled + * + * @param string $sku + * @param array $data + * @param array $expectedData + * @return void + */ + public function testOneChildNotVisible(string $sku, array $data, array $expectedData): void + { + $configurableProduct = $this->prepareConfigurableProduct($sku, $data); + $result = $this->renderStockBlock($configurableProduct); + $this->performAsserts($result, $expectedData); + } + + /** + * @return array + */ + public function oneChildNotVisibleDataProvider(): array + { + return [ + 'one_child_out_of_stock' => [ + 'sku' => 'simple_10', + 'data' => [ + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'is_in_stock' => StockStatusInterface::STATUS_OUT_OF_STOCK, + ], + ], + 'expected_data' => [ + 'stock_status' => 'In stock', + 'options' => [ + [ + 'label' => 'Option 2', + 'product' => 'simple_20', + ], + ], + ], + ], + 'one_child_disabled' => [ + 'sku' => 'simple_10', + 'data' => [ + 'status' => Status::STATUS_DISABLED, + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'is_in_stock' => StockStatusInterface::STATUS_IN_STOCK, + ], + ], + 'expected_data' => [ + 'stock_status' => 'In stock', + 'options' => [ + [ + 'label' => 'Option 2', + 'product' => 'simple_20', + ], + ], + ], + ], + ]; + } + + /** + * @magentoConfigFixture current_store cataloginventory/options/show_out_of_stock 1 + * + * @dataProvider oneChildNotVisibleDataProviderWithEnabledConfig + * + * @param string $sku + * @param array $data + * @param array $expectedData + * @return void + */ + public function testOneChildNotVisibleWithEnabledShowOutOfStockProducts( + string $sku, + array $data, + array $expectedData + ): void { + $configurableProduct = $this->prepareConfigurableProduct($sku, $data); + $result = $this->renderStockBlock($configurableProduct); + $this->performAsserts($result, $expectedData); + } + + /** + * @return array + */ + public function oneChildNotVisibleDataProviderWithEnabledConfig(): array + { + return [ + 'one_child_out_of_stock' => [ + 'sku' => 'simple_10', + 'data' => [ + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'is_in_stock' => StockStatusInterface::STATUS_OUT_OF_STOCK, + ], + ], + 'expected_data' => [ + 'stock_status' => 'In stock', + 'options' => [ + [ + 'label' => 'Option 2', + 'product' => 'simple_20' + ], + [ + 'label' => 'Option 1', + 'product' => 'simple_10', + ], + ], + ], + ], + 'one_child_disabled' => [ + 'sku' => 'simple_10', + 'data' => [ + 'status' => Status::STATUS_DISABLED, + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'is_in_stock' => StockStatusInterface::STATUS_IN_STOCK, + ], + ], + 'expected_data' => [ + 'stock_status' => 'In stock', + 'options' => [ + [ + 'label' => 'Option 2', + 'product' => 'simple_20', + ], + ], + ], + ], + ]; + } + + /** + * Update product with data + * + * @param array $sku + * @param array $data + * @return void + */ + private function updateProduct(string $sku, array $data): void + { + $currentStore = $this->storeManager->getStore(); + try { + $this->storeManager->setCurrentStore(Store::DEFAULT_STORE_ID); + $product = $this->productRepository->get($sku); + $product->addData($data); + $this->productRepository->save($product); + } finally { + $this->storeManager->setCurrentStore($currentStore); + } + } + + /** + * Check attribute options + * + * @param array $actualData + * @param array $expectedData + * @return void + */ + private function assertConfig(array $actualData, array $expectedData): void + { + $this->assertCount(count($expectedData), $actualData['options_data'], 'Redundant options were loaded'); + $sku = array_column($expectedData, 'product'); + $idBySkuMapping = $this->productResource->getProductsIdsBySkus($sku); + foreach ($expectedData as $expectedOption) { + $expectedId = $idBySkuMapping[$expectedOption['product']]; + $itemToCheck = $actualData['options_data'][$expectedId] ?? null; + $this->assertNotNull($itemToCheck); + foreach ($actualData['attributes']['options'] as $actualAttributeDataItem) { + if ($actualAttributeDataItem['id'] === reset($itemToCheck)) { + $this->assertEquals($expectedOption['label'], $actualAttributeDataItem['label']); + } + } + } + } + + /** + * Render stock block + * + * @param ProductInterface $configurableProduct + * @return string + */ + private function renderStockBlock(ProductInterface $configurableProduct): string + { + $this->block->setProduct($configurableProduct); + $this->block->setTemplate(self::STOCK_DISPLAY_TEMPLATE); + + return $this->block->toHtml(); + } + + /** + * Perform test asserts + * + * @param string $result + * @param array $expectedData + * @return void + */ + private function performAsserts(string $result, array $expectedData): void + { + $this->assertEquals((string)__($expectedData['stock_status']), trim(strip_tags($result))); + $config = $this->json->unserialize($this->block->getJsonConfig()); + $dataToCheck = ['attributes' => reset($config['attributes']), 'options_data' => $config['index']]; + $this->assertConfig($dataToCheck, $expectedData['options']); + } + + /** + * Prepare configurable product with children to test + * + * @param string $sku + * @param array $data + * @return ProductInterface + */ + private function prepareConfigurableProduct(string $sku, array $data): ProductInterface + { + $this->updateProduct($sku, $data); + + return $this->productRepository->get('configurable', false, null, true); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/MultiStoreConfigurableViewOnProductPageTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/MultiStoreConfigurableViewOnProductPageTest.php new file mode 100644 index 0000000000000..c1adf0ef1d2be --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/MultiStoreConfigurableViewOnProductPageTest.php @@ -0,0 +1,255 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Block\Product\View\Type; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Store\ExecuteInStoreContext; +use PHPUnit\Framework\TestCase; + +/** + * Class check configurable product options displaying per stores + * + * @magentoDbIsolation disabled + */ +class MultiStoreConfigurableViewOnProductPageTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** @var LayoutInterface */ + private $layout; + + /** @var SerializerInterface */ + private $serializer; + + /** @var ProductResource */ + private $productResource; + + /** @var ExecuteInStoreContext */ + private $executeInStoreContext; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->serializer = $this->objectManager->get(SerializerInterface::class); + $this->productResource = $this->objectManager->get(ProductResource::class); + $this->executeInStoreContext = $this->objectManager->get(ExecuteInStoreContext::class); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores.php + * + * @dataProvider expectedLabelsDataProvider + * + * @param array $expectedStoreData + * @param array $expectedSecondStoreData + * @return void + */ + public function testMultiStoreLabelView(array $expectedStoreData, array $expectedSecondStoreData): void + { + $this->executeInStoreContext->execute('default', [$this, 'assertProductLabel'], $expectedStoreData); + $this->executeInStoreContext->execute('fixturestore', [$this, 'assertProductLabel'], $expectedSecondStoreData); + } + + /** + * @return array + */ + public function expectedLabelsDataProvider(): array + { + return [ + [ + 'options_first_store' => [ + 'simple_option_1_default_store' => [ + 'label' => 'Option 1 Default Store', + ], + 'simple_option_2_default_store' => [ + 'label' => 'Option 2 Default Store', + ], + 'simple_option_3_default_store' => [ + 'label' => 'Option 3 Default Store', + ], + ], + 'options_second_store' => [ + 'simple_option_1_default_store' => [ + 'label' => 'Option 1 Second Store', + ], + 'simple_option_2_default_store' => [ + 'label' => 'Option 2 Second Store', + ], + 'simple_option_3_default_store' => [ + 'label' => 'Option 3 Second Store', + ], + ], + ], + ]; + } + + /** + * Assert configurable product labels config + * + * @param $expectedStoreData + * @return void + */ + public function assertProductLabel($expectedStoreData): void + { + $product = $this->productRepository->get('configurable', false, null, true); + $config = $this->getBlockConfig($product)['attributes'] ?? null; + $this->assertNotNull($config); + $this->assertAttributeConfig($expectedStoreData, reset($config)); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_two_websites.php + * + * @dataProvider expectedProductDataProvider + * + * @param array $expectedProducts + * @param array $expectedSecondStoreProducts + * @return void + */ + public function testMultiStoreOptionsView(array $expectedProducts, array $expectedSecondStoreProducts): void + { + $this->prepareConfigurableProduct('configurable', 'fixture_second_store'); + $this->executeInStoreContext->execute('default', [$this, 'assertProductConfig'], $expectedProducts); + $this->executeInStoreContext->execute( + 'fixture_second_store', + [$this, 'assertProductConfig'], + $expectedSecondStoreProducts + ); + } + + /** + * @return array + */ + public function expectedProductDataProvider(): array + { + return [ + [ + 'expected_store_products' => ['simple_option_1', 'simple_option_2'], + 'expected_second_store_products' => ['simple_option_2'], + ], + ]; + } + + /** + * Assert configurable product config + * + * @param $expectedProducts + * @return void + */ + public function assertProductConfig($expectedProducts): void + { + $product = $this->productRepository->get('configurable', false, null, true); + $config = $this->getBlockConfig($product)['index'] ?? null; + $this->assertNotNull($config); + $this->assertProducts($expectedProducts, $config); + } + + /** + * Prepare configurable product to test + * + * @param string $sku + * @param string $storeCode + * @return void + */ + private function prepareConfigurableProduct(string $sku, string $storeCode): void + { + $product = $this->productRepository->get($sku, false, null, true); + $productToUpdate = $product->getTypeInstance()->getUsedProductCollection($product) + ->setPageSize(1)->getFirstItem(); + $this->assertNotEmpty($productToUpdate->getData(), 'Configurable product does not have a child'); + $this->executeInStoreContext->execute($storeCode, [$this, 'setProductDisabled'], $productToUpdate); + } + + /** + * Assert product options display per stores + * + * @param array $expectedProducts + * @param array $config + * @return void + */ + private function assertProducts(array $expectedProducts, array $config): void + { + $this->assertCount(count($expectedProducts), $config); + $idsBySkus = $this->productResource->getProductsIdsBySkus($expectedProducts); + + foreach ($idsBySkus as $productId) { + $this->assertArrayHasKey($productId, $config); + } + } + + /** + * Set product status attribute to disabled + * + * @param ProductInterface $product + * @param string $storeCode + * @return void + */ + public function setProductDisabled(ProductInterface $product): void + { + $product->setStatus(Status::STATUS_DISABLED); + $this->productRepository->save($product); + } + + /** + * Get block config + * + * @param ProductInterface $product + * @return array + */ + private function getBlockConfig(ProductInterface $product): array + { + $block = $this->layout->createBlock(Configurable::class); + $block->setProduct($product); + + return $this->serializer->unserialize($block->getJsonConfig()); + } + + /** + * Assert configurable product config + * + * @param array $expectedData + * @param array $actualOptions + * @return void + */ + private function assertAttributeConfig(array $expectedData, array $actualOptions): void + { + $skus = array_keys($expectedData); + $idBySkuMap = $this->productResource->getProductsIdsBySkus($skus); + array_walk($actualOptions['options'], function (&$option) { + unset($option['id']); + }); + foreach ($expectedData as $sku => &$option) { + $option['products'] = [$idBySkuMap[$sku]]; + } + $this->assertEquals(array_values($expectedData), $actualOptions['options']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/RenderConfigurableOptionsTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/RenderConfigurableOptionsTest.php new file mode 100644 index 0000000000000..6a8dea32be620 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/RenderConfigurableOptionsTest.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Block\Product\View\Type; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Block\Product\View; +use Magento\Catalog\Model\Product\Visibility; +use Magento\ConfigurableProduct\Helper\Data; +use Magento\ConfigurableProduct\Model\ConfigurableAttributeData; +use Magento\Framework\Registry; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\View\Result\Page; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Test cases related to render configurable options. + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + */ +class RenderConfigurableOptionsTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var Data + */ + private $configurableHelper; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ConfigurableAttributeData + */ + private $configurableAttributeData; + + /** + * @var Registry + */ + private $registry; + + /** + * @var Page + */ + private $page; + + /** + * @var Json + */ + private $json; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->configurableHelper = $this->objectManager->get(Data::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->configurableAttributeData = $this->objectManager->get(ConfigurableAttributeData::class); + $this->registry = $this->objectManager->get(Registry::class); + $this->page = $this->objectManager->create(Page::class); + $this->json = $this->objectManager->get(Json::class); + parent::setUp(); + } + + /** + * Assert that all configurable options was rendered correctly if one of + * child product is visible on catalog\search. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * + * @return void + */ + public function testRenderConfigurableOptionsBlockWithOneVisibleOption(): void + { + $configurableProduct = $this->productRepository->get('Configurable product'); + $childProduct = $this->productRepository->get('Simple option 1'); + $childProduct->setVisibility(Visibility::VISIBILITY_BOTH); + $this->productRepository->save($childProduct); + $allProducts = $configurableProduct->getTypeInstance()->getUsedProducts($configurableProduct, null); + $options = $this->configurableHelper->getOptions($configurableProduct, $allProducts); + $confAttrData = $this->configurableAttributeData->getAttributesData($configurableProduct, $options); + $attributesJson = str_replace( + ['[', ']'], + ['\[', '\]'], + $this->json->serialize($confAttrData['attributes']) + ); + $optionsHtml = $this->getConfigurableOptionsHtml('Configurable product'); + $this->assertRegExp("/\"spConfig\": {\"attributes\":{$attributesJson}/", $optionsHtml); + } + + /** + * Render configurable options block. + * + * @param string $configurableSku + * @return string + */ + private function getConfigurableOptionsHtml(string $configurableSku): string + { + $product = $this->productRepository->get($configurableSku); + $this->registry->unregister('product'); + $this->registry->register('product', $product); + $optionsBlock = $this->getOptionsWrapperBlockWithOnlyConfigurableBlock(); + $optionHtml = $optionsBlock->toHtml(); + $this->registry->unregister('product'); + + return $optionHtml; + } + + /** + * Get options wrapper without extra blocks(only configurable child block). + * + * @return View + */ + private function getOptionsWrapperBlockWithOnlyConfigurableBlock(): View + { + $this->page->addHandle([ + 'default', + 'catalog_product_view', + 'catalog_product_view_type_configurable', + ]); + $this->page->getLayout()->generateXml(); + + /** @var View $productInfoOptionsWrapper */ + $productInfoOptionsWrapper = $this->page->getLayout()->getBlock('product.info.options.wrapper'); + $productInfoOptionsWrapper->unsetChild('product_options'); + $productInfoOptionsWrapper->unsetChild('html_calendar'); + + return $productInfoOptionsWrapper; + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/HelperTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/HelperTest.php new file mode 100644 index 0000000000000..ba684f37175e4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/HelperTest.php @@ -0,0 +1,369 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; +use Magento\Catalog\Model\Product\Media\Config; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Tests for image processing plugins for child products by saving a configurable product. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class HelperTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Helper + */ + private $helper; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ProductResource + */ + private $productResource; + + /** + * @var ProductAttributeRepositoryInterface + */ + private $productAttributeRepository; + + /** + * @var SerializerInterface + */ + private $jsonSerializer; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * @var Config + */ + private $config; + + /** + * @var WriteInterface + */ + private $mediaDirectory; + + /** + * @var ProductInterface + */ + private $configurableProduct; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->request = $this->objectManager->get(RequestInterface::class); + $this->helper = $this->objectManager->create(Helper::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->productResource =$this->objectManager->get(ProductResource::class); + $this->productAttributeRepository = $this->objectManager->create(ProductAttributeRepositoryInterface::class); + $this->jsonSerializer = $this->objectManager->get(SerializerInterface::class); + $this->searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $this->config = $this->objectManager->get(Config::class); + $this->mediaDirectory = $this->objectManager->get(Filesystem::class)->getDirectoryWrite(DirectoryList::MEDIA); + $this->configurableProduct = $this->productRepository->get('configurable'); + } + + /** + * Tests adding images with various roles to child products by saving a configurable product. + * + * @magentoDataFixture Magento/Catalog/_files/product_image.php + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @dataProvider initializeDataProvider + * @param array $childProducts + * @param array $expectedImages + * @return void + */ + public function testInitialize(array $childProducts, array $expectedImages): void + { + $this->setRequestParams($childProducts); + $this->helper->initialize($this->configurableProduct); + $this->assertChildProductImages($expectedImages); + } + + /** + * Tests replacing images with various roles to child products by saving a configurable product. + * + * @magentoDataFixture Magento/Catalog/_files/product_image.php + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @dataProvider initializeWithExistingChildImagesDataProvider + * @param array $childProducts + * @param array $expectedImages + * @return void + */ + public function testInitializeWithExistingChildImages(array $childProducts, array $expectedImages): void + { + $this->updateChildProductsImages( + [ + 'simple_10' => '/m/a/magento_thumbnail.jpg.tmp', + 'simple_20' => '/m/a/magento_small_image.jpg.tmp', + ] + ); + $this->setRequestParams($childProducts); + $this->helper->initialize($this->configurableProduct); + $this->assertChildProductImages($expectedImages); + } + + /** + * @return array + */ + public function initializeDataProvider(): array + { + return [ + 'children_with_same_image_and_roles' => [ + 'child_products' => [ + 'simple_10' => [ + 'media_gallery' => $this->getMediaGallery(['ben062bdw2v' => '/m/a/magento_image.jpg.tmp']), + 'images' => [ + '/m/a/magento_image.jpg.tmp' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + ], + 'simple_20' => [ + 'media_gallery' => $this->getMediaGallery(['ben062bdw2v' => '/m/a/magento_image.jpg.tmp']), + 'images' => [ + '/m/a/magento_image.jpg.tmp' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + ], + ], + 'expected_images' => [ + 'simple_10' => [ + '/m/a/magento_image_1.jpg' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + 'simple_20' => [ + '/m/a/magento_image_2.jpg' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + ], + ], + 'children_with_different_images' => [ + 'child_products' => [ + 'simple_10' => [ + 'media_gallery' => $this->getMediaGallery(['ben062bdw2v' => '/m/a/magento_image.jpg.tmp']), + 'images' => [ + '/m/a/magento_image.jpg.tmp' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + ], + 'simple_20' => [ + 'media_gallery' => $this->getMediaGallery( + ['lrwuv5ukisn' => '/m/a/magento_small_image.jpg.tmp'] + ), + 'images' => [ + '/m/a/magento_small_image.jpg.tmp' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + ], + ], + 'expected_images' => [ + 'simple_10' => [ + '/m/a/magento_image_1.jpg' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + 'simple_20' => [ + '/m/a/magento_small_image_1.jpg' => ['swatch_image', 'small_image', 'image', 'thumbnail'], + ], + ], + ], + 'children_with_different_image_roles' => [ + 'child_products' => [ + 'simple_10' => [ + 'media_gallery' => $this->getMediaGallery( + [ + 'ben062bdw2v' => '/m/a/magento_image.jpg.tmp', + 'lrwuv5ukisn' => '/m/a/magento_small_image.jpg.tmp', + ] + ), + 'images' => [ + '/m/a/magento_image.jpg.tmp' => ['swatch_image', 'small_image'], + '/m/a/magento_small_image.jpg.tmp' => ['image', 'thumbnail'], + ], + ], + 'simple_20' => [ + 'media_gallery' => $this->getMediaGallery( + [ + 'ben062bdw2v' => '/m/a/magento_image.jpg.tmp', + 'lrwuv5ukisn' => '/m/a/magento_small_image.jpg.tmp', + ] + ), + 'images' => [ + '/m/a/magento_small_image.jpg.tmp' => ['swatch_image', 'small_image'], + '/m/a/magento_image.jpg.tmp' => ['image', 'thumbnail'], + ], + ], + ], + 'expected_images' => [ + 'simple_10' => [ + '/m/a/magento_image_1.jpg' => ['swatch_image', 'small_image'], + '/m/a/magento_small_image_1.jpg' => ['image', 'thumbnail'], + ], + 'simple_20' => [ + '/m/a/magento_small_image_2.jpg' => ['swatch_image', 'small_image'], + '/m/a/magento_image_2.jpg' => ['image', 'thumbnail'], + ], + ], + ], + ]; + } + + /** + * @return array + */ + public function initializeWithExistingChildImagesDataProvider(): array + { + $dataProvider = $this->initializeDataProvider(); + unset($dataProvider['children_with_different_images'], $dataProvider['children_with_different_image_roles']); + + return array_values($dataProvider); + } + + /** + * Sets configurable product params to request. + * + * @param array $childProducts + * @return void + */ + private function setRequestParams(array $childProducts): void + { + $matrix = $associatedProductIds = []; + $attribute = $this->productAttributeRepository->get('test_configurable'); + + foreach ($childProducts as $sku => $product) { + $simpleProduct = $this->productRepository->get($sku); + $attributeValue = $simpleProduct->getData('test_configurable'); + foreach ($product['images'] as $image => $roles) { + foreach ($roles as $role) { + $product[$role] = $image; + } + } + unset($product['images']); + $product['configurable_attribute'] = $this->jsonSerializer->serialize( + ['test_configurable' => $attributeValue] + ); + $product['variationKey'] = $attributeValue; + $product['id'] = $simpleProduct->getId(); + $product['sku'] = $sku; + $product['was_changed'] = true; + $product['newProduct'] = 0; + $matrix[] = $product; + $associatedProductIds[] = $simpleProduct->getId(); + } + $this->request->setParams( + [ + 'attributes' => [$attribute->getAttributeId()], + 'configurable-matrix-serialized' => $this->jsonSerializer->serialize($matrix), + ] + ); + $this->request->setPostValue( + 'associated_product_ids_serialized', + $this->jsonSerializer->serialize($associatedProductIds) + ); + } + + /** + * Asserts child products images. + * + * @param array $expectedImages + * @return void + */ + private function assertChildProductImages(array $expectedImages): void + { + $simpleIds = $this->configurableProduct->getExtensionAttributes()->getConfigurableProductLinks(); + $criteria = $this->searchCriteriaBuilder->addFilter('entity_id', $simpleIds, 'in')->create(); + foreach ($this->productRepository->getList($criteria)->getItems() as $simpleProduct) { + $images = $expectedImages[$simpleProduct->getSku()]; + foreach ($images as $image => $roles) { + foreach ($roles as $role) { + $this->assertEquals($image, $simpleProduct->getData($role)); + } + $this->assertFileExists( + $this->mediaDirectory->getAbsolutePath($this->config->getBaseMediaPath() . $image) + ); + } + } + } + + /** + * Returns media gallery product param. + * + * @param array $imageNames + * @return array + */ + private function getMediaGallery(array $imageNames): array + { + $images = []; + foreach ($imageNames as $key => $item) { + $images[$key] = ['file' => $item, 'label' => '', 'media_type' => 'image']; + } + + return ['images' => $images]; + } + + /** + * Sets image to child products. + * + * @param array $imageNames + * @return void + */ + private function updateChildProductsImages(array $imageNames): void + { + $simpleIds = $this->configurableProduct->getExtensionAttributes()->getConfigurableProductLinks(); + $criteria = $this->searchCriteriaBuilder->addFilter('entity_id', $simpleIds, 'in')->create(); + $products = $this->productRepository->getList($criteria)->getItems(); + foreach ($products as $simpleProduct) { + $simpleProduct->setStoreId(Store::DEFAULT_STORE_ID) + ->setImage($imageNames[$simpleProduct->getSku()]) + ->setSmallImage($imageNames[$simpleProduct->getSku()]) + ->setThumbnail($imageNames[$simpleProduct->getSku()]) + ->setSwatchImage($imageNames[$simpleProduct->getSku()]) + ->setData( + 'media_gallery', + [ + 'images' => [ + ['file' => $imageNames[$simpleProduct->getSku()], 'label' => '', 'media_type' => 'image'] + ] + ] + ); + $this->productResource->save($simpleProduct); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php index b71507ae43f9f..833100e3e4740 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php @@ -3,76 +3,521 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Controller\Adminhtml; -use Magento\Catalog\Model\Product; -use Magento\Framework\Registry; -use Magento\TestFramework\ObjectManager; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type\Simple; +use Magento\Catalog\Model\Product\Type\Virtual; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Eav\Model\Config; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Registry; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; /** + * Tests for configurable product admin save. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @magentoAppArea adminhtml + * @magentoDbIsolation enabled */ -class ProductTest extends \Magento\TestFramework\TestCase\AbstractBackendController +class ProductTest extends AbstractBackendController { + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ProductAttributeRepositoryInterface + */ + private $productAttributeRepository; + + /** + * @var Registry + */ + private $registry; + + /** + * @var SerializerInterface + */ + private $jsonSerializer; + + /** + * @var Config + */ + private $eavConfig; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->productAttributeRepository = $this->_objectManager->create(ProductAttributeRepositoryInterface::class); + $this->registry = $this->_objectManager->get(Registry::class); + $this->jsonSerializer = $this->_objectManager->get(SerializerInterface::class); + $this->eavConfig = $this->_objectManager->get(Config::class); + } + /** * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php * @magentoDataFixture Magento/ConfigurableProduct/_files/associated_products.php + * @return void */ - public function testSaveActionAssociatedProductIds() + public function testSaveActionAssociatedProductIds(): void { $associatedProductIds = ['3', '14', '15', '92']; - $associatedProductIdsJSON = json_encode($associatedProductIds); $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'id' => 1, - 'attributes' => [$this->_getConfigurableAttribute()->getId()], - 'associated_product_ids_serialized' => $associatedProductIdsJSON, + 'attributes' => [$this->getAttribute('test_configurable')->getId()], + 'associated_product_ids_serialized' => $this->jsonSerializer->serialize($associatedProductIds), ] ); + $this->dispatch('backend/catalog/product/save'); + $this->assertSessionMessages($this->equalTo([__('You saved the product.')]), MessageInterface::TYPE_SUCCESS); + $this->assertRegistryConfigurableLinks($associatedProductIds); + $this->assertConfigurableLinks('configurable', $associatedProductIds); + } + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php + * @dataProvider saveNewProductDataProvider + * @param array $childProducts + * @return void + */ + public function testSaveNewProduct(array $childProducts): void + { + $this->serRequestParams($childProducts); $this->dispatch('backend/catalog/product/save'); + $this->assertSessionMessages($this->equalTo([__('You saved the product.')]), MessageInterface::TYPE_SUCCESS); + $this->assertChildProducts($childProducts); + $this->assertConfigurableOptions('configurable', $childProducts); + $this->assertConfigurableLinks('configurable', $this->getProductIds(array_keys($childProducts))); + } - /** @var $objectManager ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** + * @return array + */ + public function saveNewProductDataProvider(): array + { + return [ + 'with_different_prices_and_qty' => [ + 'child_products' => [ + 'simple_1' => [ + 'name' => 'simple_1', + 'sku' => 'simple_1', + 'price' => '200', + 'weight' => '1', + 'qty' => '100', + 'attributes' => ['test_configurable' => 'Option 1'], + ], + 'simple_2' => [ + 'name' => 'simple_2', + 'sku' => 'simple_2', + 'price' => '100', + 'weight' => '1', + 'qty' => '200', + 'attributes' => ['test_configurable' => 'Option 2'], + ], + ], + ], + 'without_weight' => [ + 'child_products' => [ + 'simple_1' => [ + 'name' => 'simple_1', + 'sku' => 'simple_1', + 'price' => '100', + 'qty' => '100', + 'attributes' => ['test_configurable' => 'Option 1'], + ], + 'simple_2' => [ + 'name' => 'simple_2', + 'sku' => 'simple_2', + 'price' => '100', + 'qty' => '100', + 'attributes' => ['test_configurable' => 'Option 2'], + ], + ], + ], + ]; + } - /** @var $product Product */ - $product = $objectManager->get(Registry::class)->registry('current_product'); - $configurableProductLinks = array_values($product->getExtensionAttributes()->getConfigurableProductLinks()); - self::assertEquals( - $associatedProductIds, - $configurableProductLinks, - 'Product links are not available in the registry' + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_one_simple.php + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_attribute_2.php + * @dataProvider saveExistProductDataProvider + * @param array $childProducts + * @param array $associatedProducts + * @return void + */ + public function testSaveExistProduct(array $childProducts, array $associatedProducts): void + { + $configurableProduct = $this->productRepository->get('configurable'); + $this->serRequestParams($childProducts, $associatedProducts, (int)$configurableProduct->getId()); + $this->dispatch('backend/catalog/product/save'); + $this->assertSessionMessages($this->equalTo([__('You saved the product.')]), MessageInterface::TYPE_SUCCESS); + $this->assertChildProducts($childProducts); + $this->assertConfigurableOptions('configurable', $childProducts); + $this->assertConfigurableLinks( + 'configurable', + $this->getProductIds(array_merge($associatedProducts, array_keys($childProducts))) + ); + } + + /** + * @return array + */ + public function saveExistProductDataProvider(): array + { + return [ + 'added_new_option' => [ + 'child_products' => [ + 'simple_2' => [ + 'name' => 'simple_2', + 'sku' => 'simple_2', + 'price' => '100', + 'weight' => '1', + 'qty' => '200', + 'attributes' => ['test_configurable' => 'Option 2'], + ], + ], + 'associated_products' => ['simple_1'], + ], + 'added_new_option_and_delete_old' => [ + 'child_products' => [ + 'simple_2' => [ + 'name' => 'simple_2', + 'sku' => 'simple_2', + 'price' => '100', + 'qty' => '100', + 'attributes' => ['test_configurable' => 'Option 2'], + ], + ], + 'associated_products' => [], + ], + 'delete_all_options' => [ + 'child_products' => [], + 'associated_products' => [], + ], + 'added_new_attribute' => [ + 'child_products' => [ + 'simple_1_1' => [ + 'name' => 'simple_1_1', + 'sku' => 'simple_1_1', + 'price' => '100', + 'weight' => '1', + 'qty' => '200', + 'attributes' => [ + 'test_configurable' => 'Option 1', + 'test_configurable_2' => 'Option 1', + ], + ], + 'simple_1_2' => [ + 'name' => 'simple_1_2', + 'sku' => 'simple_1_2', + 'price' => '100', + 'weight' => '1', + 'qty' => '200', + 'attributes' => [ + 'test_configurable' => 'Option 1', + 'test_configurable_2' => 'Option 2', + ], + ], + ], + 'associated_products' => [], + ], + 'added_new_attribute_and_delete_old' => [ + 'child_products' => [ + 'simple_2_1' => [ + 'name' => 'simple_2_1', + 'sku' => 'simple_2_1', + 'price' => '100', + 'qty' => '100', + 'attributes' => ['test_configurable_2' => 'Option 1'], + ], + 'simple_2_2' => [ + 'name' => 'simple_2_2', + 'sku' => 'simple_2_2', + 'price' => '100', + 'qty' => '100', + 'attributes' => ['test_configurable_2' => 'Option 2'], + ], + ], + 'associated_products' => [], + ], + ]; + } + + /** + * Sets products data into request. + * + * @param array $childProducts + * @param array|null $associatedProducts + * @param int|null $mainProductId + * @return void + */ + private function serRequestParams( + array $childProducts, + ?array $associatedProducts = [], + ?int $mainProductId = null + ): void { + $this->setVariationMatrix($childProducts); + $this->setAssociatedProducts($associatedProducts); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams( + [ + 'type' => Configurable::TYPE_CODE, + 'set' => $this->getDefaultAttributeSetId(), + 'id' => $mainProductId, + ] + ); + $this->getRequest()->setPostValue( + 'product', + [ + 'attribute_set_id' => $this->getDefaultAttributeSetId(), + 'name' => 'configurable', + 'sku' => 'configurable', + 'configurable_attributes_data' => $this->getConfigurableAttributesData($childProducts) ?: null, + ] ); + } - /** @var $product \Magento\Catalog\Api\Data\ProductInterface */ - $product = $objectManager->get(ProductRepositoryInterface::class)->getById(1, false, null, true); - $configurableProductLinks = array_values($product->getExtensionAttributes()->getConfigurableProductLinks()); - self::assertEquals( + /** + * Asserts product configurable links. + * + * @param string $sku + * @param array $associatedProductIds + * @return void + */ + private function assertConfigurableLinks(string $sku, array $associatedProductIds): void + { + $product = $this->productRepository->get($sku, false, null, true); + $this->assertEquals( $associatedProductIds, - $configurableProductLinks, + array_values($product->getExtensionAttributes()->getConfigurableProductLinks() ?: []), 'Product links are not available in the database' ); } /** - * Retrieve configurable attribute instance + * Asserts product from registry configurable links. * - * @return \Magento\Catalog\Model\Entity\Attribute - */ - protected function _getConfigurableAttribute() - { - return \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Entity\Attribute::class - )->loadByCode( - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Eav\Model\Config::class - )->getEntityType( - 'catalog_product' - )->getId(), - 'test_configurable' + * @param array $associatedProductIds + * @return void + */ + private function assertRegistryConfigurableLinks(array $associatedProductIds): void + { + $product = $this->registry->registry('current_product'); + $this->assertNotNull($product); + $this->assertEquals( + $associatedProductIds, + array_values($product->getExtensionAttributes()->getConfigurableProductLinks() ?: []), + 'Product links are not available in the registry' + ); + } + + /** + * Asserts child products data. + * + * @param array $childProducts + * @return void + */ + private function assertChildProducts(array $childProducts): void + { + foreach ($this->getProducts(array_column($childProducts, 'sku')) as $product) { + $expectedProduct = $childProducts[$product->getSku()]; + $this->assertEquals($expectedProduct['price'], $product->getPrice()); + + if (!empty($expectedProduct['weight'])) { + $this->assertEquals($expectedProduct['weight'], (double)$product->getWeight()); + $this->assertInstanceOf(Simple::class, $product->getTypeInstance()); + } else { + $this->assertInstanceOf(Virtual::class, $product->getTypeInstance()); + } + + $this->assertEquals($expectedProduct['qty'], $product->getExtensionAttributes()->getStockItem()->getQty()); + } + } + + /** + * Asserts that configurable attributes present in product configurable option list. + * + * @param string $sku + * @param array $childProducts + * @return void + */ + private function assertConfigurableOptions(string $sku, array $childProducts): void + { + $configurableProduct = $this->productRepository->get($sku, false, null, true); + $options = $configurableProduct->getExtensionAttributes()->getConfigurableProductOptions(); + if (empty($childProducts)) { + $this->assertNull($options); + } else { + foreach ($options as $option) { + $attribute = $this->getAttribute($option->getAttributeId()); + foreach ($childProducts as $childProduct) { + $this->assertContains($attribute->getAttributeCode(), array_keys($childProduct['attributes'])); + } + } + } + } + + /** + * Sets configurable product params to request. + * + * @param array $childProducts + * @return void + */ + private function setVariationMatrix(array $childProducts): void + { + $matrix = $attributeIds = $configurableAttributes = []; + foreach ($childProducts as $product) { + foreach ($product['attributes'] as $attributeCode => $optionLabel) { + $attribute = $this->getAttribute($attributeCode); + $configurableAttributes[$attributeCode] = $attribute->getSource()->getOptionId($optionLabel); + $attributeIds[] = $attribute->getAttributeId(); + } + $product['status'] = Status::STATUS_ENABLED; + $product['configurable_attribute'] = $this->jsonSerializer->serialize($configurableAttributes); + $product['newProduct'] = 1; + $product['variationKey'] = implode('-', array_values($configurableAttributes)); + $matrix[] = $product; + } + $this->getRequest()->setPostValue( + [ + 'affect_configurable_product_attributes' => 1, + 'attributes' => $attributeIds, + 'new-variations-attribute-set-id' => $this->getDefaultAttributeSetId(), + 'configurable-matrix-serialized' => $this->jsonSerializer->serialize($matrix), + ] ); } + + /** + * Sets associated product ids param to request. + * + * @param array|null $associatedProducts + */ + private function setAssociatedProducts(?array $associatedProducts): void + { + if (!empty($associatedProducts)) { + $associatedProductIds = array_map( + function (ProductInterface $product) { + return $product->getId(); + }, + $this->getProducts($associatedProducts) + ); + $this->getRequest()->setPostValue( + 'associated_product_ids_serialized', + $this->jsonSerializer->serialize($associatedProductIds) + ); + } + } + + /** + * Returns product configurable attributes data. + * + * @param array $childProducts + * @return array + */ + private function getConfigurableAttributesData(array $childProducts): array + { + $result = []; + foreach ($childProducts as $product) { + foreach ($product['attributes'] as $attributeCode => $optionLabel) { + $attribute = $this->getAttribute($attributeCode); + $optionId = $attribute->getSource()->getOptionId($optionLabel); + if (empty($result[$attribute->getAttributeId()])) { + $result[$attribute->getAttributeId()] = [ + 'attribute_id' =>$attribute->getAttributeId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getAttributeCode(), + 'position' => '0', + 'values' => [ + $optionId => [ + 'include' => '1', + 'value_index' => $optionId, + ], + ], + ]; + } else { + $result[$attribute->getAttributeId()]['values'][$optionId] = [ + 'include' => '1', + 'value_index' => $optionId, + ]; + } + } + } + + return $result; + } + + /** + * Retrieve default product attribute set id. + * + * @return int + */ + private function getDefaultAttributeSetId(): int + { + return (int)$this->eavConfig + ->getEntityType(ProductAttributeInterface::ENTITY_TYPE_CODE) + ->getDefaultAttributeSetId(); + } + + /** + * Retrieve configurable attribute instance. + * + * @param string $attributeCode + * @return ProductAttributeInterface + */ + private function getAttribute(string $attributeCode): ProductAttributeInterface + { + return $this->productAttributeRepository->get($attributeCode); + } + + /** + * Returns products by sku list. + * + * @param array $skuList + * @return ProductInterface[] + */ + private function getProducts(array $skuList): array + { + $result = []; + foreach ($skuList as $sku) { + $result[] = $this->productRepository->get($sku); + } + + return $result; + } + + /** + * Returns product ids by sku list. + * + * @param array $skuList + * @return array + */ + private function getProductIds(array $skuList): array + { + $associatedProductIds = []; + foreach ($this->getProducts($skuList) as $product) { + $associatedProductIds[] = $product->getId(); + } + + return $associatedProductIds; + } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/FindByUrlRewriteTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/FindByUrlRewriteTest.php new file mode 100644 index 0000000000000..23ab905fa0eab --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/FindByUrlRewriteTest.php @@ -0,0 +1,267 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection; +use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollectionFactory; +use Magento\UrlRewrite\Model\UrlRewrite as UrlRewriteItem; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use PHPUnit\Framework\TestCase; + +/** + * Test cases related to check that URL rewrite has created or not. + */ +class FindByUrlRewriteTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManger; + + /** + * @var ProductResource + */ + private $productResource; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var UrlRewriteCollectionFactory + */ + private $urlRewriteCollectionFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManger = Bootstrap::getObjectManager(); + $this->productResource = $this->objectManger->get(ProductResource::class); + $this->productRepository = $this->objectManger->get(ProductRepositoryInterface::class); + $this->urlRewriteCollectionFactory = $this->objectManger->get(UrlRewriteCollectionFactory::class); + parent::setUp(); + } + + /** + * Assert that product is available by URL rewrite with different visibility. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @dataProvider visibilityWithExpectedResultDataProvider + * @magentoDbIsolation enabled + * + * @param array $productsData + * @return void + */ + public function testCheckIsUrlRewriteForChildrenProductsHasCreated(array $productsData): void + { + $this->checkConfigurableUrlRewriteWasCreated(); + $this->updateProductsVisibility($productsData); + $productIdsBySkus = $this->getProductIdsBySkus($productsData); + $urlRewritesCollection = $this->getUrlRewritesCollectionByProductIds($productIdsBySkus); + $expectedCount = 0; + foreach ($productsData as $productData) { + $productId = $productIdsBySkus[$productData['sku']]; + /** @var UrlRewriteItem $urlRewrite */ + $urlRewrite = $urlRewritesCollection->getItemByColumnValue( + UrlRewrite::TARGET_PATH, + "catalog/product/view/id/{$productId}" + ); + if ($productData['url_rewrite_created']) { + $this->assertNotNull($urlRewrite); + $this->assertEquals($productId, $urlRewrite->getEntityId()); + $this->assertEquals('product', $urlRewrite->getEntityType()); + $expectedCount++; + } else { + $this->assertNull($urlRewrite); + } + } + $this->assertCount($expectedCount, $urlRewritesCollection); + } + + /** + * Return products visibility, expected result and other product additional data. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * + * @return array + */ + public function visibilityWithExpectedResultDataProvider(): array + { + return [ + 'visibility_for_both_product_only_catalog' => [ + [ + [ + 'sku' => 'Simple option 1', + 'visibility' => Visibility::VISIBILITY_IN_CATALOG, + 'url_rewrite_created' => true, + ], + [ + 'sku' => 'Simple option 2', + 'visibility' => Visibility::VISIBILITY_IN_CATALOG, + 'url_rewrite_created' => true, + ], + ], + ], + 'visibility_for_both_product_catalog_search' => [ + [ + [ + 'sku' => 'Simple option 1', + 'visibility' => Visibility::VISIBILITY_BOTH, + 'url_rewrite_created' => true, + ], + [ + 'sku' => 'Simple option 2', + 'visibility' => Visibility::VISIBILITY_BOTH, + 'url_rewrite_created' => true, + ], + ], + ], + 'visibility_for_both_product_only_search' => [ + [ + [ + 'sku' => 'Simple option 1', + 'visibility' => Visibility::VISIBILITY_IN_SEARCH, + 'url_rewrite_created' => true, + ], + [ + 'sku' => 'Simple option 2', + 'visibility' => Visibility::VISIBILITY_IN_SEARCH, + 'url_rewrite_created' => true, + ], + ], + ], + 'visibility_for_both_product_not_visible_individuality' => [ + [ + [ + 'sku' => 'Simple option 1', + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE, + 'url_rewrite_created' => false, + ], + [ + 'sku' => 'Simple option 2', + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE, + 'url_rewrite_created' => false, + ], + ], + ], + 'visibility_for_one_product_only_catalog' => [ + [ + [ + 'sku' => 'Simple option 1', + 'visibility' => Visibility::VISIBILITY_IN_CATALOG, + 'url_rewrite_created' => true, + ], + [ + 'sku' => 'Simple option 2', + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE, + 'url_rewrite_created' => false, + ], + ], + ], + 'visibility_for_one_product_catalog_search' => [ + [ + [ + 'sku' => 'Simple option 1', + 'visibility' => Visibility::VISIBILITY_BOTH, + 'url_rewrite_created' => true, + ], + [ + 'sku' => 'Simple option 2', + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE, + 'url_rewrite_created' => false, + ], + ], + ], + 'visibility_for_one_product_only_search' => [ + [ + [ + 'sku' => 'Simple option 1', + 'visibility' => Visibility::VISIBILITY_IN_SEARCH, + 'url_rewrite_created' => true, + ], + [ + 'sku' => 'Simple option 2', + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE, + 'url_rewrite_created' => false, + ], + ], + ], + ]; + } + + /** + * Update products visibility. + * + * @param array $productsData + * @return void + */ + private function updateProductsVisibility(array $productsData): void + { + foreach ($productsData as $productData) { + $product = $this->productRepository->get($productData['sku']); + $product->setVisibility($productData['visibility']); + $this->productRepository->save($product); + } + } + + /** + * Get URL rewrite collection by product ids. + * + * @param int[] $productIds + * @param string $storeCode + * @return UrlRewriteCollection + */ + private function getUrlRewritesCollectionByProductIds( + array $productIds, + string $storeCode = 'default' + ): UrlRewriteCollection { + $collection = $this->urlRewriteCollectionFactory->create(); + $collection->addStoreFilter($storeCode); + $collection->addFieldToFilter(UrlRewrite::ENTITY_TYPE, ['eq' => 'product']); + $collection->addFieldToFilter(UrlRewrite::ENTITY_ID, ['in' => $productIds]); + + return $collection; + } + + /** + * Check that configurable url rewrite was created. + * + * @return void + */ + private function checkConfigurableUrlRewriteWasCreated(): void + { + $configurableProduct = $this->productRepository->get('Configurable product'); + $configurableUrlRewrite = $this->getUrlRewritesCollectionByProductIds([$configurableProduct->getId()]) + ->getFirstItem(); + $this->assertEquals( + $configurableUrlRewrite->getTargetPath(), + "catalog/product/view/id/{$configurableProduct->getId()}" + ); + } + + /** + * Load all product ids by skus. + * + * @param array $productsData + * @return array + */ + private function getProductIdsBySkus(array $productsData): array + { + $skus = array_column($productsData, 'sku'); + + return $this->productResource->getProductsIdsBySkus($skus); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php index e530029e9755e..6f491b33a3496 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php @@ -3,48 +3,94 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\Customer\Model\Group; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Catalog\Model\Product\Price\GetPriceIndexDataByProductId; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; /** - * Class PriceTest + * Provides tests for configurable product pricing. + * * @magentoDbIsolation disabled */ -class PriceTest extends \PHPUnit\Framework\TestCase +class PriceTest extends TestCase { - /** @var \Magento\Framework\ObjectManagerInterface */ - protected $objectManager; + /** + * @var ObjectManagerInterface + */ + private $objectManager; - /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory */ - protected $customOptionFactory; + /** + * @var ProductRepositoryInterface + */ + private $productRepository; /** - * + * @var Price + */ + private $priceModel; + + /** + * @var GetPriceIndexDataByProductId + */ + private $getPriceIndexDataByProductId; + + /** + * @var WebsiteRepositoryInterface + */ + private $websiteRepository; + + /** + * @inheritdoc */ protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->priceModel = $this->objectManager->create(Price::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->getPriceIndexDataByProductId = $this->objectManager->get(GetPriceIndexDataByProductId::class); + $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); } /** * @magentoDataFixture Magento/ConfigurableProduct/_files/tax_rule.php * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - * @magentoDbIsolation disabled + * @return void */ - public function testGetFinalPrice() + public function testGetFinalPrice(): void { $this->assertPrice(10); + $this->assertIndexTableData( + 'configurable', + ['price' => 0, 'final_price' => 0, 'min_price' => 10, 'max_price' => 20, 'tier_price' => null] + ); + $this->assertIndexTableData( + 'simple_10', + ['price' => 10, 'final_price' => 10, 'min_price' => 10, 'max_price' => 10, 'tier_price' => null] + ); + $this->assertIndexTableData( + 'simple_20', + ['price' => 20, 'final_price' => 20, 'min_price' => 20, 'max_price' => 20, 'tier_price' => null] + ); } /** * @magentoConfigFixture current_store tax/display/type 1 * @magentoDataFixture Magento/ConfigurableProduct/_files/tax_rule.php * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - * @magentoDbIsolation disabled + * @return void */ - public function testGetFinalPriceExcludingTax() + public function testGetFinalPriceExcludingTax(): void { $this->assertPrice(10); } @@ -53,9 +99,9 @@ public function testGetFinalPriceExcludingTax() * @magentoConfigFixture current_store tax/display/type 2 * @magentoDataFixture Magento/ConfigurableProduct/_files/tax_rule.php * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - * @magentoDbIsolation disabled + * @return void */ - public function testGetFinalPriceIncludingTax() + public function testGetFinalPriceIncludingTax(): void { //lowest price of configurable variation + 10% $this->assertPrice(11); @@ -65,9 +111,9 @@ public function testGetFinalPriceIncludingTax() * @magentoConfigFixture current_store tax/display/type 3 * @magentoDataFixture Magento/ConfigurableProduct/_files/tax_rule.php * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - * @magentoDbIsolation disabled + * @return void */ - public function testGetFinalPriceIncludingExcludingTax() + public function testGetFinalPriceIncludingExcludingTax(): void { //lowest price of configurable variation + 10% $this->assertPrice(11); @@ -76,109 +122,78 @@ public function testGetFinalPriceIncludingExcludingTax() /** * @magentoDataFixture Magento/ConfigurableProduct/_files/tax_rule.php * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - * @magentoDbIsolation disabled + * @return void */ - public function testGetFinalPriceWithSelectedSimpleProduct() + public function testGetFinalPriceWithSelectedSimpleProduct(): void { - $product = $this->getProduct(1); - $product->addCustomOption('simple_product', 20, $this->getProduct(20)); + $product = $this->productRepository->get('configurable'); + $product->addCustomOption('simple_product', 20, $this->productRepository->get('simple_20')); $this->assertPrice(20, $product); } /** - * @magentoConfigFixture current_store tax/display/type 1 - * @magentoDataFixture Magento/ConfigurableProduct/_files/tax_rule.php - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - * @magentoDbIsolation disabled + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price.php + * @return void */ - public function testGetFinalPriceWithCustomOption() + public function testGetFinalPriceWithCustomOptionAndSimpleTierPrice(): void { - $product = $this->getProduct(1); - - $options = $this->prepareOptions( - [ - [ - 'option_id' => null, - 'previous_group' => 'text', - 'title' => 'Test Field', - 'type' => 'field', - 'is_require' => 1, - 'sort_order' => 0, - 'price' => 100, - 'price_type' => 'fixed', - 'sku' => '1-text', - 'max_characters' => 100, - ], - ], - $product + $configurable = $this->productRepository->get('configurable'); + $this->assertIndexTableData( + 'configurable', + ['price' => 0, 'final_price' => 0, 'min_price' => 9, 'max_price' => 30, 'tier_price' => 15] ); - - $product->setOptions($options); - $product->setCanSaveCustomOptions(true); - - /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->save($product); - - $optionId = $product->getOptions()[0]->getId(); - $product->addCustomOption(AbstractType::OPTION_PREFIX . $optionId, 'text'); - $product->addCustomOption('option_ids', $optionId); - $this->assertPrice(110, $product); + $this->assertIndexTableData( + 'simple_10', + ['price' => 10, 'final_price' => 9, 'min_price' => 9, 'max_price' => 9, 'tier_price' => null] + ); + $this->assertIndexTableData( + 'simple_20', + ['price' => 20, 'final_price' => 15, 'min_price' => 15, 'max_price' => 15, 'tier_price' => 15] + ); + $optionId = $configurable->getOptions()[0]->getId(); + $configurable->addCustomOption(AbstractType::OPTION_PREFIX . $optionId, 'text'); + $configurable->addCustomOption('option_ids', $optionId); + // First simple special price (9) + Option price (15) + $this->assertPrice(24, $configurable); + $configurable->addCustomOption('simple_product', 20, $this->productRepository->get('simple_20')); + // Second simple tier price (15) + Option price (15) + $this->assertPrice(30, $configurable); } /** - * @param array $options - * @param \Magento\Catalog\Model\Product $product - * @return \Magento\Catalog\Api\Data\ProductCustomOptionInterface[] + * Asserts price data in index table. + * + * @param string $sku + * @param array $expectedPrices + * @return void */ - protected function prepareOptions($options, $product) + private function assertIndexTableData(string $sku, array $expectedPrices): void { - $preparedOptions = []; - - if (!$this->customOptionFactory) { - $this->customOptionFactory = $this->objectManager->create( - \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class - ); - } - - foreach ($options as $option) { - $option = $this->customOptionFactory->create(['data' => $option]); - $option->setProductSku($product->getSku()); - - $preparedOptions[] = $option; + $data = $this->getPriceIndexDataByProductId->execute( + (int)$this->productRepository->get($sku)->getId(), + Group::NOT_LOGGED_IN_ID, + (int)$this->websiteRepository->get('base')->getId() + ); + $data = reset($data); + foreach ($expectedPrices as $column => $price) { + $this->assertEquals($price, $data[$column], $column); } - - return $preparedOptions; } /** - * Test + * Asserts product final price. * - * @param $expectedPrice - * @param null $product + * @param float $expectedPrice + * @param ProductInterface|null $product * @return void */ - protected function assertPrice($expectedPrice, $product = null) + private function assertPrice(float $expectedPrice, ?ProductInterface $product = null): void { - $product = $product ?: $this->getProduct(1); - - /** @var $model \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price */ - $model = $this->objectManager->create( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price::class - ); - + $product = $product ?: $this->productRepository->get('configurable'); // final price is the lowest price of configurable variations - $this->assertEquals(round($expectedPrice, 2), round($model->getFinalPrice(1, $product), 2)); - } - - /** - * @param int $id - * @return \Magento\Catalog\Model\Product - */ - private function getProduct($id) - { - /** @var $productRepository ProductRepositoryInterface */ - $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); - return $productRepository->getById($id, true, null, true); + $this->assertEquals( + round($expectedPrice, 2), + round($this->priceModel->getFinalPrice(1, $product), 2) + ); } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/SalableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/SalableTest.php new file mode 100644 index 0000000000000..33c82dce21963 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/SalableTest.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\CatalogInventory\Api\Data\StockStatusInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Check is configurable product salable with different conditions + * + * @magentoAppArea frontend + */ +class SalableTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * + * @dataProvider salableDataProvider + * + * @param array $productSkus + * @param array $productData + * @param bool $expectedValue + * @return void + */ + public function testIsSalable(array $productSkus, array $productData, bool $expectedValue): void + { + $this->updateProduct($productSkus, $productData); + $configurableProduct = $this->productRepository->get('configurable', false, null, true); + + $this->assertEquals($expectedValue, $configurableProduct->getIsSalable()); + } + + /** + * @return array + */ + public function salableDataProvider(): array + { + return [ + 'all children enabled_and_in_stock' => [ + 'product_skus' => [], + 'data' => [], + 'expected_value' => true, + ], + 'one_child_out_of_stock' => [ + 'product_skus' => ['simple_10'], + 'data' => [ + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'is_in_stock' => StockStatusInterface::STATUS_OUT_OF_STOCK, + ], + ], + 'expected_value' => true, + ], + 'one_child_disabled' => [ + 'product_skus' => ['simple_10'], + 'data' => ['status' => Status::STATUS_DISABLED], + 'expected_value' => true, + ], + 'all_children_disabled' => [ + 'product_skus' => ['simple_10', 'simple_20'], + 'data' => ['status' => Status::STATUS_DISABLED], + 'expected_value' => false, + ], + 'all_children_out_of_stock' => [ + 'product_skus' => ['simple_10', 'simple_20'], + 'data' => [ + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'is_in_stock' => StockStatusInterface::STATUS_OUT_OF_STOCK, + ], + ], + 'expected_value' => false, + ] + ]; + } + + /** + * Update product with data + * + * @param array $skus + * @param array $data + * @return void + */ + private function updateProduct(array $skus, array $data): void + { + if (!empty($skus)) { + foreach ($skus as $sku) { + $product = $this->productRepository->get($sku); + $product->addData($data); + $this->productRepository->save($product); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php index fc2e99d6a9d10..c3b845694c6a0 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/VariationHandlerTest.php @@ -3,78 +3,105 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\ConfigurableProduct\Model\Product; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogInventory\Api\StockRegistryInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; /** + * Tests for simple products generation by saving a configurable product. + * * @magentoAppIsolation enabled - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoDbIsolation enabled */ -class VariationHandlerTest extends \PHPUnit\Framework\TestCase +class VariationHandlerTest extends TestCase { - /** @var \Magento\ConfigurableProduct\Model\Product\VariationHandler */ - private $_model; + /** + * @var ObjectManagerInterface + */ + private $objectManager; - /** @var \Magento\Catalog\Model\Product */ - private $_product; + /** + * @var VariationHandler + */ + private $variationHandler; - /** @var \Magento\CatalogInventory\Api\StockRegistryInterface */ + /** + * @var ProductInterface + */ + private $product; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var StockRegistryInterface + */ private $stockRegistry; + /** + * @inheritdoc + */ protected function setUp() { - $this->_product = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Product::class - ); - $this->_product->load(1); - - $this->_model = Bootstrap::getObjectManager()->create( - \Magento\ConfigurableProduct\Model\Product\VariationHandler::class - ); - // prevent fatal errors by assigning proper "singleton" of type instance to the product - $this->_product->setTypeInstance($this->_model); - $this->stockRegistry = Bootstrap::getObjectManager()->get( - \Magento\CatalogInventory\Api\StockRegistryInterface::class - ); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->variationHandler = $this->objectManager->create(VariationHandler::class); + $this->product = $this->productRepository->get('configurable'); + $this->stockRegistry = $this->objectManager->get(StockRegistryInterface::class); } /** - * @param array $productsData + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php * @dataProvider generateSimpleProductsDataProvider + * @param array $productsData + * @return void */ - public function testGenerateSimpleProducts($productsData) + public function testGenerateSimpleProducts(array $productsData): void { - $this->_product->setNewVariationsAttributeSetId(4); - // Default attribute set id - $generatedProducts = $this->_model->generateSimpleProducts($this->_product, $productsData); + $this->product->setImage('some_test_image.jpg') + ->setSmallImage('some_test_image.jpg') + ->setThumbnail('some_test_image.jpg') + ->setSwatchImage('some_test_image.jpg') + ->setNewVariationsAttributeSetId($this->product->getDefaultAttributeSetId()); + $generatedProducts = $this->variationHandler->generateSimpleProducts($this->product, $productsData); $this->assertEquals(3, count($generatedProducts)); foreach ($generatedProducts as $productId) { $stockItem = $this->stockRegistry->getStockItem($productId); - /** @var $product \Magento\Catalog\Model\Product */ - $product = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Product::class - ); - $product->load($productId); + $product = $this->productRepository->getById($productId); $this->assertNotNull($product->getName()); $this->assertNotNull($product->getSku()); $this->assertNotNull($product->getPrice()); $this->assertNotNull($product->getWeight()); $this->assertEquals('1', $stockItem->getIsInStock()); + $this->assertNull($product->getImage()); + $this->assertNull($product->getSmallImage()); + $this->assertNull($product->getThumbnail()); + $this->assertNull($product->getSwatchImage()); } } /** * @param array $productsData + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php * @dataProvider generateSimpleProductsWithPartialDataDataProvider - * @magentoDbIsolation enabled + * @return void */ - public function testGenerateSimpleProductsWithPartialData($productsData) + public function testGenerateSimpleProductsWithPartialData(array $productsData): void { - $this->_product->setNewVariationsAttributeSetId(4); - $generatedProducts = $this->_model->generateSimpleProducts($this->_product, $productsData); - $parentStockItem = $this->stockRegistry->getStockItem($this->_product->getId()); + $this->product->setNewVariationsAttributeSetId(4); + $generatedProducts = $this->variationHandler->generateSimpleProducts($this->product, $productsData); + $parentStockItem = $this->stockRegistry->getStockItem($this->product->getId()); foreach ($generatedProducts as $productId) { $stockItem = $this->stockRegistry->getStockItem($productId); $this->assertEquals($parentStockItem->getManageStock(), $stockItem->getManageStock()); @@ -85,7 +112,7 @@ public function testGenerateSimpleProductsWithPartialData($productsData) /** * @return array */ - public static function generateSimpleProductsDataProvider() + public function generateSimpleProductsDataProvider(): array { return [ [ @@ -122,7 +149,7 @@ public static function generateSimpleProductsDataProvider() /** * @return array */ - public static function generateSimpleProductsWithPartialDataDataProvider() + public function generateSimpleProductsWithPartialDataDataProvider(): array { return [ [ diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/QuickSearchTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/QuickSearchTest.php new file mode 100644 index 0000000000000..6884be3b04d14 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/QuickSearchTest.php @@ -0,0 +1,170 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Visibility; +use Magento\TestFramework\Catalog\Model\Layer\QuickSearchByQuery; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Test cases related to find configurable product via quick search using mysql search engine. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php + * + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + */ +class QuickSearchTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var QuickSearchByQuery + */ + private $quickSearchByQuery; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->quickSearchByQuery = $this->objectManager->get(QuickSearchByQuery::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + parent::setUp(); + } + + /** + * Assert that configurable child products has not found by query using mysql search engine. + * + * @magentoConfigFixture default/catalog/search/engine mysql + * + * @return void + */ + public function testChildProductsHasNotFoundedByQuery(): void + { + $this->checkThatOnlyConfigurableProductIsAvailableBySearch('Configurable Option'); + } + + /** + * Assert that child product of configurable will be available by search after + * set to product visibility by catalog and search using mysql search engine. + * + * @magentoConfigFixture default/catalog/search/engine mysql + * @dataProvider productAvailabilityInSearchByVisibilityDataProvider + * + * @param int $visibility + * @param bool $expectedResult + * @return void + */ + public function testOneOfChildIsAvailableBySearch(int $visibility, bool $expectedResult): void + { + $this->checkThatOnlyConfigurableProductIsAvailableBySearch('Configurable Option'); + $this->updateProductVisibility($visibility); + $this->checkProductAvailabilityInSearch($expectedResult); + $this->checkThatOnlyConfigurableProductIsAvailableBySearch('White'); + } + + /** + * Return data with product visibility and expected result. + * + * @return array + */ + public function productAvailabilityInSearchByVisibilityDataProvider(): array + { + return [ + 'visible_catalog_only' => [ + Visibility::VISIBILITY_IN_CATALOG, + false, + ], + 'visible_catalog_and_search' => [ + Visibility::VISIBILITY_BOTH, + true, + ], + 'visible_search_only' => [ + Visibility::VISIBILITY_IN_SEARCH, + true, + ], + 'visible_search_not_visible_individuality' => [ + Visibility::VISIBILITY_NOT_VISIBLE, + false, + ], + ]; + } + + /** + * Assert that configurable product was found by option value using mysql search engine. + * + * @magentoConfigFixture default/catalog/search/engine mysql + * + * @return void + */ + public function testSearchByOptionValue(): void + { + $this->checkThatOnlyConfigurableProductIsAvailableBySearch('Option 1'); + } + + /** + * Assert that anyone child product is not available by quick search. + * + * @param string $searchQuery + * + * @return void + */ + private function checkThatOnlyConfigurableProductIsAvailableBySearch(string $searchQuery): void + { + $searchResult = $this->quickSearchByQuery->execute($searchQuery); + $this->assertCount(1, $searchResult->getItems()); + /** @var Product $configurableProduct */ + $configurableProduct = $searchResult->getFirstItem(); + $this->assertEquals('Configurable product', $configurableProduct->getSku()); + } + + /** + * Update product visibility. + * + * @param int $visibility + * @return void + */ + private function updateProductVisibility(int $visibility): void + { + $childProduct = $this->productRepository->get('Simple option 1'); + $childProduct->setVisibility($visibility); + $this->productRepository->save($childProduct); + } + + /** + * Assert that configurable and one of child product is available by search. + * + * @param bool $firstChildIsVisible + * @return void + */ + private function checkProductAvailabilityInSearch(bool $firstChildIsVisible): void + { + $searchResult = $this->quickSearchByQuery->execute('Black'); + $this->assertNotNull($searchResult->getItemByColumnValue(Product::SKU, 'Configurable product')); + $this->assertEquals( + $firstChildIsVisible, + (bool)$searchResult->getItemByColumnValue(Product::SKU, 'Simple option 1') + ); + $this->assertNull($searchResult->getItemByColumnValue(Product::SKU, 'Simple option 2')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_attribute_different_labels_per_stores.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_attribute_different_labels_per_stores.php new file mode 100644 index 0000000000000..0d99869d4adf9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_attribute_different_labels_per_stores.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../Store/_files/core_fixturestore.php'; + +$objectManager = Bootstrap::getObjectManager(); +$defaultInstalledStoreId = $storeManager->getStore('default')->getId(); +$secondStoreId = $storeManager->getStore('fixturestore')->getId(); +/** @var CategorySetup $installer */ +$installer = $objectManager->get(CategorySetup::class); +/** @var Attribute $attribute */ +$attribute = $objectManager->get(AttributeFactory::class)->create(); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +$entityType = $installer->getEntityTypeId(ProductAttributeInterface::ENTITY_TYPE_CODE); +if (!$attribute->loadByCode($entityType, 'different_labels_attribute')->getAttributeId()) { + $attribute->setData( + [ + 'frontend_label' => ['Different option labels dropdown attribute'], + 'entity_type_id' => $entityType, + 'frontend_input' => 'select', + 'backend_type' => 'int', + 'is_required' => '0', + 'attribute_code' => 'different_labels_attribute', + 'is_global' => ScopedAttributeInterface::SCOPE_GLOBAL, + 'is_user_defined' => 1, + 'is_unique' => '0', + 'is_searchable' => '0', + 'is_comparable' => '0', + 'is_filterable' => '1', + 'is_filterable_in_search' => '0', + 'is_used_for_promo_rules' => '0', + 'is_html_allowed_on_front' => '1', + 'used_in_product_listing' => '1', + 'used_for_sort_by' => '0', + 'option' => [ + 'value' => [ + 'option_1' => [ + Store::DEFAULT_STORE_ID => 'Option 1', + $defaultInstalledStoreId => 'Option 1 Default Store', + $secondStoreId => 'Option 1 Second Store', + ], + 'option_2' => [ + Store::DEFAULT_STORE_ID => 'Option 2', + $defaultInstalledStoreId => 'Option 2 Default Store', + $secondStoreId => 'Option 2 Second Store', + ], + 'option_3' => [ + Store::DEFAULT_STORE_ID => 'Option 3', + $defaultInstalledStoreId => 'Option 3 Default Store', + $secondStoreId => 'Option 3 Second Store', + ], + ], + 'order' => [ + 'option_1' => 1, + 'option_2' => 2, + 'option_3' => 3, + ], + ], + ] + ); + $attributeRepository->save($attribute); + $installer->addAttributeToGroup( + ProductAttributeInterface::ENTITY_TYPE_CODE, + 'Default', + 'General', + $attribute->getId() + ); +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_attribute_different_labels_per_stores_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_attribute_different_labels_per_stores_rollback.php new file mode 100644 index 0000000000000..f69545c831a98 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_attribute_different_labels_per_stores_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\Framework\Exception\NoSuchEntityException; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Registry; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); + +try { + $attributeRepository->deleteById('different_labels_attribute'); +} catch (NoSuchEntityException $e) { + //already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/../../Store/_files/core_fixturestore_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores.php new file mode 100644 index 0000000000000..c4498c6beae4e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionFactory; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/configurable_attribute_different_labels_per_stores.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('different_labels_attribute'); +$options = $attribute->getOptions(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->cleanCache(); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +$attributeValues = []; +$associatedProductIds = []; +$rootCategoryId = $baseWebsite->getDefaultStore()->getRootCategoryId(); +array_shift($options); + +foreach ($options as $option) { + $product = $productFactory->create(); + $product->setTypeId(ProductType::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Option ' . $option->getLabel()) + ->setSku(strtolower(str_replace(' ', '_', 'simple ' . $option->getLabel()))) + ->setPrice(150) + ->setDifferentLabelsAttribute($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product = $productRepository->save($product); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], +]; +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$product = $productFactory->create(); +/** @var ProductExtensionFactory $extensionAttributesFactory */ +$extensionAttributesFactory = $objectManager->get(ProductExtensionFactory::class); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?: $extensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores_rollback.php new file mode 100644 index 0000000000000..c82da5f653bd8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_different_option_labeles_per_stores_rollback.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\ConfigurableProduct\Model\DeleteConfigurableProduct; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var DeleteConfigurableProduct $deleteConfigurableProduct */ +$deleteConfigurableProduct = $objectManager->get(DeleteConfigurableProduct::class); +$deleteConfigurableProduct->execute('configurable'); + +require __DIR__ . '/configurable_attribute_different_labels_per_stores_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_two_websites.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_two_websites.php new file mode 100644 index 0000000000000..17837deb15a03 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_two_websites.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionFactory; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../Store/_files/second_website_with_two_stores.php'; +require __DIR__ . '/configurable_attribute.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('test_configurable'); +$options = $attribute->getOptions(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsite = $websiteRepository->get('base'); +$secondWebsite = $websiteRepository->get('test'); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->cleanCache(); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +$attributeValues = []; +$associatedProductIds = []; +$rootCategoryId = $baseWebsite->getDefaultStore()->getRootCategoryId(); +array_shift($options); + +foreach ($options as $option) { + $product = $productFactory->create(); + $product->setTypeId(ProductType::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId(), $secondWebsite->getId()]) + ->setName('Configurable Option ' . $option->getLabel()) + ->setSku(strtolower(str_replace(' ', '_', 'simple ' . $option->getLabel()))) + ->setPrice(150) + ->setTestConfigurable($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product = $productRepository->save($product); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], +]; +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$product = $productFactory->create(); +/** @var ProductExtensionFactory $extensionAttributesFactory */ +$extensionAttributesFactory = $objectManager->get(ProductExtensionFactory::class); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?: $extensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId(), $secondWebsite->getId()]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_two_websites_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_two_websites_rollback.php new file mode 100644 index 0000000000000..78e3109352693 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_two_websites_rollback.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\ConfigurableProduct\Model\DeleteConfigurableProduct; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var DeleteConfigurableProduct $deleteConfigurableProduct */ +$deleteConfigurableProduct = $objectManager->get(DeleteConfigurableProduct::class); +$deleteConfigurableProduct->execute('configurable'); + +require __DIR__ . '/configurable_attribute_rollback.php'; +require __DIR__ . '/../../Store/_files/second_website_with_two_stores_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_category.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_category.php new file mode 100644 index 0000000000000..0db8b8b097644 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_category.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\CategoryLinkManagementInterface; +use Magento\Catalog\Helper\DefaultCategory; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../Catalog/_files/category.php'; +require __DIR__ . '/product_configurable.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class); +/** @var DefaultCategory $categoryHelper */ +$categoryHelper = $objectManager->get(DefaultCategory::class); + +foreach (['simple_10', 'simple_20', 'configurable'] as $sku) { + $categoryLinkManagement->assignProductToCategories($sku, [$categoryHelper->getId(), 333]); +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_category_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_category_rollback.php new file mode 100644 index 0000000000000..36d070f566bb4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_category_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../Catalog/_files/category_rollback.php'; +require __DIR__ . '/product_configurable_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_child_products_with_images.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_child_products_with_images.php new file mode 100644 index 0000000000000..d2418ccaaddec --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_child_products_with_images.php @@ -0,0 +1,62 @@ +<?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; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../../Magento/Catalog/_files/product_image.php'; +require __DIR__ . '/product_configurable.php'; + +$objectManager = Bootstrap::getObjectManager(); +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$firstSimple = $productRepository->get('simple_10'); +$secondSimple = $productRepository->get('simple_20'); +/** @var $firstSimple Product */ +$firstSimple->setStoreId(Store::DEFAULT_STORE_ID) + ->setImage('/m/a/magento_image.jpg') + ->setSmallImage('/m/a/magento_image.jpg') + ->setThumbnail('/m/a/magento_image.jpg') + ->setData( + 'media_gallery', + [ + 'images' => [ + [ + 'file' => '/m/a/magento_image.jpg', + 'position' => 1, + 'label' => 'Image Alt Text', + 'disabled' => 0, + 'media_type' => 'image' + ], + ] + ] + ) + ->setCanSaveCustomOptions(true) + ->save(); +/** @var $secondSimple Product */ +$secondSimple->setStoreId(Store::DEFAULT_STORE_ID) + ->setImage('/m/a/magento_thumbnail.jpg') + ->setSmallImage('/m/a/magento_thumbnail.jpg') + ->setThumbnail('/m/a/magento_thumbnail.jpg') + ->setSwatchImage('/m/a/magento_thumbnail.jpg') + ->setData( + 'media_gallery', + [ + 'images' => [ + [ + 'file' => '/m/a/magento_thumbnail.jpg', + 'position' => 2, + 'label' => 'Thumbnail Image', + 'disabled' => 0, + 'media_type' => 'image' + ], + ] + ] + ) + ->setCanSaveCustomOptions(true) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_child_products_with_images_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_child_products_with_images_rollback.php new file mode 100644 index 0000000000000..11350999ae7aa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_child_products_with_images_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_configurable_rollback.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_image_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price.php new file mode 100644 index 0000000000000..994d1d0f27583 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; +use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; +use Magento\Customer\Model\Group; +use Magento\Store\Api\WebsiteRepositoryInterface; + +require __DIR__ . '/product_configurable_with_custom_option_type_text.php'; + +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +/** @var ProductTierPriceInterfaceFactory $tierPriceFactory */ +$tierPriceFactory = $objectManager->get(ProductTierPriceInterfaceFactory::class); +/** @var ProductTierPriceExtensionFactory $tpExtensionAttributeFactory */ +$tpExtensionAttributeFactory = $objectManager->get(ProductTierPriceExtensionFactory::class); + +$firstSimple = $productRepository->get('simple_10'); +$firstSimple->setSpecialPrice(9); +$productRepository->save($firstSimple); + +$secondSimple = $productRepository->get('simple_20'); +$tierPriceExtensionAttribute = $tpExtensionAttributeFactory->create( + ['data' => ['website_id' => $websiteRepository->get('admin')->getId(), 'percentage_value' => 25]] +); +$tierPrices[] = $tierPriceFactory + ->create(['data' => ['customer_group_id' => Group::CUST_GROUP_ALL, 'qty' => 1]]) + ->setExtensionAttributes($tierPriceExtensionAttribute); +$secondSimple->setTierPrices($tierPrices); +$productRepository->save($secondSimple); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price_rollback.php new file mode 100644 index 0000000000000..5157ec05a8834 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_custom_option_and_simple_tier_price_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_configurable_with_custom_option_type_text_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple.php new file mode 100644 index 0000000000000..dd3a31bb29016 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/configurable_attribute.php'; +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +/** @var ProductExtensionInterfaceFactory $productExtensionAttributes */ +$productExtensionAttributesFactory = $objectManager->get(ProductExtensionInterfaceFactory::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$defaultWebsiteId = $websiteRepository->get('base')->getId(); + +$option = $attribute->getSource()->getOptionId('Option 1'); +$product = $productFactory->create(); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$defaultWebsiteId]) + ->setName('Configurable Option 1') + ->setSku('simple_1') + ->setPrice(10.00) + ->setTestConfigurable($option) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); +$product = $productRepository->save($product); + +$configurableOptions = $optionsFactory->create( + [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => [['label' => 'test', 'attribute_id' => $attribute->getId(), 'value_index' => $option]], + ], + ] +); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?: $productExtensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks([$product->getId()]); + +$configurableProduct = $productFactory->create(); +$configurableProduct->setExtensionAttributes($extensionConfigurableAttributes); +$configurableProduct->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($configurableProduct->getDefaultAttributeSetId()) + ->setWebsiteIds([$defaultWebsiteId]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository->save($configurableProduct); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_rollback.php new file mode 100644 index 0000000000000..165b714caa508 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +foreach (['simple_1', 'configurable'] as $sku) { + try { + $product = $productRepository->get($sku); + $productRepository->delete($product); + } catch (NoSuchEntityException $e) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); +require __DIR__ . '/configurable_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_out_of_stock_children.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_out_of_stock_children.php new file mode 100644 index 0000000000000..5c749584b2917 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_out_of_stock_children.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionFactory; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\CatalogInventory\Api\Data\StockStatusInterface; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/configurable_attribute.php'; +require __DIR__ . '/../../Catalog/_files/category.php'; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('test_configurable'); +$options = $attribute->getOptions(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +$attributeValues = []; +$associatedProductIds = []; +$rootCategoryId = $baseWebsite->getDefaultStore()->getRootCategoryId(); +array_shift($options); + +foreach ($options as $option) { + $product = $productFactory->create(); + $product->setTypeId(ProductType::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Option ' . $option->getLabel()) + ->setSku(strtolower(str_replace(' ', '_', 'simple ' . $option->getLabel()))) + ->setPrice(150) + ->setTestConfigurable($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId, 333]) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => StockStatusInterface::STATUS_OUT_OF_STOCK + ]); + $product = $productRepository->save($product); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], +]; +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$product = $productFactory->create(); +/** @var ProductExtensionFactory $extensionAttributesFactory */ +$extensionAttributesFactory = $objectManager->get(ProductExtensionFactory::class); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?: $extensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId, 333]) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_in_stock' => StockStatusInterface::STATUS_IN_STOCK + ]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_out_of_stock_children_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_out_of_stock_children_rollback.php new file mode 100644 index 0000000000000..d13b7688f6d91 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_out_of_stock_children_rollback.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\ConfigurableProduct\Model\DeleteConfigurableProduct; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var DeleteConfigurableProduct $deleteConfigurableProductService */ +$deleteConfigurableProductService = $objectManager->get(DeleteConfigurableProduct::class); +$deleteConfigurableProductService->execute('configurable'); + +require __DIR__ . '/configurable_attribute_rollback.php'; +require __DIR__ . '/../../Catalog/_files/category_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php new file mode 100644 index 0000000000000..bdf7b1e87d77c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\CatalogInventory\Model\Stock\ItemFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/configurable_attribute.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductExtensionInterfaceFactory $productExtensionFactory */ +$productExtensionFactory = $objectManager->get(ProductExtensionInterfaceFactory::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var AttributeOptionInterface[] $options */ +$options = $attribute->getOptions(); +$associatedProductIds = $attributeValues = []; +$simpleProductsData = [ + ['Simple option 1', 10, 'Black'], + ['Simple option 2', 20, 'White'], +]; +foreach ($options as $option) { + if (!$option->getValue()) { + continue; + } + [$productSku, $productPrice, $productDescription] = array_shift($simpleProductsData); + $product = $productFactory->create(); + $product->isObjectNew(true); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable ' . $option->getLabel()) + ->setSku($productSku) + ->setPrice($productPrice) + ->setTestConfigurable($option->getValue()) + ->setDescription($productDescription) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product = $productRepository->save($product); + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} +$product = $productFactory->create(); +$product->isObjectNew(true); +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable product with two child') + ->setSku('Configurable product') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$configurableOptions = $optionsFactory->create( + [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], + ] +); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?? $productExtensionFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products_rollback.php new file mode 100644 index 0000000000000..b0d9b3d80e11e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +foreach (['Simple option 1', 'Simple option 2', 'Configurable product'] as $sku) { + try { + $productRepository->deleteById($sku); + } catch (NoSuchEntityException $e) { + //Product has deleted. + } +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); +require __DIR__ . '/configurable_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text.php new file mode 100644 index 0000000000000..dc173b1cd7607 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/product_configurable.php'; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductCustomOptionInterfaceFactory $optionRepository */ +$optionRepository = $objectManager->get(ProductCustomOptionInterfaceFactory::class); + +$createdOption = $optionRepository->create([ + 'data' => [ + 'is_require' => 0, + 'sku' => 'option-1', + 'title' => 'Option 1', + 'type' => ProductCustomOptionInterface::OPTION_TYPE_AREA, + 'price' => 15, + 'price_type' => 'fixed', + ] +]); +$createdOption->setProductSku($product->getSku()); +$product->setOptions([$createdOption]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text_rollback.php new file mode 100644 index 0000000000000..c6c17b956ee37 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_custom_option_type_text_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/product_configurable_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php b/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php index a67665c6d3c48..93e7833038a42 100644 --- a/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php +++ b/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php @@ -53,7 +53,13 @@ public function testRenderRestrictMode(): void $this->assertNotEmpty($header = $this->response->getHeader('Content-Security-Policy')); $this->assertEmpty($this->response->getHeader('Content-Security-Policy-Report-Only')); - $this->assertEquals('default-src https://magento.com \'self\';', $header->getFieldValue()); + $contentSecurityPolicyContent = []; + if ($header instanceof \ArrayIterator) { + foreach ($header as $item) { + $contentSecurityPolicyContent[] = $item->getFieldValue(); + } + } + $this->assertEquals(['default-src https://magento.com \'self\';'], $contentSecurityPolicyContent); } /** @@ -73,9 +79,15 @@ public function testRenderRestrictWithReportingMode(): void $this->assertNotEmpty($header = $this->response->getHeader('Content-Security-Policy')); $this->assertEmpty($this->response->getHeader('Content-Security-Policy-Report-Only')); + $contentSecurityPolicyContent = []; + if ($header instanceof \ArrayIterator) { + foreach ($header as $item) { + $contentSecurityPolicyContent[] = $item->getFieldValue(); + } + } $this->assertEquals( - 'default-src https://magento.com \'self\'; report-uri /csp-reports/; report-to report-endpoint;', - $header->getFieldValue() + ['default-src https://magento.com \'self\'; report-uri /csp-reports/; report-to report-endpoint;'], + $contentSecurityPolicyContent ); $this->assertNotEmpty($reportToHeader = $this->response->getHeader('Report-To')); $this->assertNotEmpty($reportData = json_decode("[{$reportToHeader->getFieldValue()}]", true)); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index d76c520ade3b1..4a7cc7591f7aa 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -437,17 +437,6 @@ public function testCartAction() $this->assertContains('<div id="customer_cart_grid1"', $body); } - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testProductReviewsAction() - { - $this->getRequest()->setParam('id', 1); - $this->dispatch('backend/customer/index/productReviews'); - $body = $this->getResponse()->getBody(); - $this->assertContains('<div id="reviewGrid"', $body); - } - /** * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/SessionTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/SessionTest.php index 9497e93dd47b2..4b6d3e3019dc1 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/SessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/SessionTest.php @@ -6,9 +6,12 @@ namespace Magento\Customer\Model; use Magento\Framework\App\PageCache\FormKey; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Session\SidResolverInterface; use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; use Magento\Framework\Stdlib\Cookie\PublicCookieMetadata; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Response\Http as HttpResponse; /** * @magentoDataFixture Magento/Customer/_files/customer.php @@ -29,6 +32,11 @@ class SessionTest extends \PHPUnit\Framework\TestCase /** @var PublicCookieMetadata $cookieMetadata */ protected $cookieMetadata; + /** + * @var HttpResponse + */ + private $response; + protected function setUp() { $this->_customerSession = Bootstrap::getObjectManager()->create( @@ -48,6 +56,7 @@ protected function setUp() 'form_key', $this->cookieMetadata ); + $this->response = Bootstrap::getObjectManager()->get(ResponseInterface::class); } public function testLoginById() @@ -100,4 +109,26 @@ public function testLogoutActionFlushesFormKey() $this->assertNotEquals($beforeKey, $afterKey); } + + /** + * Check that SID is not used in redirects. + * + * @return void + * @magentoConfigFixture current_store web/session/use_frontend_sid 1 + */ + public function testNoSid(): void + { + $this->_customerSession->authenticate(); + $location = (string)$this->response->getHeader('Location'); + $this->assertNotEmpty($location); + $this->assertNotContains(SidResolverInterface::SESSION_ID_QUERY_PARAM .'=', $location); + $beforeAuthUrl = $this->_customerSession->getData('before_auth_url'); + $this->assertNotEmpty($beforeAuthUrl); + $this->assertNotContains(SidResolverInterface::SESSION_ID_QUERY_PARAM .'=', $beforeAuthUrl); + + $this->_customerSession->authenticate('/customer/account'); + $location = (string)$this->response->getHeader('Location'); + $this->assertNotEmpty($location); + $this->assertNotContains(SidResolverInterface::SESSION_ID_QUERY_PARAM .'=', $location); + } } 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 new file mode 100644 index 0000000000000..20a1f4623a1f7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_group_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\Framework\Api\SearchCriteriaBuilder; +use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Customer\Api\Data\GroupInterface; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +$groupRepository = $objectManager->get(GroupRepositoryInterface::class); +/** @var SearchCriteriaBuilder $searchBuilder */ +$searchBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchBuilder->addFilter(GroupInterface::CODE, 'custom_group') + ->create(); +$groups = $groupRepository->getList($searchCriteria) + ->getItems(); +foreach ($groups as $group) { + try { + $groupRepository->delete($group); + } catch (NoSuchEntityException $exception) { + //Group already removed + } +} diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php index 77ceae27e0774..7b5ddc4b9fa5f 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -6,11 +6,17 @@ namespace Magento\CustomerImportExport\Model\Import; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\ImportExport\Model\Import; /** * Test for class \Magento\CustomerImportExport\Model\Import\Customer which covers validation logic + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyPublicMethods) */ class CustomerTest extends \PHPUnit\Framework\TestCase { @@ -82,6 +88,8 @@ public function testImportData() $this->directoryWrite ); + $existingCustomer = $this->getCustomer('CharlesTAlston@teleworm.us', 1); + /** @var $customersCollection \Magento\Customer\Model\ResourceModel\Customer\Collection */ $customersCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Customer\Model\ResourceModel\Customer\Collection::class @@ -107,13 +115,6 @@ public function testImportData() $this->assertEquals($expectAddedCustomers, $addedCustomers, 'Added unexpected amount of customers'); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - $existingCustomer = $objectManager->get( - \Magento\Framework\Registry::class - )->registry('_fixture/Magento_ImportExport_Customer'); - $updatedCustomer = $customers[$existingCustomer->getId()]; $this->assertNotEquals( @@ -154,6 +155,12 @@ public function testImportDataWithOneAdditionalColumn(): void $this->directoryWrite ); + $existingCustomer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Customer\Model\Customer::class + ); + $existingCustomer->setWebsiteId(1); + $existingCustomer = $existingCustomer->loadByEmail('CharlesTAlston@teleworm.us'); + /** @var $customersCollection \Magento\Customer\Model\ResourceModel\Customer\Collection */ $customersCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Customer\Model\ResourceModel\Customer\Collection::class @@ -171,12 +178,6 @@ public function testImportDataWithOneAdditionalColumn(): void $customers = $customersCollection->getItems(); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - $existingCustomer = $objectManager->get(\Magento\Framework\Registry::class) - ->registry('_fixture/Magento_ImportExport_Customer'); - $updatedCustomer = $customers[$existingCustomer->getId()]; $this->assertNotEquals( @@ -210,8 +211,8 @@ public function testImportDataWithOneAdditionalColumn(): void ); $this->assertEquals( - $existingCustomer->getCustomerGroupId(), - $updatedCustomer->getCustomerGroupId(), + $existingCustomer->getGroupId(), + $updatedCustomer->getGroupId(), 'Customer group must not be changed' ); } @@ -352,4 +353,61 @@ public function testValidateEmailForDeleteBehavior() $this->_model->getErrorAggregator()->getErrorsByCode([Customer::ERROR_CUSTOMER_NOT_FOUND]) ); } + + /** + * Test import existing customers + * + * @magentoDataFixture Magento/Customer/_files/import_export/customers.php + * @return void + */ + public function testUpdateExistingCustomers(): void + { + $this->doImport(__DIR__ . '/_files/customers_to_update.csv', Import::BEHAVIOR_ADD_UPDATE); + $customer = $this->getCustomer('customer@example.com', 1); + $this->assertEquals('Firstname-updated', $customer->getFirstname()); + $this->assertEquals('Lastname-updated', $customer->getLastname()); + $this->assertEquals(1, $customer->getStoreId()); + $customer = $this->getCustomer('julie.worrell@example.com', 1); + $this->assertEquals('Julie-updated', $customer->getFirstname()); + $this->assertEquals('Worrell-updated', $customer->getLastname()); + $this->assertEquals(1, $customer->getStoreId()); + $customer = $this->getCustomer('david.lamar@example.com', 1); + $this->assertEquals('David-updated', $customer->getFirstname()); + $this->assertEquals('Lamar-updated', $customer->getLastname()); + $this->assertEquals(1, $customer->getStoreId()); + } + + /** + * Gets customer entity. + * + * @param string $email + * @param int $websiteId + * @return CustomerInterface + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getCustomer(string $email, int $websiteId): CustomerInterface + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var CustomerRepositoryInterface $repository */ + $repository = $objectManager->get(CustomerRepositoryInterface::class); + return $repository->get($email, $websiteId); + } + + /** + * Import using given file and behavior + * + * @param string $file + * @param string $behavior + */ + private function doImport(string $file, string $behavior): void + { + $source = new \Magento\ImportExport\Model\Import\Source\Csv($file, $this->directoryWrite); + $this->_model + ->setParameters(['behavior' => $behavior]) + ->setSource($source) + ->validateData() + ->hasToBeTerminated(); + $this->_model->importData(); + } } diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_update.csv b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_update.csv new file mode 100644 index 0000000000000..b6d8e24860ed3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_update.csv @@ -0,0 +1,4 @@ +email,_website,_store,confirmation,created_at,created_in,default_billing,default_shipping,disable_auto_group_change,dob,firstname,gender,group_id,lastname,middlename,password_hash,prefix,rp_token,rp_token_created_at,store_id,suffix,taxvat,website_id,password +customer@example.com,base,"default",,5/6/2012 16:15,Admin,"1","1",0,,"Firstname-updated",Male,1,"Lastname-updated",T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,"1", +julie.worrell@example.com,base,"",,5/6/2012 16:19,Admin,"1","1",0,,"Julie-updated",Female,1,"Worrell-updated",T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,"1", +david.lamar@example.com,base,"",,5/6/2012 16:25,Admin,"1","1",0,,"David-updated",Male,1,"Lamar-updated",T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,"1", diff --git a/dev/tests/integration/testsuite/Magento/Directory/Block/CurrencyTest.php b/dev/tests/integration/testsuite/Magento/Directory/Block/CurrencyTest.php new file mode 100644 index 0000000000000..30527bc2fa926 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/Block/CurrencyTest.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Directory\Block; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Check currency block behaviour + * + * @see \Magento\Directory\Block\Currency + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class CurrencyTest extends TestCase +{ + private const CURRENCY_SWITCHER_TEMPLATE = 'Magento_Directory::currency.phtml'; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var LayoutInterface */ + private $layout; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + } + + /** + * @magentoConfigFixture current_store currency/options/allow USD + * + * @return void + */ + public function testDefaultCurrencySwitcher(): void + { + $this->assertCurrencySwitcherPerStore(''); + } + + /** + * @magentoConfigFixture current_store currency/options/allow EUR,USD + * + * @return void + */ + public function testCurrencySwitcher(): void + { + $this->assertCurrencySwitcherPerStore('Currency USD - US Dollar EUR - Euro'); + } + + /** + * @magentoConfigFixture current_store currency/options/allow USD,CNY + * @magentoConfigFixture fixturestore_store currency/options/allow USD,UAH + * + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/Directory/_files/usd_cny_rate.php + * @magentoDataFixture Magento/Directory/_files/usd_uah_rate.php + * + * @return void + */ + public function testMultiStoreCurrencySwitcher(): void + { + $this->assertCurrencySwitcherPerStore('Currency USD - US Dollar CNY - Chinese Yuan'); + $this->assertCurrencySwitcherPerStore('Currency USD - US Dollar UAH - Ukrainian Hryvnia', 'fixturestore'); + } + + /** + * Check currency switcher diplaying per stores + * + * @param string $expectedData + * @param string $storeCode + * @return void + */ + private function assertCurrencySwitcherPerStore( + string $expectedData, + string $storeCode = 'default' + ): void { + $currentStore = $this->storeManager->getStore(); + try { + if ($currentStore->getCode() !== $storeCode) { + $this->storeManager->setCurrentStore($storeCode); + } + + $actualData = trim(preg_replace('/\s+/', ' ', strip_tags($this->getBlock()->toHtml()))); + $this->assertEquals($expectedData, $actualData); + } finally { + if ($currentStore->getCode() !== $storeCode) { + $this->storeManager->setCurrentStore($currentStore); + } + } + } + + /** + * Get currency block + * + * @return Currency + */ + private function getBlock(): Currency + { + $block = $this->layout->createBlock(Currency::class); + $block->setTemplate(self::CURRENCY_SWITCHER_TEMPLATE); + + return $block; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Directory/_files/usd_cny_rate.php b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_cny_rate.php new file mode 100644 index 0000000000000..8651f2cc760d2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_cny_rate.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Directory\Model\Currency; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +$rates = ['USD' => ['CNY' => '7.0000']]; +/** @var Currency $currencyModel */ +$currencyModel = $objectManager->create(Currency::class); +$currencyModel->saveRates($rates); diff --git a/dev/tests/integration/testsuite/Magento/Directory/_files/usd_cny_rate_rollback.php b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_cny_rate_rollback.php new file mode 100644 index 0000000000000..c553995e6288c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_cny_rate_rollback.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Directory\Model\RemoveCurrencyRateByCode; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var RemoveCurrencyRateByCode $deleteRateByCode */ +$deleteRateByCode = $objectManager->get(RemoveCurrencyRateByCode::class); +$deleteRateByCode->execute('CNY'); diff --git a/dev/tests/integration/testsuite/Magento/Directory/_files/usd_uah_rate.php b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_uah_rate.php new file mode 100644 index 0000000000000..3bb4bded1979c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_uah_rate.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Directory\Model\Currency; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +$rates = ['USD' => ['UAH' => '24.0000']]; +/** @var Currency $currencyModel */ +$currencyModel = $objectManager->create(Currency::class); +$currencyModel->saveRates($rates); diff --git a/dev/tests/integration/testsuite/Magento/Directory/_files/usd_uah_rate_rollback.php b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_uah_rate_rollback.php new file mode 100644 index 0000000000000..131f533666132 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/_files/usd_uah_rate_rollback.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Directory\Model\RemoveCurrencyRateByCode; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var RemoveCurrencyRateByCode $deleteRateByCode */ +$deleteRateByCode = $objectManager->get(RemoveCurrencyRateByCode::class); +$deleteRateByCode->execute('UAH'); diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php new file mode 100644 index 0000000000000..32fed4730adfc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Downloadable\Api\DomainManagerInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Downloadable\Api\Data\LinkInterfaceFactory; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Downloadable\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Downloadable\Helper\Download; +use Magento\Downloadable\Model\Link; +use Magento\Downloadable\Api\Data\SampleInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Downloadable\Api\Data\LinkInterface; + +$objectManager = Bootstrap::getObjectManager(); + +$storeManager = $objectManager->get(StoreManagerInterface::class); +$storeManager->setCurrentStore($storeManager->getStore('admin')->getId()); + +$domainManager = $objectManager->get(DomainManagerInterface::class); +$domainManager->addDomains( + [ + 'example.com', + 'www.example.com', + 'www.sample.example.com', + 'google.com' + ] +); + +$product = $objectManager->get(ProductInterface::class); +$product + ->setTypeId(Type::TYPE_DOWNLOADABLE) + ->setId(1) + ->setAttributeSetId(4) + ->setName('Downloadable Product') + ->setSku('downloadable-product') + ->setPrice(10) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setLinksPurchasedSeparately(true) + ->setLinksTitle('Links') + ->setSamplesTitle('Samples') + ->setStockData( + [ + 'qty' => 100, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ] + ); + +$linkFactory = $objectManager->get(LinkInterfaceFactory::class); +/** @var LinkInterface $link */ +$link = $linkFactory->create(); +$link->setTitle('Downloadable Product Link'); +$link->setIsShareable(Link::LINK_SHAREABLE_CONFIG); +$link->setLinkUrl('http://example.com/downloadable.txt'); +$link->setLinkType(Download::LINK_TYPE_URL); +$link->setStoreId($product->getStoreId()); +$link->setWebsiteId($product->getStore()->getWebsiteId()); +$link->setProductWebsiteIds($product->getWebsiteIds()); +$link->setSortOrder(1); +$link->setPrice(0); +$link->setNumberOfDownloads(0); + +$sampleFactory = $objectManager->get(SampleInterfaceFactory::class); +$sample = $sampleFactory->create(); +$sample->setTitle('Downloadable Product Sample') + ->setSampleType(Download::LINK_TYPE_URL) + ->setSampleUrl('http://example.com/downloadable.txt') + ->setStoreId($product->getStoreId()) + ->setWebsiteId($product->getStore()->getWebsiteId()) + ->setProductWebsiteIds($product->getWebsiteIds()) + ->setSortOrder(10); + +$extension = $product->getExtensionAttributes(); +$extension->setDownloadableProductLinks([$link]); +$extension->setDownloadableProductSamples([$sample]); +$product->setExtensionAttributes($extension); + +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url_rollback.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url_rollback.php new file mode 100644 index 0000000000000..9a2e1c74fcd33 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url_rollback.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Downloadable\Api\DomainManagerInterface; +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var DomainManagerInterface $domainManager */ +$domainManager = $objectManager->get(DomainManagerInterface::class); +$domainManager->removeDomains( + [ + 'example.com', + 'www.example.com', + 'www.sample.example.com', + 'google.com' + ] +); + +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('downloadable-product', false, null, true); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { // @codingStandardsIgnoreLine +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php b/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php index d0e4471e2ea68..861be98c13e72 100644 --- a/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php +++ b/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php @@ -6,7 +6,11 @@ namespace Magento\DownloadableImportExport\Model; use Magento\CatalogImportExport\Model\AbstractProductExportImportTestCase; +use Magento\Catalog\Model\Product; +/** + * Test export and import downloadable products + */ class DownloadableTest extends AbstractProductExportImportTestCase { /** @@ -17,15 +21,7 @@ public function exportImportDataProvider(): array return [ 'downloadable-product' => [ [ - 'Magento/Downloadable/_files/product_downloadable.php' - ], - [ - 'downloadable-product', - ], - ], - 'downloadable-product-with-files' => [ - [ - 'Magento/Downloadable/_files/product_downloadable_with_files.php' + 'Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php' ], [ 'downloadable-product', @@ -46,43 +42,54 @@ public function exportImportDataProvider(): array * @param string[] $skippedAttributes * @return void * @dataProvider exportImportDataProvider - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function testImportExport(array $fixtures, array $skus, array $skippedAttributes = []): void { - $this->markTestSkipped('Uncomment after MAGETWO-38240 resolved'); + $skippedAttributes = array_merge(self::$skippedAttributes, ['downloadable_links']); + parent::testImportExport($fixtures, $skus, $skippedAttributes); } /** * @inheritdoc */ protected function assertEqualsSpecificAttributes( - \Magento\Catalog\Model\Product $expectedProduct, - \Magento\Catalog\Model\Product $actualProduct + Product $expectedProduct, + Product $actualProduct ): void { - $expectedProductLinks = $expectedProduct->getExtensionAttributes()->getDownloadableProductLinks(); + $expectedProductLinks = $expectedProduct->getExtensionAttributes()->getDownloadableProductLinks(); $expectedProductSamples = $expectedProduct->getExtensionAttributes()->getDownloadableProductSamples(); - $actualProductLinks = $actualProduct->getExtensionAttributes()->getDownloadableProductLinks(); + $actualProductLinks = $actualProduct->getExtensionAttributes()->getDownloadableProductLinks(); $actualProductSamples = $actualProduct->getExtensionAttributes()->getDownloadableProductSamples(); $this->assertEquals(count($expectedProductLinks), count($actualProductLinks)); $this->assertEquals(count($expectedProductSamples), count($actualProductSamples)); - - $expectedLinksArray = []; - foreach ($expectedProductLinks as $link) { - $expectedLinksArray[] = $link->getData(); + $actualLinks = $this->getDataWithSortingById($actualProductLinks); + $expectedLinks = $this->getDataWithSortingById($actualProductLinks); + foreach ($actualLinks as $key => $actualLink) { + $this->assertEquals($expectedLinks[$key], $actualLink); } - foreach ($actualProductLinks as $actualLink) { - $this->assertContains($expectedLinksArray, $actualLink->getData()); + $actualSamples = $this->getDataWithSortingById($actualProductSamples); + $expectedSamples = $this->getDataWithSortingById($expectedProductSamples); + foreach ($actualSamples as $key => $actualSample) { + $this->assertEquals($expectedSamples[$key], $actualSample); } + } - $expectedSamplesArray = []; - foreach ($expectedProductSamples as $sample) { - $expectedSamplesArray[] = $sample->getData(); - } - foreach ($actualProductSamples as $actualSample) { - $this->assertContains($expectedSamplesArray, $actualSample->getData()); + /** + * Get data with sorting by id + * + * @param array $objects + * + * @return array + */ + private function getDataWithSortingById(array $objects) + { + $result = []; + foreach ($objects as $object) { + $result[$object->getId()] = $object->getData(); } + + return $result; } } diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DateTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DateTest.php index cb75d3e0d4a8e..9057415cf5248 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DateTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DateTest.php @@ -7,19 +7,20 @@ namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with input type date. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class DateTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Date::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Date::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Date::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Date::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DateTimeTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DateTimeTest.php new file mode 100644 index 0000000000000..e80a29877a508 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DateTimeTest.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; + +/** + * Test cases related to create attribute with input type datetime. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class DateTimeTest extends AbstractSaveAttributeTest +{ + /** + * Test create attribute and compare attribute data and input data. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DateTime::getAttributeDataWithCheckArray + * @magentoConfigFixture default/general/locale/timezone UTC + * + * @param array $attributePostData + * @param array $checkArray + * @return void + */ + public function testCreateAttribute(array $attributePostData, array $checkArray): void + { + $this->createAttributeUsingDataAndAssert($attributePostData, $checkArray); + } + + /** + * Test create attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DateTime::getAttributeDataWithErrorMessage + * + * @param array $attributePostData + * @param string $errorMessage + * @return void + */ + public function testCreateAttributeWithError(array $attributePostData, string $errorMessage): void + { + $this->createAttributeUsingDataWithErrorAndAssert($attributePostData, $errorMessage); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DropDownTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DropDownTest.php index 1a3f363832d6e..070dc850057cf 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DropDownTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/DropDownTest.php @@ -7,19 +7,20 @@ namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with input type dropdown. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class DropDownTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/MultipleSelectTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/MultipleSelectTest.php index 1c0f5ea720f70..6f51546a5d62d 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/MultipleSelectTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/MultipleSelectTest.php @@ -7,19 +7,20 @@ namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with input type multiselect. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class MultipleSelectTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextAreaTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextAreaTest.php index 9c5b1a8587674..c315f61b89148 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextAreaTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextAreaTest.php @@ -7,19 +7,20 @@ namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with input type text_area. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class TextAreaTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextArea::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextArea::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextArea::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextArea::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextEditorTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextEditorTest.php index 807e0cfd570b2..36e95550a562a 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextEditorTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextEditorTest.php @@ -7,19 +7,20 @@ namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with input type text_editor. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class TextEditorTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextEditor::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextEditor::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextEditor::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextEditor::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextTest.php index 70069dcedd0e4..3a7747f59939a 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/TextTest.php @@ -7,19 +7,20 @@ namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with input type text. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class TextTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Text::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Text::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Text::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Text::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/YesNoTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/YesNoTest.php index 7bb26556c3fd6..28ba04465b870 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/YesNoTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Save/InputType/YesNoTest.php @@ -7,19 +7,20 @@ namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with yes/no input type. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class YesNoTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\YesNo::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\YesNo::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\YesNo::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\YesNo::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DateTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DateTest.php new file mode 100644 index 0000000000000..5df39674d05c8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DateTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type date. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class DateTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Date::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/product_date_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('date_attribute', $postData); + $this->assertUpdateAttributeProcess('date_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Date::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_date_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('date_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Date::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_date_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('date_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DateTimeTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DateTimeTest.php new file mode 100644 index 0000000000000..2a6f730baf624 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DateTimeTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type date. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class DateTimeTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DateTime::getUpdateProvider + * @magentoConfigFixture default/general/locale/timezone UTC + * @magentoDataFixture Magento/Catalog/_files/product_datetime_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('datetime_attribute', $postData); + $this->assertUpdateAttributeProcess('datetime_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DateTime::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_datetime_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('datetime_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DateTime::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_datetime_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('datetime_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DropDownTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DropDownTest.php new file mode 100644 index 0000000000000..4b3fb2cf6fac9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/DropDownTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type dropdown. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class DropDownTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('dropdown_attribute', $postData); + $this->assertUpdateAttributeProcess('dropdown_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('dropdown_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('dropdown_attribute', $postData, $expectedData); + } + + /** + * Test update attribute options on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\DropDown::getUpdateOptionsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + * + * @param array $postData + * @return void + */ + public function testUpdateOptionsOnStores(array $postData): void + { + $this->processUpdateOptionsOnStores('dropdown_attribute', $postData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/MultipleSelectTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/MultipleSelectTest.php new file mode 100644 index 0000000000000..fa8b63a2d034c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/MultipleSelectTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type multiselect. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class MultipleSelectTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('multiselect_attribute', $postData); + $this->assertUpdateAttributeProcess('multiselect_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('multiselect_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('multiselect_attribute', $postData, $expectedData); + } + + /** + * Test update attribute options on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\MultipleSelect::getUpdateOptionsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute.php + * + * @param array $postData + * @return void + */ + public function testUpdateOptionsOnStores(array $postData): void + { + $this->processUpdateOptionsOnStores('multiselect_attribute', $postData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextAreaTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextAreaTest.php new file mode 100644 index 0000000000000..708de8dec916c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextAreaTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type text_area. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class TextAreaTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextArea::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/product_text_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('text_attribute', $postData); + $this->assertUpdateAttributeProcess('text_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextArea::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_text_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('text_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextArea::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_text_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('text_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextEditorTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextEditorTest.php new file mode 100644 index 0000000000000..14e0f84741782 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextEditorTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type text_editor. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class TextEditorTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextEditor::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/product_text_editor_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('text_editor_attribute', $postData); + $this->assertUpdateAttributeProcess('text_editor_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextEditor::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_text_editor_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('text_editor_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\TextEditor::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_text_editor_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('text_editor_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextTest.php new file mode 100644 index 0000000000000..fb025e4280c96 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/TextTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type text. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class TextTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Text::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/product_varchar_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('varchar_attribute', $postData); + $this->assertUpdateAttributeProcess('varchar_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Text::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_varchar_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('varchar_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\Text::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_varchar_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('varchar_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/YesNoTest.php b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/YesNoTest.php new file mode 100644 index 0000000000000..c2baeba182836 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Controller/Adminhtml/Product/Attribute/Update/InputType/YesNoTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with yes/no input type. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class YesNoTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\YesNo::getUpdateProvider + * @magentoDataFixture Magento/Catalog/_files/product_boolean_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('boolean_attribute', $postData); + $this->assertUpdateAttributeProcess('boolean_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\YesNo::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Catalog/_files/product_boolean_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('boolean_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Eav\Model\Attribute\DataProvider\YesNo::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Catalog/_files/product_boolean_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('boolean_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/ConfigurableProduct/Model/QuickSearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/ConfigurableProduct/Model/QuickSearchTest.php new file mode 100644 index 0000000000000..50cb4974a9cf1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch6/ConfigurableProduct/Model/QuickSearchTest.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch6\ConfigurableProduct\Model; + +use Magento\ConfigurableProduct\Model\QuickSearchTest as ConfigurableProductQuickSearchTest; + +/** + * Test cases related to find configurable product via quick search using Elasticsearch 6.0+ search engine. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php + * + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + */ +class QuickSearchTest extends ConfigurableProductQuickSearchTest +{ + /** + * Assert that configurable child products has not found by query using Elasticsearch 6.0+ search engine. + * + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod + * + * @magentoConfigFixture default/catalog/search/engine elasticsearch6 + * + * @return void + */ + public function testChildProductsHasNotFoundedByQuery(): void + { + parent::testChildProductsHasNotFoundedByQuery(); + } + + /** + * Assert that child product of configurable will be available by search after + * set to product visibility by catalog and search using Elasticsearch 6.0+ search engine. + * + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod + * + * @magentoConfigFixture default/catalog/search/engine elasticsearch6 + * + * @dataProvider productAvailabilityInSearchByVisibilityDataProvider + * + * @param int $visibility + * @param bool $expectedResult + * @return void + */ + public function testOneOfChildIsAvailableBySearch(int $visibility, bool $expectedResult): void + { + parent::testOneOfChildIsAvailableBySearch($visibility, $expectedResult); + } + + /** + * Assert that configurable product was found by option value using Elasticsearch 6.0+ search engine. + * + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod + * + * @magentoConfigFixture default/catalog/search/engine elasticsearch6 + * + * @return void + */ + public function testSearchByOptionValue(): void + { + parent::testSearchByOptionValue(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php index 8183a5878ba85..cb338d00cab16 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php @@ -5,11 +5,22 @@ */ namespace Magento\Framework\App\Response\HeaderProvider; +use Magento\Framework\App\Response\Http as HttpResponse; +use Zend\Http\Header\HeaderInterface; + +/** + * Class AbstractHeaderTestCase + */ abstract class AbstractHeaderTestCase extends \Magento\TestFramework\TestCase\AbstractController { - /** @var \Magento\Framework\App\Response\Http */ + /** + * @var HttpResponse + */ private $interceptedResponse; + /** + * @inheritDoc + */ public function setUp() { parent::setUp(); @@ -17,12 +28,11 @@ public function setUp() [ 'preferences' => [ - \Magento\Framework\App\Response\Http::class => - \Magento\Framework\App\Response\Http\Interceptor::class + HttpResponse::class => 'Magento\Framework\App\Response\Http\Interceptor' ] ] ); - $this->interceptedResponse = $this->_objectManager->create(\Magento\Framework\App\Response\Http::class); + $this->interceptedResponse = $this->_objectManager->create(HttpResponse::class); } /** @@ -33,16 +43,30 @@ public function setUp() */ protected function assertHeaderPresent($name, $value) { + $value = [$value]; $this->interceptedResponse->sendResponse(); - $header = $this->interceptedResponse->getHeader($name); - $this->assertTrue(is_subclass_of($header, \Zend\Http\Header\HeaderInterface::class, false)); + + $headerContent = []; + if ($header instanceof \ArrayIterator) { + foreach ($header as $item) { + $headerContent[] = $item->getFieldValue(); + } + } elseif ($header instanceof HeaderInterface) { + $headerContent[] = $header->getFieldValue(); + } + $this->assertSame( $value, - $header->getFieldValue() + $headerContent ); } + /** + * Assert is no header. + * + * @param string $name + */ protected function assertHeaderNotPresent($name) { $this->interceptedResponse->sendResponse(); diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/UpgradeInsecureTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/UpgradeInsecureTest.php index 18d58bea14b93..8b621c1d65974 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/UpgradeInsecureTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/UpgradeInsecureTest.php @@ -14,7 +14,7 @@ class UpgradeInsecureTest extends AbstractHeaderTestCase */ public function testHeaderPresent() { - $this->assertHeaderPresent('Content-Security-Policy', 'upgrade-insecure-requests'); + $this->assertHeaderPresent('Content-Security-Policy', 'upgrade-insecure-requests;'); } /** diff --git a/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php b/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php index 3205c19445ee1..482a3c9cbd619 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php @@ -52,7 +52,7 @@ function ini_set($varName, $newValue) SessionManagerTest::$isIniSetInvoked[$varName] = $newValue; return true; } - return call_user_func_array('\ini_set', func_get_args()); + return call_user_func_array('\ini_set', [$varName, $newValue]); } /** @@ -213,15 +213,16 @@ public function testDestroy() public function testSetSessionId() { $this->initializeModel(); - $sessionId = $this->model->getSessionId(); - $this->appState->expects($this->atLeastOnce()) + $this->assertNotEmpty($this->model->getSessionId()); + $this->appState->expects($this->any()) ->method('getAreaCode') ->willReturn(\Magento\Framework\App\Area::AREA_FRONTEND); - $this->model->setSessionId($this->sidResolver->getSid($this->model)); - $this->assertEquals($sessionId, $this->model->getSessionId()); $this->model->setSessionId('test'); $this->assertEquals('test', $this->model->getSessionId()); + /* Use not valid identifier */ + $this->model->setSessionId('test_id'); + $this->assertEquals('test', $this->model->getSessionId()); } /** @@ -230,17 +231,14 @@ public function testSetSessionId() public function testSetSessionIdFromParam() { $this->initializeModel(); - $this->appState->expects($this->atLeastOnce()) + $this->appState->expects($this->any()) ->method('getAreaCode') ->willReturn(\Magento\Framework\App\Area::AREA_FRONTEND); + $currentId = $this->model->getSessionId(); $this->assertNotEquals('test_id', $this->model->getSessionId()); - $this->request->getQuery()->set($this->sidResolver->getSessionIdQueryParam($this->model), 'test-id'); - $this->model->setSessionId($this->sidResolver->getSid($this->model)); - $this->assertEquals('test-id', $this->model->getSessionId()); - /* Use not valid identifier */ - $this->request->getQuery()->set($this->sidResolver->getSessionIdQueryParam($this->model), 'test_id'); + $this->request->getQuery()->set(SidResolverInterface::SESSION_ID_QUERY_PARAM, 'test-id'); $this->model->setSessionId($this->sidResolver->getSid($this->model)); - $this->assertEquals('test-id', $this->model->getSessionId()); + $this->assertEquals($currentId, $this->model->getSessionId()); } public function testGetSessionIdForHost() diff --git a/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php b/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php index db830d228201c..081857348d7da 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php @@ -477,6 +477,8 @@ public function testGetDirectUrl() } /** + * Check that SID is removed from URL. + * * Note: isolation flushes the URL memory cache * @magentoAppIsolation enabled * @@ -485,11 +487,8 @@ public function testGetDirectUrl() */ public function testSessionUrlVar() { - $sessionId = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\Session\Generic::class - )->getSessionId(); $sessionUrl = $this->model->sessionUrlVar('<a href="http://example.com/?___SID=U">www.example.com</a>'); - $this->assertEquals('<a href="http://example.com/?SID=' . $sessionId . '">www.example.com</a>', $sessionUrl); + $this->assertEquals('<a href="http://example.com/">www.example.com</a>', $sessionUrl); } public function testUseSessionIdForUrl() diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php index 5f0c7176bce7a..a398207c9e649 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php @@ -5,15 +5,19 @@ */ namespace Magento\Framework\View\Element; -use Magento\Framework\View\Element\AbstractBlock; +use Magento\Framework\Math\Random; +use Magento\Framework\Session\SessionManagerInterface; +use Magento\Framework\Session\SidResolverInterface; +use Magento\TestFramework\Helper\Bootstrap; /** * @magentoAppIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AbstractBlockTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\View\Element\AbstractBlock + * @var AbstractBlock */ protected $_block; @@ -24,22 +28,29 @@ class AbstractBlockTest extends \PHPUnit\Framework\TestCase protected static $_mocks = []; + /** + * @var SessionManagerInterface + */ + private $session; + protected function setUp() { - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\App\State::class) - ->setAreaCode('frontend'); - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\View\DesignInterface::class - )->setDefaultDesignTheme(); + /** @var \Magento\Framework\App\State $state */ + $state = Bootstrap::getObjectManager()->get(\Magento\Framework\App\State::class); + $state->setAreaCode('frontend'); + /** @var \Magento\Framework\View\DesignInterface $design */ + $design = Bootstrap::getObjectManager()->get(\Magento\Framework\View\DesignInterface::class); + $design->setDefaultDesignTheme(); $this->_block = $this->getMockForAbstractClass( - \Magento\Framework\View\Element\AbstractBlock::class, + AbstractBlock::class, [ - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Framework\View\Element\Context::class ), ['module_name' => 'Magento_Theme'] ] ); + $this->session = Bootstrap::getObjectManager()->get(SessionManagerInterface::class); } /** @@ -113,9 +124,9 @@ public function testSetGetNameInLayout() ); $layout->createBlock(\Magento\Framework\View\Element\Template::class, $name); $block = $layout->getBlock($name); - $this->assertInstanceOf(\Magento\Framework\View\Element\AbstractBlock::class, $block); + $this->assertInstanceOf(AbstractBlock::class, $block); $block->setNameInLayout($name); - $this->assertInstanceOf(\Magento\Framework\View\Element\AbstractBlock::class, $layout->getBlock($name)); + $this->assertInstanceOf(AbstractBlock::class, $layout->getBlock($name)); $this->assertEquals($name, $block->getNameInLayout()); $this->assertTrue($layout->hasElement($name)); $newName = 'new_name'; @@ -549,7 +560,7 @@ public function testGetCacheKeyInfo() public function testGetCacheKey() { $name = uniqid('block.'); - $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + $block = Bootstrap::getObjectManager()->get( \Magento\Framework\View\LayoutInterface::class )->createBlock( \Magento\Framework\View\Element\Text::class @@ -564,6 +575,35 @@ public function testGetCacheKey() $this->assertEquals(AbstractBlock::CACHE_KEY_PREFIX . 'key', $block->getCacheKey()); } + /** + * Check that SIDs inside blocks are not being replaced. + * + * @return void + * @magentoCache block_html enabled + */ + public function testNoSid(): void + { + $blockId = 'block-with-sid' .Random::getRandomNumber(1, 9999); + $block = $this->_createBlockWithLayout( + $blockId, + $blockId, + \Magento\Framework\View\Element\Text::class + ); + $outerId = 'block-outer' .Random::getRandomNumber(1, 9999); + $outer = $this->_createBlockWithLayout($outerId, $outerId); + $block->setText( + $text = 'Some text with ' .SidResolverInterface::SESSION_ID_QUERY_PARAM + .'=' .$this->session->getSessionId() + ); + $block->setData('cache_lifetime', 3600); + //Caching the block's content + $outer->getBlockHtml($blockId); + //New ID generated, must not be replace in block's content. + $this->session->regenerateId(); + $html = $outer->getBlockHtml($blockId); + $this->assertEquals($text, $html); + } + /** * Create <N> sample blocks * @@ -610,7 +650,7 @@ protected function _createSampleBlocks( protected function _createBlockWithLayout( $name = 'block', $alias = null, - $type = \Magento\Framework\View\Element\AbstractBlock::class + $type = AbstractBlock::class ) { $typePart = explode('\\', $type); $mockClass = array_pop($typePart) . 'Mock'; @@ -618,7 +658,7 @@ protected function _createBlockWithLayout( self::$_mocks[$mockClass] = $this->getMockForAbstractClass( $type, [ - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Framework\View\Element\Context::class ), ['module_name' => 'Magento_Theme'] @@ -627,7 +667,7 @@ protected function _createBlockWithLayout( ); } if ($this->_layout === null) { - $this->_layout = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + $this->_layout = Bootstrap::getObjectManager()->get( \Magento\Framework\View\LayoutInterface::class ); } diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Export/Adapter/CsvTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Export/Adapter/CsvTest.php new file mode 100644 index 0000000000000..b874e55aea9ed --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Export/Adapter/CsvTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\ImportExport\Model\Export\Adapter; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for Export adapter csv + */ +class CsvTest extends TestCase +{ + /** + * @var string Destination file name + */ + private $destination = 'destinationFile'; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Csv + */ + private $csv; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->csv = $this->objectManager->create( + Csv::class, + ['destination' => $this->destination] + ); + } + + /** + * Test to destruct export adapter + */ + public function testDestruct(): void + { + /** @var Filesystem $fileSystem */ + $fileSystem = $this->objectManager->get(Filesystem::class); + $directoryHandle = $fileSystem->getDirectoryRead(DirectoryList::VAR_DIR); + /** Assert that the destination file is present after construct */ + $this->assertFileExists( + $directoryHandle->getAbsolutePath($this->destination), + 'The destination file was\'t created after construct' + ); + /** Assert that the destination file was removed after destruct */ + $this->csv = null; + $this->assertFileNotExists( + $directoryHandle->getAbsolutePath($this->destination), + 'The destination file was\'t removed after destruct' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php index fed8c76852872..cf39757cb8264 100644 --- a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php @@ -182,8 +182,12 @@ protected function updateAttribute( array $data ): void { $attribute = $this->attributeRepository->get($this->getAttributeCode()); + $attribute->setDataChanges(false); $attribute->addData($data); - $this->attributeRepository->save($attribute); + + if ($attribute->hasDataChanges()) { + $this->attributeRepository->save($attribute); + } } /** @@ -223,8 +227,11 @@ protected function updateProducts( foreach ($products as $productSku => $stringValue) { $product = $this->productRepository->get($productSku, false, $storeId, true); + $productValue = $attribute->usesSource() + ? $attribute->getSource()->getOptionId($stringValue) + : $stringValue; $product->addData( - [$attribute->getAttributeCode() => $attribute->getSource()->getOptionId($stringValue)] + [$attribute->getAttributeCode() => $productValue] ); $this->productRepository->save($product); } diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Configurable/PriceFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Configurable/PriceFilterTest.php new file mode 100644 index 0000000000000..9dcc713bc4867 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/Configurable/PriceFilterTest.php @@ -0,0 +1,157 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Category\Configurable; + +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Framework\Module\Manager; +use Magento\LayeredNavigation\Block\Navigation\AbstractFiltersTest; +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Filter\Item; +use Magento\Store\Model\Store; + +/** + * Provides price filter tests for configurable in navigation block on category page. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class PriceFilterTest extends AbstractFiltersTest +{ + /** + * @var Manager + */ + private $moduleManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->moduleManager = $this->objectManager->get(Manager::class); + //This check is needed because LayeredNavigation independent of Magento_ConfigurableProduct + if (!$this->moduleManager->isEnabled('Magento_ConfigurableProduct')) { + $this->markTestSkipped('Magento_ConfigurableProduct module disabled.'); + } + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_category.php + * @magentoDataFixture Magento/Catalog/_files/category_product.php + * @magentoConfigFixture current_store catalog/layered_navigation/price_range_calculation manual + * @magentoConfigFixture current_store catalog/layered_navigation/price_range_step 10 + * @dataProvider getFiltersDataProvider + * @param array $products + * @param array $expectation + * @return void + */ + public function testGetFilters(array $products, array $expectation): void + { + $this->updateProductData($products); + $this->getCategoryFiltersAndAssert([], ['is_filterable' => '1'], $expectation, 'Category 1'); + } + + /** + * @return array + */ + public function getFiltersDataProvider(): array + { + return [ + 'all_children_active' => [ + 'products_data' => [ + 'simple333' => ['price' => 60.00], + ], + 'expectation' => [ + [ + 'label' => '<span class="price">$10.00</span> - <span class="price">$19.99</span>', + 'value' => '10-20', + 'count' => 1, + ], + [ + 'label' => '<span class="price">$60.00</span> and above', + 'value' => '60-', + 'count' => 1, + ], + ], + ], + 'one_child_disabled' => [ + 'products_data' => [ + 'simple333' => ['price' => 50.00], + 'simple_10' => ['status' => Status::STATUS_DISABLED], + ], + 'expectation' => [ + [ + 'label' => '<span class="price">$20.00</span> - <span class="price">$29.99</span>', + 'value' => '20-30', + 'count' => 1, + ], + [ + 'label' => '<span class="price">$50.00</span> and above', + 'value' => '50-', + 'count' => 1, + ], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_CATEGORY; + } + + /** + * @inheritdoc + */ + protected function getAttributeCode(): string + { + return 'price'; + } + + /** + * @inheritdoc + */ + protected function prepareFilterItems(AbstractFilter $filter): array + { + $items = []; + /** @var Item $item */ + foreach ($filter->getItems() as $item) { + $item = [ + 'label' => __($item->getData('label'))->render(), + 'value' => $item->getData('value'), + 'count' => $item->getData('count'), + ]; + $items[] = $item; + } + + return $items; + } + + /** + * Updates products data. + * + * @param array $products + * @param int $storeId + * @return void + */ + private function updateProductData( + array $products, + int $storeId = Store::DEFAULT_STORE_ID + ): void { + foreach ($products as $productSku => $data) { + $product = $this->productRepository->get($productSku, false, $storeId, true); + $product->addData($data); + $this->productRepository->save($product); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php index eb4148d77b21e..f84cd5ba08259 100644 --- a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/DecimalFilterTest.php @@ -71,25 +71,6 @@ protected function prepareFilterItems(AbstractFilter $filter): array return $items; } - /** - * @inheritdoc - */ - protected function updateProducts( - array $products, - string $attributeCode, - int $storeId = Store::DEFAULT_STORE_ID - ): void { - $attribute = $this->attributeRepository->get($attributeCode); - - foreach ($products as $productSku => $value) { - $product = $this->productRepository->get($productSku, false, $storeId, true); - $product->addData( - [$attribute->getAttributeCode() => $value] - ); - $this->productRepository->save($product); - } - } - /** * @return array */ diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/OutOfStockProductsFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/OutOfStockProductsFilterTest.php new file mode 100644 index 0000000000000..9a0deedea94ab --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/OutOfStockProductsFilterTest.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Category; + +use Magento\Catalog\Model\Layer\Resolver; +use Magento\CatalogInventory\Model\Configuration; +use Magento\Framework\App\Config\MutableScopeConfigInterface; +use Magento\Framework\App\ScopeInterface; +use Magento\LayeredNavigation\Block\Navigation\AbstractFiltersTest; +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Store\Model\ScopeInterface as StoreScope; + +/** + * Provides tests for select filter in navigation block on category page with out of stock products + * and enabled out of stock products displaying. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class OutOfStockProductsFilterTest extends AbstractFiltersTest +{ + /** + * @var MutableScopeConfigInterface + */ + private $scopeConfig; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->scopeConfig = $this->objectManager->get(MutableScopeConfigInterface::class); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_dropdown_attribute.php + * @magentoDataFixture Magento/Catalog/_files/out_of_stock_product_with_category.php + * @magentoDataFixture Magento/Catalog/_files/product_with_category.php + * @dataProvider getFiltersWithOutOfStockProduct + * @param int $showOutOfStock + * @param array $expectation + * @return void + */ + public function testGetFiltersWithOutOfStockProduct(int $showOutOfStock, array $expectation): void + { + $this->updateConfigShowOutOfStockFlag($showOutOfStock); + $this->getCategoryFiltersAndAssert( + ['out-of-stock-product' => 'Option 1', 'in-stock-product' => 'Option 2'], + ['is_filterable' => AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS], + $expectation, + 'Category 1' + ); + } + + /** + * @return array + */ + public function getFiltersWithOutOfStockProduct(): array + { + return [ + 'show_out_of_stock' => [ + 'show_out_of_stock' => 1, + 'expectation' => [['label' => 'Option 1', 'count' => 1], ['label' => 'Option 2', 'count' => 1]], + ], + 'not_show_out_of_stock' => [ + 'show_out_of_stock' => 0, + 'expectation' => [['label' => 'Option 2', 'count' => 1]], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_CATEGORY; + } + + /** + * @inheritdoc + */ + protected function getAttributeCode(): string + { + return 'dropdown_attribute'; + } + + /** + * Updates store config 'cataloginventory/options/show_out_of_stock' flag. + * + * @param int $showOutOfStock + * @return void + */ + protected function updateConfigShowOutOfStockFlag(int $showOutOfStock): void + { + $this->scopeConfig->setValue( + Configuration::XML_PATH_SHOW_OUT_OF_STOCK, + $showOutOfStock, + StoreScope::SCOPE_STORE, + ScopeInterface::SCOPE_DEFAULT + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/PriceFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/PriceFilterTest.php new file mode 100644 index 0000000000000..a82b4bf0fd00d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Category/PriceFilterTest.php @@ -0,0 +1,212 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Category; + +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Framework\App\Config\MutableScopeConfigInterface; +use Magento\Framework\App\ScopeInterface; +use Magento\LayeredNavigation\Block\Navigation\AbstractFiltersTest; +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Filter\Item; +use Magento\Store\Model\ScopeInterface as StoreScope; +use Magento\Store\Model\Store; + +/** + * Provides price filter tests with different price ranges calculation in navigation block on category page. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class PriceFilterTest extends AbstractFiltersTest +{ + /** + * @var MutableScopeConfigInterface + */ + private $scopeConfig; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->scopeConfig = $this->objectManager->get(MutableScopeConfigInterface::class); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_with_three_products.php + * @dataProvider getFiltersDataProvider + * @param array $config + * @param array $products + * @param array $expectation + * @return void + */ + public function testGetFilters(array $config, array $products, array $expectation): void + { + $this->applyCatalogConfig($config); + $this->getCategoryFiltersAndAssert( + $products, + ['is_filterable' => AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS], + $expectation, + 'Category 999' + ); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function getFiltersDataProvider(): array + { + return [ + 'auto_calculation_variation_with_small_price_difference' => [ + 'config' => ['catalog/layered_navigation/price_range_calculation' => 'auto'], + 'products_data' => ['simple1000' => 10.00, 'simple1001' => 20.00, 'simple1002' => 50.00], + 'expectation' => [ + ['label' => '$10.00 - $19.99', 'value' => '10-20', 'count' => 1], + ['label' => '$20.00 - $29.99', 'value' => '20-30', 'count' => 1], + ['label' => '$50.00 and above', 'value' => '50-', 'count' => 1], + ], + ], + 'auto_calculation_variation_with_big_price_difference' => [ + 'config' => ['catalog/layered_navigation/price_range_calculation' => 'auto'], + 'products_data' => ['simple1000' => 10.00, 'simple1001' => 20.00, 'simple1002' => 300.00], + 'expectation' => [ + ['label' => '$0.00 - $99.99', 'value' => '-100', 'count' => 2], + ['label' => '$300.00 and above', 'value' => '300-', 'count' => 1], + ], + ], + 'auto_calculation_variation_with_fixed_price_step' => [ + 'config' => ['catalog/layered_navigation/price_range_calculation' => 'auto'], + 'products_data' => ['simple1000' => 300.00, 'simple1001' => 400.00, 'simple1002' => 500.00], + 'expectation' => [ + ['label' => '$300.00 - $399.99', 'value' => '300-400', 'count' => 1], + ['label' => '$400.00 - $499.99', 'value' => '400-500', 'count' => 1], + ['label' => '$500.00 and above', 'value' => '500-', 'count' => 1], + ], + ], + 'improved_calculation_variation_with_small_price_difference' => [ + 'config' => [ + 'catalog/layered_navigation/price_range_calculation' => 'improved', + 'catalog/layered_navigation/interval_division_limit' => 3, + ], + 'products_data' => ['simple1000' => 10.00, 'simple1001' => 20.00, 'simple1002' => 50.00], + 'expectation' => [ + ['label' => '$0.00 - $49.99', 'value' => '-50', 'count' => 2], + ['label' => '$50.00 and above', 'value' => '50-', 'count' => 1], + ], + ], + 'improved_calculation_variation_with_big_price_difference' => [ + 'config' => [ + 'catalog/layered_navigation/price_range_calculation' => 'improved', + 'catalog/layered_navigation/interval_division_limit' => 3, + ], + 'products_data' => ['simple1000' => 10.00, 'simple1001' => 20.00, 'simple1002' => 300.00], + 'expectation' => [ + ['label' => '$0.00 - $299.99', 'value' => '-300', 'count' => 2.0], + ['label' => '$300.00 and above', 'value' => '300-', 'count' => 1.0], + ], + ], + 'manual_calculation_with_price_step_200' => [ + 'config' => [ + 'catalog/layered_navigation/price_range_calculation' => 'manual', + 'catalog/layered_navigation/price_range_step' => 200, + ], + 'products_data' => ['simple1000' => 300.00, 'simple1001' => 300.00, 'simple1002' => 500.00], + 'expectation' => [ + ['label' => '$200.00 - $399.99', 'value' => '200-400', 'count' => 2], + ['label' => '$400.00 and above', 'value' => '400-', 'count' => 1], + ], + ], + 'manual_calculation_with_price_step_10' => [ + 'config' => [ + 'catalog/layered_navigation/price_range_calculation' => 'manual', + 'catalog/layered_navigation/price_range_step' => 10, + ], + 'products_data' => ['simple1000' => 300.00, 'simple1001' => 300.00, 'simple1002' => 500.00], + 'expectation' => [ + ['label' => '$300.00 - $309.99', 'value' => '300-310', 'count' => 2], + ['label' => '$500.00 and above', 'value' => '500-', 'count' => 1], + ], + ], + 'manual_calculation_with_number_of_intervals_10' => [ + 'config' => [ + 'catalog/layered_navigation/price_range_calculation' => 'manual', + 'catalog/layered_navigation/price_range_step' => 10, + 'catalog/layered_navigation/price_range_max_intervals' => 10, + ], + 'products_data' => ['simple1000' => 10.00, 'simple1001' => 20.00, 'simple1002' => 30.00], + 'expectation' => [ + ['label' => '$10.00 - $19.99', 'value' => '10-20', 'count' => 1], + ['label' => '$20.00 - $29.99', 'value' => '20-30', 'count' => 1], + ['label' => '$30.00 and above', 'value' => '30-', 'count' => 1], + ], + ], + 'manual_calculation_with_number_of_intervals_2' => [ + 'config' => [ + 'catalog/layered_navigation/price_range_calculation' => 'manual', + 'catalog/layered_navigation/price_range_step' => 10, + 'catalog/layered_navigation/price_range_max_intervals' => 2, + ], + 'products_data' => ['simple1000' => 10.00, 'simple1001' => 20.00, 'simple1002' => 30.00], + 'expectation' => [ + ['label' => '$10.00 - $19.99', 'value' => '10-20', 'count' => 1], + ['label' => '$20.00 and above', 'value' => '20-', 'count' => 2], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_CATEGORY; + } + + /** + * @inheritdoc + */ + protected function getAttributeCode(): string + { + return 'price'; + } + + /** + * @inheritdoc + */ + protected function prepareFilterItems(AbstractFilter $filter): array + { + $items = []; + /** @var Item $item */ + foreach ($filter->getItems() as $item) { + $items[] = [ + 'label' => strip_tags(__($item->getData('label'))->render()), + 'value' => $item->getData('value'), + 'count' => $item->getData('count'), + ]; + } + + return $items; + } + + /** + * Updates price filter store configuration. + * + * @param array $config + * @return void + */ + protected function applyCatalogConfig(array $config): void + { + foreach ($config as $path => $value) { + $this->scopeConfig->setValue($path, $value, StoreScope::SCOPE_STORE, ScopeInterface::SCOPE_DEFAULT); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/OutOfStockProductsFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/OutOfStockProductsFilterTest.php new file mode 100644 index 0000000000000..c4b7b3bdcc68b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/OutOfStockProductsFilterTest.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Search; + +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Resolver; +use Magento\LayeredNavigation\Block\Navigation\Category\OutOfStockProductsFilterTest as CategoryFilterTest; + +/** + * Provides tests for select filter in navigation block on search page with out of stock products + * and enabled out of stock products displaying. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class OutOfStockProductsFilterTest extends CategoryFilterTest +{ + /** + * @magentoDataFixture Magento/Catalog/_files/product_dropdown_attribute.php + * @magentoDataFixture Magento/Catalog/_files/out_of_stock_product_with_category.php + * @magentoDataFixture Magento/Catalog/_files/product_with_category.php + * @dataProvider getFiltersWithOutOfStockProduct + * @param int $showOutOfStock + * @param array $expectation + * @return void + */ + public function testGetFiltersWithOutOfStockProduct(int $showOutOfStock, array $expectation): void + { + $this->updateConfigShowOutOfStockFlag($showOutOfStock); + $this->getSearchFiltersAndAssert( + ['out-of-stock-product' => 'Option 1', 'in-stock-product' => 'Option 2'], + [ + 'is_filterable' => AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS, + 'is_filterable_in_search' => 1, + ], + $expectation + ); + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_SEARCH; + } +} diff --git a/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/PriceFilterTest.php b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/PriceFilterTest.php new file mode 100644 index 0000000000000..d9ac02b2bff11 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/Search/PriceFilterTest.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\LayeredNavigation\Block\Navigation\Search; + +use Magento\Catalog\Model\Layer\Filter\AbstractFilter; +use Magento\Catalog\Model\Layer\Resolver; +use Magento\LayeredNavigation\Block\Navigation\Category\PriceFilterTest as CategoryPriceFilterTest; + +/** + * Provides price filter tests with different price ranges calculation in navigation block on search page. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + */ +class PriceFilterTest extends CategoryPriceFilterTest +{ + /** + * @magentoDataFixture Magento/Catalog/_files/category_with_three_products.php + * @dataProvider getFiltersDataProvider + * @param array $config + * @param array $products + * @param array $expectation + * @return void + */ + public function testGetFilters(array $config, array $products, array $expectation): void + { + $this->applyCatalogConfig($config); + $this->getSearchFiltersAndAssert( + $products, + [ + 'is_filterable' => AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS, + 'is_filterable_in_search' => 1, + ], + $expectation + ); + } + + /** + * @inheritdoc + */ + protected function getLayerType(): string + { + return Resolver::CATALOG_LAYER_SEARCH; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php index 1a22ea947f85a..1877e1faaec67 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/IpnTest.php @@ -5,10 +5,13 @@ */ namespace Magento\Paypal\Model; -use Magento\Paypal\Model\IpnFactory; +use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Creditmemo; +use Magento\Sales\Model\Order\Invoice; +use Magento\TestFramework\Helper\Bootstrap; /** * @magentoAppArea frontend @@ -22,7 +25,7 @@ class IpnTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->_objectManager = Bootstrap::getObjectManager(); } /** @@ -158,6 +161,39 @@ public function testProcessIpnRequestRestRefund() $this->assertEmpty($order->getTotalOfflineRefunded()); } + /** + * Verifies canceling an order that was in payment review state by PayPal Express IPN message service. + * + * @magentoDataFixture Magento/Paypal/_files/order_express_with_invoice_payment_review.php + * @magentoConfigFixture current_store payment/paypal_express/active 1 + * @magentoConfigFixture current_store paypal/general/merchant_country US + */ + public function testProcessIpnRequestWithFailedStatus() + { + $ipnData = require __DIR__ . '/../_files/ipn_failed.php'; + + /** @var IpnFactory $ipnFactory */ + $ipnFactory = $this->_objectManager->create(IpnFactory::class); + $ipnModel = $ipnFactory->create( + [ + 'data' => $ipnData, + 'curlFactory' => $this->_createMockedHttpAdapter() + ] + ); + + $ipnModel->processIpnRequest(); + + $order = $this->getOrder($ipnData['invoice']); + $invoiceItems = $order->getInvoiceCollection() + ->getItems(); + /** @var Invoice $invoice */ + $invoice = array_pop($invoiceItems); + $invoice->getState(); + + $this->assertEquals(Order::STATE_CANCELED, $order->getState()); + $this->assertEquals(Invoice::STATE_CANCELED, $invoice->getState()); + } + /** * Test processIpnRequest() currency check for paypal_express and paypal_standard payment methods * @@ -224,4 +260,25 @@ protected function _createMockedHttpAdapter() $factory->expects($this->once())->method('create')->with()->will($this->returnValue($adapter)); return $factory; } + + /** + * Get stored order. + * + * @param string $incrementId + * @return OrderInterface + */ + private function getOrder(string $incrementId) + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter(OrderInterface::INCREMENT_ID, $incrementId) + ->create(); + + $orderRepository = $this->_objectManager->get(OrderRepositoryInterface::class); + $orders = $orderRepository->getList($searchCriteria) + ->getItems(); + + /** @var OrderInterface $order */ + return array_pop($orders); + } } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/ipn_failed.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/ipn_failed.php new file mode 100644 index 0000000000000..cf1822c9a1a52 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/ipn_failed.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + 'invoice' => '100000002', + 'payment_status' => 'Failed', + 'receiver_email' => 'merchant_2012050718_biz@example.com', + 'parent_txn_id' => '84J11393WC835693U', + 'payer_status' => 'verified', + 'payment_type' => 'instant', + 'txn_id' => '1P566839F9694230H', + 'txn_type' => 'cart' +]; diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/order_express_with_invoice_payment_review.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/order_express_with_invoice_payment_review.php new file mode 100644 index 0000000000000..eb6654b274ba7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/order_express_with_invoice_payment_review.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\DB\Transaction; +use Magento\Paypal\Model\Config; +use Magento\Sales\Api\InvoiceManagementInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Item; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\Service\InvoiceService; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; + +$objectManager = Bootstrap::getObjectManager(); + +$addressData = include __DIR__ . '/address_data.php'; +$billingAddress = $objectManager->create( + Address::class, + ['data' => $addressData] +); +$billingAddress->setAddressType('billing'); +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType('shipping'); + +$payment = $objectManager->create(Payment::class); +$payment->setMethod(Config::METHOD_WPP_EXPRESS); + +/** @var Item $orderItem */ +$orderItem = $objectManager->create(Item::class); +$orderItem->setProductId($product->getId())->setQtyOrdered(1); +$orderItem->setBasePrice($product->getPrice()); +$orderItem->setPrice($product->getPrice()); +$orderItem->setRowTotal($product->getPrice()); +$orderItem->setRowTotalInclTax($product->getPrice()); +$orderItem->setBaseRowTotal($product->getPrice()); +$orderItem->setBaseRowTotalInclTax($product->getPrice()); +$orderItem->setBaseRowInvoiced($product->getPrice()); +$orderItem->setProductType('simple'); + +$itemsAmount = $product->getPrice(); +$shippingAmount = 20; +$totalAmount = $itemsAmount + $shippingAmount; + +/** @var Order $order */ +$order = $objectManager->create(Order::class); +$order->setCustomerEmail('co@co.co') + ->setIncrementId('100000002') + ->addItem($orderItem) + ->setSubtotal($itemsAmount) + ->setBaseSubtotal($itemsAmount) + ->setBaseGrandTotal($totalAmount) + ->setGrandTotal($totalAmount) + ->setBaseCurrencyCode('USD') + ->setCustomerIsGuest(true) + ->setStoreId(1) + ->setEmailSent(true) + ->setState(Order::STATE_PAYMENT_REVIEW) + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setBaseTotalPaid($totalAmount) + ->setTotalPaid($totalAmount) + ->setData('base_to_global_rate', 1) + ->setData('base_to_order_rate', 1) + ->setData('shipping_amount', $shippingAmount) + ->setData('base_shipping_amount', $shippingAmount) + ->setPayment($payment); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$orderRepository->save($order); + +/** @var InvoiceService $invoiceService */ +$invoiceService = $objectManager->create(InvoiceManagementInterface::class); + +/** @var Transaction $transaction */ +$transaction = $objectManager->create(Transaction::class); + +$invoice = $invoiceService->prepareInvoice($order, [$orderItem->getId() => 1]); +$invoice->register(); + +$transaction->addObject($invoice)->addObject($order)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php index 545638bcb0c57..dcd36d4078f8c 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php @@ -5,30 +5,41 @@ */ namespace Magento\Quote\Model\Quote; +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\Model\CustomerRegistry; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Quote\Model\Quote; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Indexer\TestCase; /** + * Class to test Sales Quote address model functionality + * * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php */ -class AddressTest extends \Magento\TestFramework\Indexer\TestCase +class AddressTest extends TestCase { - /** @var \Magento\Quote\Model\Quote $quote */ + /** @var Quote $quote */ protected $_quote; - /** @var \Magento\Customer\Api\Data\CustomerInterface $customer */ + /** @var CustomerInterface $customer */ protected $_customer; - /** @var \Magento\Quote\Model\Quote\Address */ + /** @var Address */ protected $_address; - /**@var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ + /**@var CustomerRepositoryInterface $customerRepository */ protected $customerRepository; - /** @var \Magento\Customer\Api\AddressRepositoryInterface $addressRepository */ + /** @var AddressRepositoryInterface $addressRepository */ protected $addressRepository; - /** @var \Magento\Framework\Reflection\DataObjectProcessor */ + /** @var DataObjectProcessor */ protected $dataProcessor; /** @@ -36,7 +47,7 @@ class AddressTest extends \Magento\TestFramework\Indexer\TestCase */ public static function setUpBeforeClass() { - $db = \Magento\TestFramework\Helper\Bootstrap::getInstance()->getBootstrap() + $db = Bootstrap::getInstance()->getBootstrap() ->getApplication() ->getDbInstance(); if (!$db->isDbDumpExists()) { @@ -48,43 +59,46 @@ public static function setUpBeforeClass() } /** - * Initialize quote and customer fixtures + * @inheritdoc */ public function setUp() { - $this->_quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Quote\Model\Quote::class + $this->_quote = Bootstrap::getObjectManager()->create( + Quote::class ); $this->_quote->load('test01', 'reserved_order_id'); $this->_quote->setIsMultiShipping('0'); - $this->customerRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Api\CustomerRepositoryInterface::class + $this->customerRepository = Bootstrap::getObjectManager()->create( + CustomerRepositoryInterface::class ); $this->_customer = $this->customerRepository->getById(1); /** @var \Magento\Sales\Model\Order\Address $address */ - $this->_address = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Quote\Model\Quote\Address::class + $this->_address = Bootstrap::getObjectManager()->create( + Address::class ); $this->_address->setId(1); $this->_address->load($this->_address->getId()); $this->_address->setQuote($this->_quote); - $this->addressRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Api\AddressRepositoryInterface::class + $this->addressRepository = Bootstrap::getObjectManager()->create( + AddressRepositoryInterface::class ); - $this->dataProcessor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Framework\Reflection\DataObjectProcessor::class + $this->dataProcessor = Bootstrap::getObjectManager()->create( + DataObjectProcessor::class ); } + /** + * @inheritdoc + */ protected function tearDown() { - /** @var \Magento\Customer\Model\CustomerRegistry $customerRegistry */ - $customerRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->get(\Magento\Customer\Model\CustomerRegistry::class); + /** @var CustomerRegistry $customerRegistry */ + $customerRegistry = Bootstrap::getObjectManager() + ->get(CustomerRegistry::class); //Cleanup customer from registry $customerRegistry->remove(1); } @@ -102,9 +116,9 @@ public function testSameAsBillingForBillingAddress($unsetId) if ($unsetId) { $address->setId(null); } - /** @var \Magento\Customer\Api\AddressRepositoryInterface $addressRepository */ + /** @var AddressRepositoryInterface $addressRepository */ $addressRepository = Bootstrap::getObjectManager() - ->create(\Magento\Customer\Api\AddressRepositoryInterface::class); + ->create(AddressRepositoryInterface::class); $customerAddressData = $addressRepository->getById($this->_customer->getDefaultBilling()); $address->setSameAsBilling(0)->setCustomerAddressData($customerAddressData)->save(); $this->assertEquals(0, $this->_quote->getBillingAddress()->getSameAsBilling()); @@ -155,9 +169,9 @@ public function testSameAsBillingWhenQuoteAddressHasNoCustomerAddress($unsetId) */ public function testSameAsBillingWhenCustomerHasNoDefaultShippingAddress($unsetId) { - /** @var \Magento\Customer\Api\AddressRepositoryInterface $addressRepository */ + /** @var AddressRepositoryInterface $addressRepository */ $addressRepository = Bootstrap::getObjectManager() - ->create(\Magento\Customer\Api\AddressRepositoryInterface::class); + ->create(AddressRepositoryInterface::class); $this->_customer->setDefaultShipping(-1) ->setAddresses( [ @@ -194,9 +208,9 @@ public function testSameAsBillingWhenCustomerHasBillingSameShipping($unsetId) */ public function testSameAsBillingWhenCustomerHasDefaultShippingAddress() { - /** @var \Magento\Customer\Api\AddressRepositoryInterface $addressRepository */ + /** @var AddressRepositoryInterface $addressRepository */ $addressRepository = Bootstrap::getObjectManager() - ->create(\Magento\Customer\Api\AddressRepositoryInterface::class); + ->create(AddressRepositoryInterface::class); $this->_customer->setDefaultShipping(2) ->setAddresses([$addressRepository->getById($this->_address->getId())]); $this->_customer = $this->customerRepository->save($this->_customer); @@ -218,19 +232,39 @@ protected function _setCustomerAddressAndSave($unsetId) if ($unsetId) { $shippingAddress->setId(null); } - /** @var \Magento\Customer\Api\AddressRepositoryInterface $addressRepository */ + /** @var AddressRepositoryInterface $addressRepository */ $addressRepository = Bootstrap::getObjectManager() - ->create(\Magento\Customer\Api\AddressRepositoryInterface::class); + ->create(AddressRepositoryInterface::class); $shippingAddress->setSameAsBilling(0) ->setCustomerAddressData($addressRepository->getById($this->_customer->getDefaultBilling())) ->save(); } + /** + * @return array + */ public function unsetAddressIdDataProvider() { return [[true], [false]]; } + /** + * Test to get same as billing flag after change quote customer + */ + public function testSameAsBillingAfterCustomerWesChanged() + { + $shippingAddressId = 2; + $this->_quote->setCustomer($this->_customer); + /** Make different default shipping and default billing addresses */ + $this->_customer->setDefaultShipping($shippingAddressId); + $this->_quote->getShippingAddress()->setCustomerAddressId($shippingAddressId); + /** Emulate to change customer */ + $this->_quote->setOrigData('customer_id', null); + $shippingAddress = $this->_quote->getShippingAddress(); + $shippingAddress->beforeSave(); + $this->assertEquals(false, $this->_quote->getShippingAddress()->getSameAsBilling()); + } + /** * Import customer address to quote address */ @@ -241,13 +275,13 @@ public function testImportCustomerAddressDataWithCustomer() $city = 'TestCity'; $street = 'Street1'; - /** @var \Magento\Customer\Api\Data\AddressInterfaceFactory $addressFactory */ + /** @var AddressInterfaceFactory $addressFactory */ $addressFactory = Bootstrap::getObjectManager()->create( - \Magento\Customer\Api\Data\AddressInterfaceFactory::class + AddressInterfaceFactory::class ); - /** @var \Magento\Customer\Api\AddressRepositoryInterface $addressRepository */ + /** @var AddressRepositoryInterface $addressRepository */ $addressRepository = Bootstrap::getObjectManager()->create( - \Magento\Customer\Api\AddressRepositoryInterface::class + AddressRepositoryInterface::class ); $addressData = $addressFactory->create() ->setCustomerId($customerIdFromFixture) @@ -284,6 +318,9 @@ public function testExportCustomerAddressData() $this->assertEquals($company, $customerAddress->getCompany(), 'Company was exported incorrectly.'); } + /** + * Test to Set the required fields + */ public function testPopulateBeforeSaveData() { /** Preconditions */ @@ -303,9 +340,9 @@ public function testPopulateBeforeSaveData() "Precondition failed: Customer address ID was not set." ); - /** @var \Magento\Customer\Api\Data\AddressInterfaceFactory $addressFactory */ + /** @var AddressInterfaceFactory $addressFactory */ $addressFactory = Bootstrap::getObjectManager()->create( - \Magento\Customer\Api\Data\AddressInterfaceFactory::class + AddressInterfaceFactory::class ); $customerAddressData = $addressFactory->create()->setId($customerAddressId); $this->_address->setCustomerAddressData($customerAddressData); @@ -317,22 +354,26 @@ public function testPopulateBeforeSaveData() } /** - * Tests + * Test to retrieve applied taxes * - * @covers \Magento\Quote\Model\Quote\Address::setAppliedTaxes() - * @covers \Magento\Quote\Model\Quote\Address::getAppliedTaxes() - * @dataProvider dataProvider * @param $taxes * @param $expected + * @covers \Magento\Quote\Model\Quote\Address::setAppliedTaxes() + * @covers \Magento\Quote\Model\Quote\Address::getAppliedTaxes() + * @dataProvider appliedTaxesDataProvider */ public function testAppliedTaxes($taxes, $expected) { $this->_address->setAppliedTaxes($taxes); - $this->assertSame($expected, $this->_address->getAppliedTaxes()); } - public function dataProvider() + /** + * Retrieve applied taxes data provider + * + * @return array + */ + public function appliedTaxesDataProvider() { return [ ['test', 'test'], @@ -340,6 +381,9 @@ public function dataProvider() ]; } + /** + * Test to sate shipping address without region + */ public function testSaveShippingAddressWithEmptyRegionId() { $customerAddress = $this->addressRepository->getById(1); @@ -347,7 +391,7 @@ public function testSaveShippingAddressWithEmptyRegionId() $address = $this->dataProcessor->buildOutputDataArray( $customerAddress, - \Magento\Customer\Api\Data\AddressInterface::class + AddressInterface::class ); $shippingAddress = $this->_quote->getShippingAddress(); diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php index 8db7b65d0142d..1a4e640a1eabe 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php @@ -6,13 +6,52 @@ namespace Magento\Quote\Model; +use Magento\Customer\Model\Vat; +use Magento\Store\Model\ScopeInterface; +use Magento\Tax\Model\Config as TaxConfig; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Quote\Model\GetQuoteByReservedOrderId; +use Magento\Framework\ObjectManagerInterface; +use Magento\Customer\Api\Data\GroupInterface; +use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\Tax\Model\ClassModel; +use Magento\Framework\App\Config\MutableScopeConfigInterface; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Quote\Api\ShippingMethodManagementInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Tax\Api\TaxClassRepositoryInterface; +use Magento\Tax\Api\Data\TaxClassInterface; + /** - * Class ShippingMethodManagementTest + * Test for shipping methods management * * @magentoDbIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ShippingMethodManagementTest extends \PHPUnit\Framework\TestCase { + /** @var ObjectManagerInterface $objectManager */ + private $objectManager; + + /** @var GroupRepositoryInterface $groupRepository */ + private $groupRepository; + + /** @var TaxClassRepositoryInterface $taxClassRepository */ + private $taxClassRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->groupRepository = $this->objectManager->get(GroupRepositoryInterface::class); + $this->taxClassRepository = $this->objectManager->get(TaxClassRepositoryInterface::class); + } + /** * @magentoDataFixture Magento/SalesRule/_files/cart_rule_100_percent_off.php * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php @@ -173,4 +212,130 @@ private function executeTestFlow($flatRateAmount, $tableRateAmount) $this->assertEquals($expectedResult[$rate->getCarrierCode()]['method_code'], $rate->getMethodCode()); } } + + /** + * Test for estimate shipping with tax and changed VAT customer group + * + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/Tax/_files/tax_classes_de.php + * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php + * @magentoDataFixture Magento/Customer/_files/customer_group.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @magentoConfigFixture current_store customer/create_account/tax_calculation_address_type shipping + * @magentoConfigFixture current_store customer/create_account/default_group 1 + * @magentoConfigFixture current_store customer/create_account/auto_group_assign 1 + * @magentoConfigFixture current_store tax/calculation/price_includes_tax 1 + * @magentoConfigFixture current_store tax/calculation/shipping_includes_tax 1 + */ + 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'); + $customerGroup->setTaxClassId($this->getTaxClass('CustomerTaxClass')->getClassId()); + $this->groupRepository->save($customerGroup); + + $customer->setGroupId($customerGroup->getId()); + $customer->setTaxvat('12'); + $customerRepository->save($customer); + $this->setConfig($customerGroup->getId(), $this->getTaxClass('ProductTaxClass')->getClassId()); + $this->changeCustomerAddress($customer->getDefaultShipping()); + + $quote = $this->objectManager->get(GetQuoteByReservedOrderId::class)->execute('test01'); + + /** @var ShippingMethodManagementInterface $shippingEstimation */ + $shippingEstimation = $this->objectManager->get(ShippingMethodManagementInterface::class); + $result = $shippingEstimation->estimateByAddressId($quote->getId(), $customer->getDefaultShipping()); + + $this->assertEquals(6.05, $result[0]->getPriceInclTax()); + $this->assertEquals(5.0, $result[0]->getPriceExclTax()); + } + + /** + * Find the group with a given code. + * + * @param string $code + * + * @return GroupInterface + */ + protected function findCustomerGroupByCode(string $code): ?GroupInterface + { + /** @var SearchCriteriaBuilder $searchBuilder */ + $searchBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchBuilder->addFilter('code', $code) + ->create(); + $groups = $this->groupRepository->getList($searchCriteria) + ->getItems(); + + return array_shift($groups); + } + + /** + * Change customer address + * + * @param int $customerAddressId + * + * @return AddressInterface + */ + private function changeCustomerAddress(int $customerAddressId): AddressInterface + { + $addressRepository = $this->objectManager->get(AddressRepositoryInterface::class); + $address = $addressRepository->getById($customerAddressId); + $address->setVatId(12345); + $address->setCountryId('DE'); + $address->setRegionId(0); + $address->setPostcode(10178); + + return $addressRepository->save($address); + } + + /** + * Get tax class. + * + * @param string $name + * + * @return TaxClassInterface + */ + private function getTaxClass(string $name): ?TaxClassInterface + { + /** @var SearchCriteriaBuilder $searchBuilder */ + $searchBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchBuilder->addFilter(ClassModel::KEY_NAME, $name) + ->create(); + $searchResults = $this->taxClassRepository->getList($searchCriteria) + ->getItems(); + + return array_shift($searchResults); + } + + /** + * Set the configuration. + * + * @param int $customerGroupId + * @param int $productTaxClassId + * + * @return void + */ + private function setConfig(int $customerGroupId, int $productTaxClassId): void + { + $configData = [ + [ + 'path' => Vat::XML_PATH_CUSTOMER_VIV_INVALID_GROUP, + 'value' => $customerGroupId, + 'scope' => ScopeInterface::SCOPE_STORE, + ], + [ + 'path' => TaxConfig::CONFIG_XML_PATH_SHIPPING_TAX_CLASS, + 'value' => $productTaxClassId, + 'scope' => ScopeInterface::SCOPE_STORE, + ], + ]; + $config = $this->objectManager->get(MutableScopeConfigInterface::class); + foreach ($configData as $data) { + $config->setValue($data['path'], $data['value'], $data['scope']); + } + } } diff --git a/dev/tests/integration/testsuite/Magento/Review/Controller/Adminhtml/Customer/ProductReviewsTest.php b/dev/tests/integration/testsuite/Magento/Review/Controller/Adminhtml/Customer/ProductReviewsTest.php new file mode 100644 index 0000000000000..4203fb9c16b29 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/Controller/Adminhtml/Customer/ProductReviewsTest.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Review\Controller\Adminhtml\Customer; + +use Magento\Framework\App\Request\Http; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Test for customer product reviews page. + */ +class ProductReviewsTest extends AbstractBackendController +{ + /** + * Check Customer product review action. + * + * @magentoDataFixture Magento/Customer/_files/customer_sample.php + * @return void + */ + public function testProductReviewsAction(): void + { + $this->getRequest()->setPostValue(['id' => 1])->setMethod(Http::METHOD_POST); + $this->dispatch('backend/review/customer/productReviews'); + $body = $this->getResponse()->getBody(); + $this->assertContains('<div id="reviewGrid"', $body); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php index 383af7968e047..46e9ba667f390 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php @@ -82,7 +82,6 @@ public function testHandlerWithCustomer(): void $expectedVars = [ 'reason' => $errorMessage, 'checkoutType' => $checkoutType, - 'dateAndTime' => $templateTimeMethod->invoke($this->paymentFailures), 'customer' => 'John Smith', 'customerEmail' => 'aaa@aaa.com', 'paymentMethod' => 'Some Title Of The Method', @@ -94,6 +93,7 @@ public function testHandlerWithCustomer(): void 'billingAddressHtml' => $this->quote->getBillingAddress()->format('html'), 'shippingAddressHtml' => $this->quote->getShippingAddress()->format('html'), ]; + unset($templateVars['dateAndTime']); $this->assertEquals($expectedVars, $templateVars); } diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_order_with_customer.php b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_order_with_customer.php new file mode 100644 index 0000000000000..c5304f5b8809f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_order_with_customer.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\DB\Transaction; +use Magento\Sales\Model\Order\ShipmentFactory; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/order_with_customer.php'; + +$objectManager = Bootstrap::getObjectManager(); +$order->setIsInProcess(true); +/** @var Transaction $transaction */ +$transaction = $objectManager->create(Transaction::class); + +$items = []; +foreach ($order->getItems() as $orderItem) { + $items[$orderItem->getId()] = $orderItem->getQtyOrdered(); +} + +$shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); +$shipment->register(); + +$transaction->addObject($shipment)->addObject($order)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_order_with_customer_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_order_with_customer_rollback.php new file mode 100644 index 0000000000000..2595d6bf4084a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_for_order_with_customer_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/default_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/AddressTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/AddressTest.php new file mode 100644 index 0000000000000..17730262d2dfd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/AddressTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesRule\Model\Rule\Condition; + +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for \Magento\SalesRule\Model\Rule\Condition\Address. + */ +class AddressTest extends TestCase +{ + use ConditionHelper; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + } + + /** + * Tests cart price rule validation. + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoConfigFixture default_store payment/checkmo/active 1 + * @magentoDataFixture Magento/SalesRule/_files/rules_payment_method.php + * @magentoDataFixture Magento/Checkout/_files/quote_with_payment_saved.php + */ + public function testValidateRule() + { + $quote = $this->getQuote('test_order_1_with_payment'); + $rule = $this->getSalesRule('50% Off on Checkmo Payment Method'); + + $this->assertTrue( + $rule->validate($quote->getBillingAddress()), + 'Cart price rule validation failed.' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ConditionHelper.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ConditionHelper.php new file mode 100644 index 0000000000000..e857ab902fcc5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ConditionHelper.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesRule\Model\Rule\Condition; + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartInterface; +use Magento\SalesRule\Api\RuleRepositoryInterface; + +/** + * Helper class for testing cart price rule conditions. + */ +trait ConditionHelper +{ + /** + * Gets quote by reserved order id. + * + * @param string $reservedOrderId + * @return CartInterface + */ + private function getQuote($reservedOrderId) + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria)->getItems(); + return array_pop($items); + } + + /** + * Gets rule by name. + * + * @param string $name + * @return \Magento\SalesRule\Model\Rule + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getSalesRule(string $name): \Magento\SalesRule\Model\Rule + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $ruleRepository = $this->objectManager->get(RuleRepositoryInterface::class); + $items = $ruleRepository->getList($searchCriteria)->getItems(); + + $rule = array_pop($items); + /** @var \Magento\SalesRule\Model\Converter\ToModel $converter */ + $converter = $this->objectManager->get(\Magento\SalesRule\Model\Converter\ToModel::class); + + return $converter->toModel($rule); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php index 70fa11fc78c87..066f30667b53c 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php @@ -6,21 +6,24 @@ namespace Magento\SalesRule\Model\Rule\Condition; -use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Quote\Api\Data\CartInterface; -use Magento\SalesRule\Api\RuleRepositoryInterface; +use Magento\Framework\Registry; +use Magento\SalesRule\Model\Rule; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ProductTest extends \PHPUnit\Framework\TestCase { + use ConditionHelper; + /** * @var \Magento\Framework\ObjectManagerInterface */ private $objectManager; + /** + * @inheritDoc + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -50,8 +53,8 @@ public function testValidateCategorySalesRuleIncludesChildren($categoryId, $expe ->load('test_cart_with_configurable', 'reserved_order_id'); // Load the SalesRule looking for products in a specific category - /** @var $rule \Magento\SalesRule\Model\Rule */ - $rule = $this->objectManager->get(\Magento\Framework\Registry::class) + /** @var $rule Rule */ + $rule = $this->objectManager->get(Registry::class) ->registry('_fixture/Magento_SalesRule_Category'); // Prepare the parent product with the given category setting @@ -80,8 +83,8 @@ public function testValidateSalesRuleExcludesBundleChildren(): void ->load('test_cart_with_bundle_and_options', 'reserved_order_id'); // Load the SalesRule looking for excluding products with selected sku - /** @var $rule \Magento\SalesRule\Model\Rule */ - $rule = $this->objectManager->get(\Magento\Framework\Registry::class) + /** @var $rule Rule */ + $rule = $this->objectManager->get(Registry::class) ->registry('_fixture/Magento_SalesRule_Sku_Exclude'); $this->assertEquals(false, $rule->validate($quote)); @@ -129,47 +132,23 @@ public function testValidateQtySalesRuleWithConfigurable() } /** - * Gets quote by reserved order id. + * Ensure that SalesRules filtering on quote items quantity validates configurable product parent category correctly * - * @param string $reservedOrderId - * @return CartInterface - */ - private function getQuote($reservedOrderId) - { - /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ - $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); - $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) - ->create(); - - /** @var CartRepositoryInterface $quoteRepository */ - $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); - $items = $quoteRepository->getList($searchCriteria)->getItems(); - return array_pop($items); - } - - /** - * Gets rule by name. - * - * @param string $name - * @return \Magento\SalesRule\Model\Rule - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @magentoDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + * @magentoDataFixture Magento/SalesRule/_files/rules_parent_category.php */ - private function getSalesRule(string $name): \Magento\SalesRule\Model\Rule + public function testValidateParentCategoryWithConfigurable() { - /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ - $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); - $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) - ->create(); - - /** @var CartRepositoryInterface $quoteRepository */ - $ruleRepository = $this->objectManager->get(RuleRepositoryInterface::class); - $items = $ruleRepository->getList($searchCriteria)->getItems(); - - $rule = array_pop($items); - /** @var \Magento\SalesRule\Model\Converter\ToModel $converter */ - $converter = $this->objectManager->get(\Magento\SalesRule\Model\Converter\ToModel::class); + $quote = $this->getQuote('test_cart_with_configurable'); + $registry = $this->objectManager->get(Registry::class); + /** @var Rule $rule */ + $rule = $this->objectManager->create(Rule::class); + $ruleId = $registry->registry('50% Off on Configurable parent category'); + $rule->load($ruleId); - return $converter->toModel($rule); + $this->assertFalse( + $rule->validate($quote->getBillingAddress()), + 'Cart price rule validation failed.' + ); } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition.php new file mode 100644 index 0000000000000..77178abdb2384 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/** @var \Magento\Framework\Registry $registry */ +/** @var \Magento\SalesRule\Model\Rule $salesRule */ +/** @var \Magento\SalesRule\Model\RuleRepository $salesRuleRepository */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$registry = $objectManager->get(\Magento\Framework\Registry::class); +$salesRule = $objectManager->create(\Magento\SalesRule\Model\Rule::class); +$salesRuleRepository = $objectManager->create(\Magento\SalesRule\Model\RuleRepository::class); +$allRules = $salesRuleRepository->getList($objectManager->get(\Magento\Framework\Api\SearchCriteriaInterface::class)); +foreach ($allRules->getItems() as $rule) { + $salesRuleRepository->deleteById($rule->getRuleId()); +} +$salesRule->setData( + [ + 'name' => '50% off - July 4', + 'is_active' => 1, + 'customer_group_ids' => [\Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON, + 'simple_action' => 'by_percent', + 'discount_amount' => 50, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getWebsite()->getId() + ] + ] +); +$salesRule->save(); + +$registry->unregister('Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition/salesRuleId'); +$registry->register('Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition/salesRuleId', $salesRule->getId()); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition_rollback.php new file mode 100644 index 0000000000000..bafaf1a48f960 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition_rollback.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +/** @var \Magento\SalesRule\Model\Rule $salesRule */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$registry = $objectManager->get(\Magento\Framework\Registry::class); +$salesRule = $objectManager->create(\Magento\SalesRule\Model\Rule::class); +$salesRuleId = $registry->registry('Magento/SalesRule/_files/cart_rule_50_percent_off_no_condition/salesRuleId'); +if ($salesRuleId) { + $salesRule->load($salesRuleId); + if ($salesRule->getId()) { + $salesRule->delete(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_parent_category.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_parent_category.php new file mode 100644 index 0000000000000..c525fa7152447 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_parent_category.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +/** @var \Magento\SalesRule\Model\Rule $rule */ +$salesRule = Bootstrap::getObjectManager()->create(\Magento\SalesRule\Model\Rule::class); +$salesRule->setData( + [ + 'name' => '50% Off on Configurable parent category', + 'is_active' => 1, + 'customer_group_ids' => [\Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON, + 'simple_action' => 'by_percent', + 'discount_amount' => 50, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getWebsite()->getId() + ] + ] +); + +$salesRule->getConditions()->loadArray([ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'all', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Product\Subselect::class, + 'attribute' => 'qty', + 'operator' => '==', + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'all', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Product::class, + 'attribute' => 'category_ids', + 'attribute_scope' => 'parent', + 'operator' => '!=', + 'value' => '2', + 'is_value_processed' => false, + ], + ], + ], + ], +]); + +$salesRule->save(); +$registry->unregister('50% Off on Configurable parent category'); +$registry->register('50% Off on Configurable parent category', $salesRule->getRuleId()); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_parent_category_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_parent_category_rollback.php new file mode 100644 index 0000000000000..edefd5e1650e7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_parent_category_rollback.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\SalesRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Registry; + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +$rule = Bootstrap::getObjectManager()->get(Rule::class); + +/** @var Rule $rule */ +$ruleId = $registry->registry('50% Off on Configurable parent category'); +$rule->load($ruleId); +if ($rule->getId()) { + $rule->delete(); +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_payment_method.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_payment_method.php new file mode 100644 index 0000000000000..25f208d34d8e0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rules_payment_method.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\TestFramework\Helper\Bootstrap; + +/** @var \Magento\SalesRule\Model\Rule $rule */ +$salesRule = Bootstrap::getObjectManager()->create(\Magento\SalesRule\Model\Rule::class); +$salesRule->setData( + [ + 'name' => '50% Off on Checkmo Payment Method', + 'is_active' => 1, + 'customer_group_ids' => [\Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON, + 'simple_action' => 'by_percent', + 'discount_amount' => 50, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + Bootstrap::getObjectManager()->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getWebsite()->getId() + ] + ] +); + +$salesRule->getConditions()->loadArray([ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'any', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, + 'attribute' => 'payment_method', + 'operator' => '==', + 'value' => 'checkmo' + ], + ], +]); + +$salesRule->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php index d6388b188a5fd..7d5e919880d3b 100644 --- a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php @@ -52,6 +52,7 @@ public function testGetCollectionNone() * 3) Check thumbnails when no thumbnail selected * * @magentoConfigFixture default_store sitemap/product/image_include all + * @magentoConfigFixture default/web/url/catalog_media_url_format hash */ public function testGetCollectionAll() { @@ -120,6 +121,7 @@ public function testGetCollectionAll() * 3) Check thumbnails when no thumbnail selected * * @magentoConfigFixture default_store sitemap/product/image_include base + * @magentoConfigFixture default/web/url/catalog_media_url_format hash */ public function testGetCollectionBase() { diff --git a/dev/tests/integration/testsuite/Magento/Store/Controller/Store/RedirectTest.php b/dev/tests/integration/testsuite/Magento/Store/Controller/Store/RedirectTest.php new file mode 100644 index 0000000000000..df45846dfc832 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/Controller/Store/RedirectTest.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Controller\Store; + +use Magento\Framework\Session\SidResolverInterface; +use Magento\Store\Model\StoreResolver; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test Redirect controller. + * + * @magentoAppArea frontend + */ +class RedirectTest extends AbstractController +{ + /** + * Check that there's no SID in redirect URL. + * + * @return void + * @magentoDataFixture Magento/Store/_files/store.php + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoConfigFixture current_store web/session/use_frontend_sid 1 + */ + public function testNoSid(): void + { + $this->getRequest()->setParam(StoreResolver::PARAM_NAME, 'fixture_second_store'); + $this->getRequest()->setParam('___from_store', 'test'); + + $this->dispatch('/stores/store/redirect'); + + $result = (string)$this->getResponse()->getHeader('location'); + $this->assertNotEmpty($result); + $this->assertNotContains(SidResolverInterface::SESSION_ID_QUERY_PARAM .'=', $result); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php index 00de5544d8fb7..623335f7a30d7 100644 --- a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php @@ -9,9 +9,12 @@ use Magento\Catalog\Model\ProductRepository; use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Session\SidResolverInterface; use Magento\Framework\UrlInterface; use Magento\Store\Api\StoreRepositoryInterface; use Zend\Stdlib\Parameters; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -29,6 +32,11 @@ class StoreTest extends \PHPUnit\Framework\TestCase */ protected $model; + /** + * @var HttpRequest + */ + private $request; + protected function setUp() { $this->model = $this->_getStoreModel(); @@ -40,6 +48,7 @@ protected function setUp() protected function _getStoreModel() { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->request = $objectManager->get(RequestInterface::class); $this->modelParams = [ 'context' => $objectManager->get(\Magento\Framework\Model\Context::class), 'registry' => $objectManager->get(\Magento\Framework\Registry::class), @@ -49,7 +58,7 @@ protected function _getStoreModel() 'coreFileStorageDatabase' => $objectManager->get(\Magento\MediaStorage\Helper\File\Storage\Database::class), 'configCacheType' => $objectManager->get(\Magento\Framework\App\Cache\Type\Config::class), 'url' => $objectManager->get(\Magento\Framework\Url::class), - 'request' => $objectManager->get(\Magento\Framework\App\RequestInterface::class), + 'request' => $this->request, 'configDataResource' => $objectManager->get(\Magento\Config\Model\ResourceModel\Config\Data::class), 'filesystem' => $objectManager->get(\Magento\Framework\Filesystem::class), 'config' => $objectManager->get(\Magento\Framework\App\Config\ReinitableConfigInterface::class), @@ -292,6 +301,13 @@ public function testGetCurrentUrl() $this->assertStringEndsWith('default', $this->model->getCurrentUrl()); $this->assertStringEndsNotWith('default', $this->model->getCurrentUrl(false)); + $this->model + ->expects($this->any())->method('getUrl') + ->willReturn('http://localhost/index.php?' .SidResolverInterface::SESSION_ID_QUERY_PARAM .'=12345'); + $this->request->setParams([SidResolverInterface::SESSION_ID_QUERY_PARAM, '12345']); + $this->request->setQueryValue(SidResolverInterface::SESSION_ID_QUERY_PARAM, '12345'); + $this->assertContains(SidResolverInterface::SESSION_ID_QUERY_PARAM .'=12345', $this->model->getCurrentUrl()); + /** @var \Magento\Store\Model\Store $secondStore */ $secondStore = $objectManager->get(StoreRepositoryInterface::class)->get('secondstore'); @@ -306,11 +322,11 @@ public function testGetCurrentUrl() $url ); $this->assertEquals( - $secondStore->getBaseUrl() . '?___from_store=default', + $secondStore->getBaseUrl() . '?SID=12345&___from_store=default', $secondStore->getCurrentUrl() ); $this->assertEquals( - $secondStore->getBaseUrl(), + $secondStore->getBaseUrl() . '?SID=12345', $secondStore->getCurrentUrl(false) ); } diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php new file mode 100644 index 0000000000000..460e4559a0e84 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php @@ -0,0 +1,238 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Block\Product; + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Block\Product\Image; +use Magento\Catalog\Block\Product\ListProduct; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\Store; +use Magento\Swatches\Model\Plugin\ProductImage; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Tests for displaying configurable product image with swatch attributes. + * + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoAppArea frontend + */ +class ListProductTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ProductResource + */ + private $productResource; + + /** + * @var ProductAttributeRepositoryInterface + */ + private $attributeRepository; + + /** + * @var RequestInterface + */ + private $request; + + /** + * @var LayoutInterface + */ + private $layout; + + /** + * @var ListProduct + */ + private $listingBlock; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productResource = $this->objectManager->get(ProductResource::class); + $this->attributeRepository = $this->objectManager->get(ProductAttributeRepositoryInterface::class); + $this->request = $this->objectManager->get(RequestInterface::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->listingBlock = $this->layout->createBlock(ListProduct::class); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_text_swatch_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_image.php + * @dataProvider getImageDataProvider + * @param array $images + * @param string $area + * @param array $expectation + * @return void + */ + public function testGetImageForTextSwatchConfigurable(array $images, string $area, array $expectation): void + { + $this->updateAttributePreviewImageFlag('text_swatch_attribute'); + $this->addFilterToRequest('text_swatch_attribute', 'option 1'); + $this->assertProductImage($images, $area, $expectation); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_visual_swatch_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_image.php + * @dataProvider getImageDataProvider + * @param array $images + * @param string $area + * @param array $expectation + * @return void + */ + public function testGetImageForVisualSwatchConfigurable(array $images, string $area, array $expectation): void + { + $this->updateAttributePreviewImageFlag('visual_swatch_attribute'); + $this->addFilterToRequest('visual_swatch_attribute', 'option 1'); + $this->assertProductImage($images, $area, $expectation); + } + + /** + * @return array + */ + public function getImageDataProvider(): array + { + return [ + 'without_images_and_display_grid' => [ + 'images' => [], + 'display_area' => ProductImage::CATEGORY_PAGE_GRID_LOCATION, + 'expectation' => ['image_url' => 'placeholder/small_image.jpg', 'label' => 'Configurable Product'], + ], + 'without_images_and_display_list' => [ + 'images' => [], + 'display_area' => ProductImage::CATEGORY_PAGE_LIST_LOCATION, + 'expectation' => ['image_url' => 'placeholder/small_image.jpg', 'label' => 'Configurable Product'], + ], + 'with_image_on_configurable_and_display_grid' => [ + 'images' => ['configurable' => '/m/a/magento_image.jpg'], + 'display_area' => ProductImage::CATEGORY_PAGE_GRID_LOCATION, + 'expectation' => ['image_url' => '/m/a/magento_image.jpg', 'label' => 'Image Alt Text'], + ], + 'with_image_on_configurable_and_display_list' => [ + 'images' => ['configurable' => '/m/a/magento_image.jpg'], + 'display_area' => ProductImage::CATEGORY_PAGE_LIST_LOCATION, + 'expectation' => ['image_url' => '/m/a/magento_image.jpg', 'label' => 'Image Alt Text'], + ], + 'with_image_on_simple' => [ + 'images' => ['simple_option_1' => '/m/a/magento_small_image.jpg'], + 'display_area' => ProductImage::CATEGORY_PAGE_GRID_LOCATION, + 'expectation' => ['image_url' => '/m/a/magento_small_image.jpg', 'label' => 'Image Alt Text'], + ], + 'with_image_on_simple_and_configurable' => [ + 'images' => [ + 'configurable' => '/m/a/magento_image.jpg', + 'simple_option_1' => '/m/a/magento_small_image.jpg', + ], + 'display_area' => ProductImage::CATEGORY_PAGE_GRID_LOCATION, + 'expectation' => ['image_url' => '/m/a/magento_small_image.jpg', 'label' => 'Image Alt Text'], + ], + ]; + } + + /** + * Asserts image data. + * + * @param array $images + * @param string $area + * @param array $expectation + * @return void + */ + private function assertProductImage(array $images, string $area, array $expectation): void + { + $this->updateProductImages($images); + $productImage = $this->listingBlock->getImage($this->productRepository->get('configurable'), $area); + $this->assertInstanceOf(Image::class, $productImage); + $this->assertEquals($productImage->getCustomAttributes(), ''); + $this->assertEquals($productImage->getClass(), 'product-image-photo'); + $this->assertEquals($productImage->getRatio(), 1.25); + $this->assertEquals($productImage->getLabel(), $expectation['label']); + $this->assertStringEndsWith($expectation['image_url'], $productImage->getImageUrl()); + $this->assertEquals($productImage->getWidth(), 240); + $this->assertEquals($productImage->getHeight(), 300); + } + + /** + * Updates products images. + * + * @param array $images + * @return void + */ + private function updateProductImages(array $images): void + { + foreach ($images as $sku => $imageName) { + $product = $this->productRepository->get($sku); + $product->setStoreId(Store::DEFAULT_STORE_ID) + ->setImage($imageName) + ->setSmallImage($imageName) + ->setThumbnail($imageName) + ->setData( + 'media_gallery', + [ + 'images' => [ + [ + 'file' => $imageName, + 'position' => 1, + 'label' => 'Image Alt Text', + 'disabled' => 0, + 'media_type' => 'image' + ], + ], + ] + ) + ->setCanSaveCustomOptions(true); + $this->productResource->save($product); + } + } + + /** + * Updates attribute "Update Product Preview Image" flag. + * + * @param string $attributeCode + * @return void + */ + private function updateAttributePreviewImageFlag(string $attributeCode): void + { + $attribute = $this->attributeRepository->get($attributeCode); + $attribute->setData('update_product_preview_image', 1); + $this->attributeRepository->save($attribute); + } + + /** + * Adds attribute param to request. + * + * @param string $attributeCode + * @param string $optionLabel + * @return void + */ + private function addFilterToRequest(string $attributeCode, string $optionLabel): void + { + $attribute = $this->attributeRepository->get($attributeCode); + $this->request->setParams( + [$attributeCode => $attribute->getSource()->getOptionId($optionLabel)] + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/Listing/CategoryPageViewTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/Listing/CategoryPageViewTest.php new file mode 100644 index 0000000000000..7f842bf49644e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/Listing/CategoryPageViewTest.php @@ -0,0 +1,109 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Block\Product\Renderer\Configurable\Listing; + +use Magento\Swatches\Block\Product\Renderer\Configurable\ProductPageViewTest; +use Magento\Swatches\Block\Product\Renderer\Listing\Configurable; + +/** + * Test class to check configurable product with swatch attributes view behaviour on category page + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ +class CategoryPageViewTest extends ProductPageViewTest +{ + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->block = $this->layout->createBlock(Configurable::class); + $this->template = 'Magento_Swatches::product/listing/renderer.phtml'; + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_visual_swatch_attribute.php + * + * @dataProvider expectedVisualSwatchDataProvider + * + * @param array $expectedConfig + * @param array $expectedSwatchConfig + * @return void + */ + public function testCategoryPageVisualSwatchAttributeView(array $expectedConfig, array $expectedSwatchConfig): void + { + $this->checkProductViewCategoryPage($expectedConfig, $expectedSwatchConfig, ['visual_swatch_attribute']); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_text_swatch_attribute.php + * + * @dataProvider expectedTextSwatchDataProvider + * + * @param array $expectedConfig + * @param array $expectedSwatchConfig + * @return void + */ + public function testCategoryPageTextSwatchAttributeView(array $expectedConfig, array $expectedSwatchConfig): void + { + $this->checkProductViewCategoryPage($expectedConfig, $expectedSwatchConfig, ['text_swatch_attribute']); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_two_attributes.php + * + * @dataProvider expectedTwoAttributesProvider + * + * @param array $expectedConfig + * @param array $expectedSwatchConfig + * @return void + */ + public function testCategoryPageTwoAttributesView(array $expectedConfig, array $expectedSwatchConfig): void + { + $this->checkProductViewCategoryPage( + $expectedConfig, + $expectedSwatchConfig, + ['visual_swatch_attribute', 'text_swatch_attribute'] + ); + } + + /** + * Check configurable product view on category view page + * + * @param array $expectedConfig + * @param array $expectedSwatchConfig + * @param array $attributes + * @return void + */ + private function checkProductViewCategoryPage( + array $expectedConfig, + array $expectedSwatchConfig, + array $attributes + ): void { + $this->setAttributeUsedInProductListing($attributes); + $this->checkProductView($expectedConfig, $expectedSwatchConfig); + } + + /** + * Set used in product listing attributes value to true + * + * @param array $attributeCodes + * @return void + */ + private function setAttributeUsedInProductListing(array $attributeCodes): void + { + foreach ($attributeCodes as $attributeCode) { + $attribute = $this->productAttributeRepository->get($attributeCode); + $attribute->setUsedInProductListing('1'); + $this->productAttributeRepository->save($attribute); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/PriceTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/PriceTest.php new file mode 100644 index 0000000000000..2c5bf805f92de --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/PriceTest.php @@ -0,0 +1,187 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Block\Product\Renderer\Configurable; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\TierPriceInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Model\Group; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\Result\Page; +use Magento\Swatches\Block\Product\Renderer\Configurable; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Check configurable product price + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoAppArea frontend + */ +class PriceTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var Page */ + private $page; + + /** @var SerializerInterface */ + private $json; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->registry = $this->objectManager->get(Registry::class); + $this->page = $this->objectManager->get(Page::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->json = $this->objectManager->get(SerializerInterface::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->registry->unregister('product'); + + parent::tearDown(); + } + + /** + * @dataProvider childProductsDataProvider + * @magentoDataFixture Magento/Swatches/_files/configurable_product_visual_swatch_attribute.php + * @magentoCache config disabled + * + * @param array $updateData + * @param array $expectedData + * @return void + */ + public function testConfigurableOptionPrices(array $updateData, array $expectedData): void + { + $this->updateProducts($updateData); + $product = $this->productRepository->get('configurable'); + $this->registerProduct($product); + $configurableOptions = $this->getProductSwatchOptionsBlock()->getJsonConfig(); + $optionsData = $this->json->unserialize($configurableOptions); + $this->assertArrayHasKey('optionPrices', $optionsData); + $this->assertEquals($expectedData, array_values($optionsData['optionPrices'])); + } + + /** + * @return array + */ + public function childProductsDataProvider(): array + { + return [ + [ + 'update_data' => [ + 'simple_option_1' => [ + 'special_price' => 50, + ], + 'simple_option_2' => [ + 'special_price' => 58.55, + ], + 'simple_option_3' => [ + 'tier_price' => [ + [ + 'website_id' => 0, + 'cust_group' => Group::CUST_GROUP_ALL, + 'price_qty' => 1, + 'value_type' => TierPriceInterface::PRICE_TYPE_FIXED, + 'price' => 75, + ], + ], + ], + ], + 'expected_data' => [ + [ + 'oldPrice' => ['amount' => 150], + 'basePrice' => ['amount' => 50], + 'finalPrice' => ['amount' => 50], + 'tierPrices' => [], + 'msrpPrice' => ['amount' => null], + ], + [ + 'oldPrice' => ['amount' => 150], + 'basePrice' => ['amount' => 58.55], + 'finalPrice' => ['amount' => 58.55], + 'tierPrices' => [], + 'msrpPrice' => ['amount' => null], + ], + [ + 'oldPrice' => ['amount' => 150], + 'basePrice' => ['amount' => 75], + 'finalPrice' => ['amount' => 75], + 'tierPrices' => [], + 'msrpPrice' => ['amount' => null], + ], + ] + ], + ]; + } + + /** + * Update products. + * + * @param array $data + * @return void + */ + private function updateProducts(array $data): void + { + foreach ($data as $sku => $updateData) { + $product = $this->productRepository->get($sku); + $product->addData($updateData); + $this->productRepository->save($product); + } + } + + /** + * Register the product. + * + * @param ProductInterface $product + * @return void + */ + private function registerProduct(ProductInterface $product): void + { + $this->registry->unregister('product'); + $this->registry->register('product', $product); + } + + /** + * Get product swatch options block. + * + * @return Configurable + */ + private function getProductSwatchOptionsBlock(): Configurable + { + $this->page->addHandle([ + 'default', + 'catalog_product_view', + 'catalog_product_view_type_configurable', + ]); + $this->page->getLayout()->generateXml(); + + return $this->page->getLayout()->getChildBlock('product.info.options.wrapper', 'swatch_options'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/ProductPageViewTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/ProductPageViewTest.php new file mode 100644 index 0000000000000..2d016ef48faf5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/Configurable/ProductPageViewTest.php @@ -0,0 +1,414 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Block\Product\Renderer\Configurable; + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Swatches\Block\Product\Renderer\Configurable; +use Magento\Swatches\Model\Swatch; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test class to check configurable product with swatch attributes view behaviour on product page + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ +class ProductPageViewTest extends TestCase +{ + /** @var ObjectManagerInterface */ + protected $objectManager; + + /** @var Configurable */ + protected $block; + + /** @var string */ + protected $template; + + /** @var ProductAttributeRepositoryInterface */ + protected $productAttributeRepository; + + /** @var LayoutInterface */ + protected $layout; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var Registry */ + private $registry; + + /** @var SerializerInterface */ + private $json; + + /** @var ProductResource */ + private $productResource; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->block = $this->layout->createBlock(Configurable::class); + $this->registry = $this->objectManager->get(Registry::class); + $this->json = $this->objectManager->get(SerializerInterface::class); + $this->productAttributeRepository = $this->objectManager->create(ProductAttributeRepositoryInterface::class); + $this->productResource = $this->objectManager->create(ProductResource::class); + $this->template = Configurable::SWATCH_RENDERER_TEMPLATE; + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_text_swatch_attribute.php + * + * @dataProvider expectedTextSwatchDataProvider + * + * @param array $expectedConfig + * @param array $expectedSwatchConfig + * @return void + */ + public function testProductPageTextSwatchAttributeView(array $expectedConfig, array $expectedSwatchConfig): void + { + $this->checkProductView($expectedConfig, $expectedSwatchConfig); + } + + /** + * @return array + */ + public function expectedTextSwatchDataProvider(): array + { + return [ + [ + 'json_config' => [ + 'text_swatch_attribute' => [ + 'label' => 'Text swatch attribute', + 'options' => [ + ['label' => 'Option 3', 'skus' => ['simple_option_3']], + ['label' => 'Option 1', 'skus' => ['simple_option_1']], + ['label' => 'Option 2', 'skus' => ['simple_option_2']], + ], + ], + ], + 'json_swatch_config' => [ + Swatch::SWATCH_INPUT_TYPE_TEXT => [ + [ + 'type' => Swatch::SWATCH_TYPE_TEXTUAL, + 'value' => 'Swatch 3', + 'label' => 'Option 3', + ], + [ + 'type' => Swatch::SWATCH_TYPE_TEXTUAL, + 'value' => 'Swatch 1', + 'label' => 'Option 1', + ], + [ + 'type' => Swatch::SWATCH_TYPE_TEXTUAL, + 'value' => 'Swatch 2', + 'label' => 'Option 2', + ], + 'additional_data' => "{\"swatch_input_type\":\"text\"}", + ], + + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_visual_swatch_attribute.php + * + * @dataProvider expectedVisualSwatchDataProvider + * + * @param array $expectedConfig + * @param array $expectedSwatchConfig + * @return void + */ + public function testProductPageVisualSwatchAttributeView(array $expectedConfig, array $expectedSwatchConfig): void + { + $this->checkProductView($expectedConfig, $expectedSwatchConfig); + } + + /** + * @return array + */ + public function expectedVisualSwatchDataProvider(): array + { + return [ + [ + 'json_config' => [ + 'visual_swatch_attribute' => [ + 'label' => 'Visual swatch attribute', + 'options' => [ + ['label' => 'option 3', 'skus' => ['simple_option_3']], + ['label' => 'option 2', 'skus' => ['simple_option_2']], + ['label' => 'option 1', 'skus' => ['simple_option_1']], + ], + ], + ], + 'json_swatch_config' => [ + Swatch::SWATCH_INPUT_TYPE_VISUAL => [ + [ + 'type' => Swatch::SWATCH_TYPE_VISUAL_COLOR, + 'value' => '#555555', + 'label' => 'option 1', + ], + [ + 'type' => Swatch::SWATCH_TYPE_VISUAL_COLOR, + 'value' => '#aaaaaa', + 'label' => 'option 2', + ], + [ + 'type' => Swatch::SWATCH_TYPE_VISUAL_COLOR, + 'value' => '#ffffff', + 'label' => 'option 3', + ], + 'additional_data' => "{\"swatch_input_type\":\"visual\"}", + ], + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_two_attributes.php + * + * @dataProvider expectedTwoAttributesProvider + * + * @param array $expectedConfig + * @param array $expectedSwatchConfig + * @return void + */ + public function testProductPageTwoAttributesView(array $expectedConfig, array $expectedSwatchConfig): void + { + $this->checkProductView($expectedConfig, $expectedSwatchConfig); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function expectedTwoAttributesProvider(): array + { + return [ + [ + 'json_config' => [ + 'visual_swatch_attribute' => [ + 'label' => 'Visual swatch attribute', + 'options' => [ + [ + 'label' => 'option 3', + 'skus' => [ + 'simple_option_3_option_3', + 'simple_option_1_option_3', + 'simple_option_2_option_3', + ], + ], + [ + 'label' => 'option 2', + 'skus' => [ + 'simple_option_3_option_2', + 'simple_option_1_option_2', + 'simple_option_2_option_2', + ], + ], + [ + 'label' => 'option 1', + 'skus' => [ + 'simple_option_3_option_1', + 'simple_option_1_option_1', + 'simple_option_2_option_1', + ], + ], + ], + ], + 'text_swatch_attribute' => [ + 'label' => 'Text swatch attribute', + 'options' => [ + [ + 'label' => 'Option 3', + 'skus' => [ + 'simple_option_3_option_1', + 'simple_option_3_option_2', + 'simple_option_3_option_3', + ], + ], + [ + 'label' => 'Option 2', + 'skus' => [ + 'simple_option_2_option_1', + 'simple_option_2_option_2', + 'simple_option_2_option_3', + ], + ], + [ + 'label' => 'Option 1', + 'skus' => [ + 'simple_option_1_option_1', + 'simple_option_1_option_2', + 'simple_option_1_option_3', + ], + ], + ], + ], + + ], + 'json_swatch_config' => [ + Swatch::SWATCH_INPUT_TYPE_VISUAL => [ + [ + 'type' => Swatch::SWATCH_TYPE_VISUAL_COLOR, + 'value' => '#555555', + 'label' => 'option 1', + ], + [ + 'type' => Swatch::SWATCH_TYPE_VISUAL_COLOR, + 'value' => '#aaaaaa', + 'label' => 'option 2', + ], + [ + 'type' => Swatch::SWATCH_TYPE_VISUAL_COLOR, + 'value' => '#ffffff', + 'label' => 'option 3', + ], + 'additional_data' => "{\"swatch_input_type\":\"visual\"}", + ], + Swatch::SWATCH_INPUT_TYPE_TEXT => [ + [ + 'type' => Swatch::SWATCH_TYPE_TEXTUAL, + 'value' => 'Swatch 3', + 'label' => 'Option 3', + ], + [ + 'type' => Swatch::SWATCH_TYPE_TEXTUAL, + 'value' => 'Swatch 1', + 'label' => 'Option 1', + ], + [ + 'type' => Swatch::SWATCH_TYPE_TEXTUAL, + 'value' => 'Swatch 2', + 'label' => 'Option 2', + ], + 'additional_data' => "{\"swatch_input_type\":\"text\"}", + ], + ], + ], + ]; + } + + /** + * Check configurable product view + * + * @param $expectedConfig + * @param $expectedSwatchConfig + * @return void + */ + protected function checkProductView($expectedConfig, $expectedSwatchConfig): void + { + $actualConfig = $this->generateBlockJsonConfigData(); + $this->checkResultIsNotEmpty($actualConfig); + $this->assertConfig($actualConfig['json_config'], $expectedConfig); + $this->assertSwatchConfig($actualConfig['json_swatch_config'], $expectedSwatchConfig); + } + + /** + * Generate block config data + * + * @return array + */ + + private function generateBlockJsonConfigData(): array + { + $product = $this->productRepository->get('configurable'); + $this->block->setProduct($product); + $this->block->setTemplate($this->template); + $jsonConfig = $this->json->unserialize($this->block->getJsonConfig())['attributes'] ?? []; + $jsonSwatchConfig = $this->json->unserialize($this->block->getJsonSwatchConfig()); + + return ['json_config' => $jsonConfig, 'json_swatch_config' => $jsonSwatchConfig]; + } + + /** + * Assert that correct data was generated + * + * @param array $actualData + * @param array $expectedData + * @return void + */ + private function assertSwatchConfig(array $actualData, array $expectedData): void + { + foreach ($actualData as $actualDataItem) { + $currentType = $this->json->unserialize($actualDataItem['additional_data'])['swatch_input_type'] ?? null; + $this->assertNotNull($currentType); + $this->assertEquals($expectedData[$currentType]['additional_data'], $actualDataItem['additional_data']); + unset($actualDataItem['additional_data']); + foreach ($actualDataItem as $item) { + $this->assertContains($item, $expectedData[$currentType]); + } + } + } + + /** + * Assert that correct swatch data was generated + * + * @param array $actualData + * @param array $expectedData + * @return void + */ + private function assertConfig(array $actualData, array $expectedData): void + { + foreach ($actualData as $actualDataItem) { + $expectedItem = $expectedData[$actualDataItem['code']]; + $this->assertEquals($expectedItem['label'], $actualDataItem['label']); + $this->checkOptions($actualDataItem, $expectedItem); + } + } + + /** + * Check result is not not empty + * + * @param array $result + */ + private function checkResultIsNotEmpty(array $result): void + { + foreach ($result as $item) { + $this->assertNotEmpty($item); + } + } + + /** + * Check attribute options + * + * @param array $actualDataItem + * @param array $expectedItem + * @return void + */ + private function checkOptions(array $actualDataItem, array $expectedItem): void + { + foreach ($expectedItem['options'] as $expectedOption) { + $expectedSkus = array_values($expectedOption['skus']); + $expectedIds = array_values($this->productResource->getProductsIdsBySkus($expectedSkus)); + foreach ($actualDataItem['options'] as $option) { + if ($option['label'] === $expectedOption['label']) { + $this->assertEquals( + sort($expectedIds), + sort($option['products']), + 'Wrong product linked as option' + ); + } + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/ConfigurableTest.php new file mode 100644 index 0000000000000..a750abf1e0bb3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/Renderer/ConfigurableTest.php @@ -0,0 +1,252 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Block\Product\Renderer; + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\ConfigurableProduct\Block\Product\View\Type\Configurable; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Store\Model\Store; +use Magento\Swatches\Block\Product\Renderer\Configurable as ConfigurableBlock; +use Magento\Swatches\Helper\Media; +use Magento\Swatches\Model\Swatch; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Tests for configurable products options block with swatch attribute. + * + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoAppArea frontend + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ConfigurableTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var Media + */ + private $swatchHelper; + + /** + * @var UrlBuilder + */ + private $imageUrlBuilder; + + /** + * @var Configurable + */ + private $block; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var ProductAttributeRepositoryInterface + */ + private $productAttributeRepository; + + /** + * @var ProductResource + */ + private $productResource; + + /** + * @var ProductAttributeInterface + */ + private $configurableAttribute; + + /** + * @var ProductInterface + */ + private $product; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->serializer = $this->objectManager->get(SerializerInterface::class); + $this->swatchHelper = $this->objectManager->get(Media::class); + $this->imageUrlBuilder = $this->objectManager->get(UrlBuilder::class); + $this->productAttributeRepository = $this->objectManager->get(ProductAttributeRepositoryInterface::class); + $this->configurableAttribute = $this->productAttributeRepository->get('test_configurable'); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->product = $this->productRepository->get('configurable'); + $this->productResource = $this->objectManager->get(ProductResource::class); + $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(ConfigurableBlock::class); + $this->block->setProduct($this->product); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute.php + * @return void + */ + public function testGetJsonSwatchConfig(): void + { + $expectedOptions = $this->getDefaultOptionsList(); + $expectedOptions['option 2']['value'] = $this->swatchHelper->getSwatchAttributeImage( + Swatch::SWATCH_IMAGE_NAME, + '/visual_swatch_attribute_option_type_image.jpg' + ); + $expectedOptions['option 2']['thumb'] = $this->swatchHelper->getSwatchAttributeImage( + Swatch::SWATCH_THUMBNAIL_NAME, + '/visual_swatch_attribute_option_type_image.jpg' + ); + $config = $this->serializer->unserialize($this->block->getJsonSwatchConfig()); + $this->assertOptionsData($config, $expectedOptions, ['swatch_input_type' => 'visual']); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_image.php + * @return void + */ + public function testGetJsonSwatchConfigUsedProductImage(): void + { + $this->updateAttributeUseProductImageFlag(); + $this->updateProductImage('simple_option_2', '/m/a/magento_image.jpg'); + $expectedOptions = $this->getDefaultOptionsList(); + $expectedOptions['option 2']['value'] = $this->imageUrlBuilder->getUrl( + '/m/a/magento_image.jpg', + 'swatch_image_base' + ); + $expectedOptions['option 2']['thumb'] = $this->imageUrlBuilder->getUrl( + '/m/a/magento_image.jpg', + 'swatch_thumb_base' + ); + $this->assertOptionsData( + $this->serializer->unserialize($this->block->getJsonSwatchConfig()), + $expectedOptions, + ['swatch_input_type' => 'visual', 'use_product_image_for_swatch' => 1] + ); + } + + /** + * @magentoDataFixture Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute.php + * @return void + */ + public function testGetJsonSwatchConfigUsedEmptyProductImage(): void + { + $this->updateAttributeUseProductImageFlag(); + $expectedOptions = $this->getDefaultOptionsList(); + $expectedOptions['option 2']['value'] = $this->swatchHelper->getSwatchAttributeImage( + Swatch::SWATCH_IMAGE_NAME, + '/visual_swatch_attribute_option_type_image.jpg' + ); + $expectedOptions['option 2']['thumb'] = $this->swatchHelper->getSwatchAttributeImage( + Swatch::SWATCH_THUMBNAIL_NAME, + '/visual_swatch_attribute_option_type_image.jpg' + ); + $this->assertOptionsData( + $this->serializer->unserialize($this->block->getJsonSwatchConfig()), + $expectedOptions, + ['swatch_input_type' => 'visual', 'use_product_image_for_swatch' => 1] + ); + } + + /** + * @return array + */ + private function getDefaultOptionsList(): array + { + return [ + 'option 1' => ['type' => '1', 'value' => '#000000', 'label' => 'option 1'], + 'option 2' => ['type' => '2', 'value' => '', 'thumb' => '', 'label' => 'option 2'], + 'option 3' => ['type' => '3', 'value' => null, 'label' => 'option 3'], + ]; + } + + /** + * Asserts swatch options data. + * + * @param array $config + * @param array $expectedOptions + * @param array $expectedAdditional + * @return void + */ + private function assertOptionsData(array $config, array $expectedOptions, array $expectedAdditional): void + { + $this->assertNotEmpty($config); + $resultOptions = $config[$this->configurableAttribute->getAttributeId()]; + foreach ($expectedOptions as $label => $data) { + $resultOption = $resultOptions[$this->configurableAttribute->getSource()->getOptionId($label)]; + $this->assertEquals($data['type'], $resultOption['type']); + $this->assertEquals($data['label'], $resultOption['label']); + $this->assertEquals($data['value'], $resultOption['value']); + if (!empty($data['thumb'])) { + $this->assertEquals($data['thumb'], $resultOption['thumb']); + } + } + $this->assertEquals($expectedAdditional, $this->serializer->unserialize($resultOptions['additional_data'])); + } + + /** + * Updates attribute 'use_product_image_for_swatch' flag. + * + * @return void + */ + private function updateAttributeUseProductImageFlag(): void + { + $this->configurableAttribute->setData('use_product_image_for_swatch', 1); + $this->configurableAttribute = $this->productAttributeRepository->save($this->configurableAttribute); + } + + /** + * Updates Product image. + * + * @param string $sku + * @param string $imageName + * @return void + */ + private function updateProductImage(string $sku, string $imageName): void + { + $product = $this->productRepository->get($sku); + $product->setStoreId(Store::DEFAULT_STORE_ID) + ->setImage($imageName) + ->setSmallImage($imageName) + ->setThumbnail($imageName) + ->setData( + 'media_gallery', + [ + 'images' => [ + [ + 'file' => $imageName, + 'position' => 1, + 'label' => 'Image Alt Text', + 'disabled' => 0, + 'media_type' => 'image' + ], + ] + ] + ) + ->setCanSaveCustomOptions(true); + $this->productResource->save($product); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/TextSwatchTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/TextSwatchTest.php index e9839266b07a0..348afff7fe9ba 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/TextSwatchTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/TextSwatchTest.php @@ -7,7 +7,7 @@ namespace Magento\Swatches\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Entity\Attribute\Source\Table; @@ -15,13 +15,14 @@ * Test cases related to create attribute with input type text swatch. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class TextSwatchTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -35,7 +36,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/VisualSwatchTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/VisualSwatchTest.php index 56b051c8ec9c2..0ee64f0de9ca3 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/VisualSwatchTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Save/InputType/VisualSwatchTest.php @@ -7,7 +7,7 @@ namespace Magento\Swatches\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Entity\Attribute\Source\Table; @@ -15,13 +15,14 @@ * Test cases related to create attribute with input type visual swatch. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class VisualSwatchTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -35,7 +36,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/AbstractUpdateSwatchAttributeTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/AbstractUpdateSwatchAttributeTest.php new file mode 100644 index 0000000000000..2152a9c93419c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/AbstractUpdateSwatchAttributeTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Controller\Adminhtml\Product\Attribute\Update; + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; +use Magento\Swatches\Model\ResourceModel\Swatch\CollectionFactory as SwatchCollectionFactory; +use Magento\Swatches\Model\Swatch; +use Magento\Swatches\Model\SwatchAttributeType; + +/** + * Base update and assert swatch attribute data. + */ +abstract class AbstractUpdateSwatchAttributeTest extends AbstractUpdateAttributeTest +{ + /** @var SwatchAttributeType */ + private $swatchAttributeType; + + /** @var SwatchCollectionFactory */ + private $swatchCollectionFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->swatchAttributeType = $this->_objectManager->get(SwatchAttributeType::class); + $this->swatchCollectionFactory = $this->_objectManager->get(SwatchCollectionFactory::class); + } + + /** + * @inheritdoc + */ + protected function replaceStoreCodeWithId(array $optionsArray): array + { + $optionsArray = parent::replaceStoreCodeWithId($optionsArray); + foreach ($optionsArray as $key => $option) { + if (isset($option['swatch'])) { + $optionsArray[$key]['swatch'] = $this->prepareStoresData($option['swatch']); + } + } + + return $optionsArray; + } + + /** + * @inheritdoc + */ + protected function getActualOptionsData(string $attributeId): array + { + $actualOptionsData = parent::getActualOptionsData($attributeId); + foreach (array_keys($actualOptionsData) as $optionId) { + $actualOptionsData[$optionId]['swatch'] = $this->getAttributeOptionSwatchValues($optionId); + } + + return $actualOptionsData; + } + + /** + * @inheritdoc + */ + protected function prepareStoreOptionsPostData(array $optionsData): array + { + $optionsPostData = parent::prepareStoreOptionsPostData($optionsData); + $swatchType = $this->getSwatchType(); + $swatchOptionsPostData = []; + + foreach ($optionsData as $optionId => $option) { + $data = []; + $data['option' . $swatchType] = $optionsPostData[$optionId]['option']; + $optionSwatch = $swatchType == Swatch::SWATCH_INPUT_TYPE_VISUAL ? $option['swatch'][0] : $option['swatch']; + + $data['swatch' . $swatchType] = [ + 'value' => [ + $optionId => $optionSwatch, + ], + ]; + if (isset($optionsPostData[$optionId]['default'])) { + $data['default' . $swatchType] = $optionsPostData[$optionId]['default']; + } + $swatchOptionsPostData[] = $data; + } + + return $swatchOptionsPostData; + } + + /** + * @inheritdoc + */ + protected function prepareStoreOptionsExpectedData(array $optionsData): array + { + $optionsExpectedData = parent::prepareStoreOptionsExpectedData($optionsData); + $optionsArray = $optionsExpectedData['options_array']; + foreach (array_keys($optionsArray) as $optionId) { + $optionsArray[$optionId]['swatch'] = $optionsData[$optionId]['swatch']; + } + + return [ + 'options_array' => $optionsArray, + 'default_value' => $optionsExpectedData['default_value'], + ]; + } + + /** + * @inheritdoc + */ + protected function assertUpdateAttributeData( + ProductAttributeInterface $attribute, + array $expectedData + ): void { + $this->swatchAttributeType->isSwatchAttribute($attribute); + parent::assertUpdateAttributeData($attribute, $expectedData); + } + + /** + * Get attribute option swatch values by option id. + * + * @param int $optionId + * @return array + */ + private function getAttributeOptionSwatchValues(int $optionId): array + { + $swatchValues = []; + $collection = $this->swatchCollectionFactory->create(); + $collection->addFieldToFilter('option_id', $optionId); + + foreach ($collection as $item) { + $swatchValues[$item->getData('store_id')] = $item->getData('value'); + } + + return $swatchValues; + } + + /** + * Get swatch type. + * + * @return string + */ + abstract protected function getSwatchType(): string; +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/InputType/TextSwatchTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/InputType/TextSwatchTest.php new file mode 100644 index 0000000000000..b62671bef04a4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/InputType/TextSwatchTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Swatches\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateSwatchAttributeTest; +use Magento\Swatches\Model\Swatch; + +/** + * Test cases related to update attribute with input type text swatch. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class TextSwatchTest extends AbstractUpdateSwatchAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getUpdateProvider + * @magentoDataFixture Magento/Swatches/_files/product_text_swatch_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('text_swatch_attribute', $postData); + $this->assertUpdateAttributeProcess('text_swatch_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Swatches/_files/product_text_swatch_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('text_swatch_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Swatches/_files/product_text_swatch_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('text_swatch_attribute', $postData, $expectedData); + } + + /** + * Test update attribute options on stores. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\TextSwatch::getUpdateOptionsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Swatches/_files/product_text_swatch_attribute.php + * + * @param array $postData + * @return void + */ + public function testUpdateOptionsOnStores(array $postData): void + { + $this->processUpdateOptionsOnStores('text_swatch_attribute', $postData); + } + + /** + * @inheritdoc + */ + protected function getSwatchType(): string + { + return Swatch::SWATCH_INPUT_TYPE_TEXT; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/InputType/VisualSwatchTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/InputType/VisualSwatchTest.php new file mode 100644 index 0000000000000..b5aa58bbd3339 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Update/InputType/VisualSwatchTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Swatches\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateSwatchAttributeTest; +use Magento\Swatches\Model\Swatch; + +/** + * Test cases related to update attribute with input type visual swatch. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class VisualSwatchTest extends AbstractUpdateSwatchAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getUpdateProvider + * @magentoDataFixture Magento/Swatches/_files/product_visual_swatch_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('visual_swatch_attribute', $postData); + $this->assertUpdateAttributeProcess('visual_swatch_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Swatches/_files/product_visual_swatch_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('visual_swatch_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Swatches/_files/product_visual_swatch_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('visual_swatch_attribute', $postData, $expectedData); + } + + /** + * Test update attribute options on stores. + * + * @dataProvider \Magento\TestFramework\Swatches\Model\Attribute\DataProvider\VisualSwatch::getUpdateOptionsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Swatches/_files/product_visual_swatch_attribute.php + * + * @param array $postData + * @return void + */ + public function testUpdateOptionsOnStores(array $postData): void + { + $this->processUpdateOptionsOnStores('visual_swatch_attribute', $postData); + } + + /** + * @inheritdoc + */ + protected function getSwatchType(): string + { + return Swatch::SWATCH_INPUT_TYPE_VISUAL; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_text_swatch_attribute.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_text_swatch_attribute.php new file mode 100644 index 0000000000000..b5586acdfeebf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_text_swatch_attribute.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionFactory; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/product_text_swatch_attribute.php'; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('text_swatch_attribute'); +$options = $attribute->getOptions(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +$attributeValues = []; +$associatedProductIds = []; +$rootCategoryId = $baseWebsite->getDefaultStore()->getRootCategoryId(); +array_shift($options); + +foreach ($options as $option) { + $product = $productFactory->create(); + $product->setTypeId(ProductType::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Option ' . $option->getLabel()) + ->setSku(strtolower(str_replace(' ', '_', 'simple ' . $option->getLabel()))) + ->setPrice(150) + ->setTextSwatchAttribute($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product = $productRepository->save($product); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], +]; +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$product = $productFactory->create(); +/** @var ProductExtensionFactory $extensionAttributesFactory */ +$extensionAttributesFactory = $objectManager->get(ProductExtensionFactory::class); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?: $extensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_text_swatch_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_text_swatch_attribute_rollback.php new file mode 100644 index 0000000000000..f5c91e255e1a3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_text_swatch_attribute_rollback.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('text_swatch_attribute'); +$options = $attribute->getOptions(); +array_shift($options); +$productsArray = []; +foreach ($options as $option) { + $productsArray [] = strtolower(str_replace(' ', '_', 'simple ' . $option->getLabel())); +} +$productsArray[] = 'configurable'; +foreach ($productsArray as $sku) { + try { + $productRepository->deleteById($sku); + } catch (NoSuchEntityException $e) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/product_text_swatch_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_two_attributes.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_two_attributes.php new file mode 100644 index 0000000000000..b0d1948ac8be2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_two_attributes.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionFactory; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/product_text_swatch_attribute.php'; +require __DIR__ . '/product_visual_swatch_attribute.php'; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('text_swatch_attribute'); +$secondAttribute = $productAttributeRepository->get('visual_swatch_attribute'); +$options = $attribute->getOptions(); +$secondAttributeOptions = $secondAttribute->getOptions(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var ProductAttributeRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$attributeValues = []; +$secondAttributeValues = []; +$associatedProductIds = []; +$associatedProductIdsViaSecondAttribute = []; +$attributeSetId = $installer->getAttributeSetId(Product::ENTITY, 'Default'); +$productFactory = $objectManager->get(ProductFactory::class); +$rootCategoryId = $baseWebsite->getDefaultStore()->getRootCategoryId(); +array_shift($options); +array_shift($secondAttributeOptions); + +foreach ($options as $option) { + foreach ($secondAttributeOptions as $secondAttrOption) { + $product = $productFactory->create(); + $product->setTypeId(ProductType::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Option ' . $option->getLabel()) + ->setSku( + strtolower( + str_replace(' ', '_', 'simple ' . $option->getLabel() . '_' . $secondAttrOption->getLabel()) + ) + ) + ->setPrice(150) + ->setTextSwatchAttribute($option->getValue()) + ->setVisualSwatchAttribute($secondAttrOption->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product = $productRepository->save($product, true); + $associatedProductIds[] = $product->getId(); + } + + $attributeValues[] = [ + 'label' => 'test1', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; +} +foreach ($secondAttributeOptions as $secondAttrOption) { + $secondAttributeValues[] = [ + 'label' => 'test2', + 'attribute_id' => $secondAttribute->getId(), + 'value_index' => $secondAttrOption->getValue(), + ]; +} + +$allAttributes = [$attribute, $secondAttribute]; +$optionsFactory = $objectManager->get(Factory::class); + +foreach ($allAttributes as $attribute) { + $configurableAttributesData[] = + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attribute->getAttributeCode() === 'text_swatch_attribute' + ? $attributeValues + : $secondAttributeValues, + ]; + +} + +$configurableOptions = $optionsFactory->create($configurableAttributesData); +$product = $productFactory->create(); +/** @var ProductExtensionFactory $extensionAttributesFactory */ +$extensionAttributesFactory = $objectManager->get(ProductExtensionFactory::class); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?: $extensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_two_attributes_rollback.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_two_attributes_rollback.php new file mode 100644 index 0000000000000..c92330e49688b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_two_attributes_rollback.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +$options = $productAttributeRepository->get('text_swatch_attribute')->getOptions(); +$secondAttributeOptions = $productAttributeRepository->get('visual_swatch_attribute')->getOptions(); +array_shift($options); +array_shift($secondAttributeOptions); +$productsArray = []; + +foreach ($options as $option) { + foreach ($secondAttributeOptions as $secondAttrOption) { + $productsArray[] = strtolower( + str_replace(' ', '_', 'simple ' . $option->getLabel() . '_' . $secondAttrOption->getLabel()) + ); + } +} + +$productsArray[] = 'configurable'; +foreach ($productsArray as $sku) { + try { + $productRepository->deleteById($sku); + } catch (NoSuchEntityException $e) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/product_text_swatch_attribute_rollback.php'; +require __DIR__ . '/product_visual_swatch_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_visual_swatch_attribute.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_visual_swatch_attribute.php new file mode 100644 index 0000000000000..c47be2717f5a8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_visual_swatch_attribute.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductExtensionFactory; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ProductFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/product_visual_swatch_attribute.php'; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('visual_swatch_attribute'); +$options = $attribute->getOptions(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsite = $websiteRepository->get('base'); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +$attributeValues = []; +$associatedProductIds = []; +$rootCategoryId = $baseWebsite->getDefaultStore()->getRootCategoryId(); +array_shift($options); + +foreach ($options as $option) { + $product = $productFactory->create(); + $product->setTypeId(ProductType::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Option ' . $option->getLabel()) + ->setSku(strtolower(str_replace(' ', '_', 'simple ' . $option->getLabel()))) + ->setPrice(150) + ->setVisualSwatchAttribute($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product = $productRepository->save($product); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], +]; +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$product = $productFactory->create(); +/** @var ProductExtensionFactory $extensionAttributesFactory */ +$extensionAttributesFactory = $objectManager->get(ProductExtensionFactory::class); +$extensionConfigurableAttributes = $product->getExtensionAttributes() ?: $extensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$baseWebsite->getId()]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setCategoryIds([$rootCategoryId]) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_visual_swatch_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_visual_swatch_attribute_rollback.php new file mode 100644 index 0000000000000..22ea728f36327 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_visual_swatch_attribute_rollback.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductAttributeRepositoryInterface $productAttributeRepository */ +$productAttributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +$attribute = $productAttributeRepository->get('visual_swatch_attribute'); +$options = $attribute->getOptions(); +array_shift($options); +$productsArray = []; + +foreach ($options as $option) { + $productsArray [] = strtolower(str_replace(' ', '_', 'simple ' . $option->getLabel())); +} + +$productsArray[] = 'configurable'; +foreach ($productsArray as $sku) { + try { + $productRepository->deleteById($sku); + } catch (NoSuchEntityException $e) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/product_visual_swatch_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute.php new file mode 100644 index 0000000000000..0d1eb1ce12f76 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +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\Catalog\Model\ProductFactory; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/visual_swatch_attribute_with_different_options_type.php'; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var Factory $optionsFactory */ +$optionsFactory = $objectManager->get(Factory::class); +/** @var ProductExtensionInterfaceFactory $productExtensionAttributes */ +$productExtensionAttributesFactory = $objectManager->get(ProductExtensionInterfaceFactory::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$defaultWebsiteId = $websiteRepository->get('base')->getId(); +$attributeValues = $associatedProductIds = []; +$options = $attribute->getSource()->getAllOptions(); +array_shift($options); +foreach ($options as $option) { + /** @var Product $product */ + $product = $productFactory->create(); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setWebsiteIds([$defaultWebsiteId]) + ->setName('Configurable Option' . $option['label']) + ->setSku('simple_' . str_replace(' ', '_', $option['label'])) + ->setPrice(100) + ->setData('test_configurable', $option['value']) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product = $productRepository->save($product); + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option['value'], + ]; + $associatedProductIds[] = $product->getId(); +} + +/** @var Product $configurableProduct */ +$configurableProduct = $productFactory->create(); +$configurableOptions = $optionsFactory->create( + [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], + ] +); +$extensionConfigurableAttributes = $configurableProduct->getExtensionAttributes() + ?: $productExtensionAttributesFactory->create(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); + +$configurableProduct->setExtensionAttributes($extensionConfigurableAttributes); +$configurableProduct->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($configurableProduct->getDefaultAttributeSetId()) + ->setWebsiteIds([$defaultWebsiteId]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository->save($configurableProduct); diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute_rollback.php new file mode 100644 index 0000000000000..e2cddfa9065e2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/configurable_product_with_visual_swatch_attribute_rollback.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); +$attribute = $attributeRepository->get('test_configurable'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +$options = $attribute->getSource()->getAllOptions(); +array_shift($options); +foreach ($options as $option) { + try { + $productRepository->deleteById('simple_' . str_replace(' ', '_', $option['label'])); + } catch (NoSuchEntityException $e) { + //Product already removed + } +} + +try { + $productRepository->deleteById('configurable'); +} catch (NoSuchEntityException $e) { + //Product already removed +} + +require __DIR__ . '/visual_swatch_attribute_with_different_options_type_rollback.php'; + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/product_visual_swatch_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/product_visual_swatch_attribute_rollback.php index 67157532bdb98..e6b23a757441d 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/_files/product_visual_swatch_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/product_visual_swatch_attribute_rollback.php @@ -11,6 +11,7 @@ use Magento\Catalog\Api\ProductAttributeRepositoryInterface; $objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ $registry = $objectManager->get(Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); @@ -18,7 +19,7 @@ $attributeRepository = $objectManager->create(ProductAttributeRepositoryInterface::class); try { - $attributeRepository->deleteById('text_swatch_attribute'); + $attributeRepository->deleteById('visual_swatch_attribute'); } catch (NoSuchEntityException $e) { } $registry->unregister('isSecureArea'); diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/visual_swatch_attribute_with_different_options_type.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/visual_swatch_attribute_with_different_options_type.php index a4a755c4b92db..8d2b427d7f7f3 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/_files/visual_swatch_attribute_with_different_options_type.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/visual_swatch_attribute_with_different_options_type.php @@ -24,7 +24,7 @@ $imagesGenerator = Bootstrap::getObjectManager()->get(ImagesGenerator::class); /** @var SwatchesMedia $swatchesMedia */ $swatchesMedia = Bootstrap::getObjectManager()->get(SwatchesMedia::class); -$imageName = 'visual_swatch_attribute_option_type_image.jpg'; +$imageName = '/visual_swatch_attribute_option_type_image.jpg'; $imagesGenerator->generate([ 'image-width' => 110, 'image-height' => 90, diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php index ae1f475d43b71..5a2351f7a1660 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php @@ -67,110 +67,110 @@ protected function getCustomerById($id) * @magentoDataFixture Magento/Catalog/_files/products.php * @magentoConfigFixture current_store tax/calculation/algorithm UNIT_BASE_CALCULATION * @dataProvider collectUnitBasedDataProvider + * @param array $quoteItems + * @param array $expected + * @return void */ - public function testCollectUnitBased($expected) + public function testCollectUnitBased(array $quoteItems, array $expected): void { - $customerTaxClassId = $this->getCustomerTaxClassId(); - $fixtureCustomerId = 1; - /** @var \Magento\Customer\Model\Customer $customer */ - $customer = $this->objectManager->create(\Magento\Customer\Model\Customer::class)->load($fixtureCustomerId); - /** @var \Magento\Customer\Model\Group $customerGroup */ - $customerGroup = $this->objectManager->create( - \Magento\Customer\Model\Group::class - )->load( - 'custom_group', - 'customer_group_code' - ); - $customerGroup->setTaxClassId($customerTaxClassId)->save(); - $customer->setGroupId($customerGroup->getId())->save(); + $this->quote($quoteItems, $expected); + } + + public function collectUnitBasedDataProvider(): array + { + return [ + 'one_item' => [ + [ + [ + 'sku' => 'simple', + 'qty' => 2 + ], + ], + [ + [ + 'subtotal' => 20, + 'subtotal_incl_tax' => 21.5, + 'base_subtotal_total_incl_tax' => 21.5, + 'tax_amount' => 1.5, + 'discount_amount' => 0, + ], + [ + [ + 'tax_amount' => 1.5, + 'price' => 10, + 'price_incl_tax' => 10.75, + 'row_total' => 20, + 'row_total_incl_tax' => 21.5, + 'tax_percent' => 7.5, + ], + ], + ], + ], + ]; + } + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @magentoDataFixture Magento/Tax/_files/tax_classes.php + * @magentoDataFixture Magento/Customer/_files/customer_group.php + * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoConfigFixture current_store tax/calculation/algorithm UNIT_BASE_CALCULATION + * @dataProvider collectUnitBasedBundleProductDataProvider + * @param array $quoteItems + * @param array $expected + * @return void + */ + public function testCollectUnitBasedBundleProduct(array $quoteItems, array $expected): void + { $productTaxClassId = $this->getProductTaxClassId(); /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->productRepository->get('simple'); - $product->setTaxClassId($productTaxClassId)->save(); - - $quoteShippingAddressDataObject = $this->getShippingAddressDataObject($fixtureCustomerId); - - /** @var \Magento\Quote\Model\Quote\Address $quoteShippingAddress */ - $quoteShippingAddress = $this->objectManager->create(\Magento\Quote\Model\Quote\Address::class); - $quoteShippingAddress->importCustomerAddressData($quoteShippingAddressDataObject); - $quantity = 2; - - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class); - $quote->setStoreId( - 1 - )->setIsActive( - true - )->setIsMultiShipping( - false - )->assignCustomerWithAddressChange( - $this->getCustomerById($customer->getId()) - )->setShippingAddress( - $quoteShippingAddress - )->setBillingAddress( - $quoteShippingAddress - )->setCheckoutMethod( - $customer->getMode() - )->setPasswordHash( - $customer->encryptPassword($customer->getPassword()) - )->addProduct( - $product->load($product->getId()), - $quantity - ); - $address = $quote->getShippingAddress(); - /** @var \Magento\Quote\Model\ShippingAssignment $shippingAssignment */ - $shippingAssignment = $this->objectManager->create(\Magento\Quote\Model\ShippingAssignment::class); - $shipping = $this->objectManager->create(\Magento\Quote\Model\Shipping::class); - $shipping->setAddress($address); - $shippingAssignment->setShipping($shipping); - $shippingAssignment->setItems($address->getAllItems()); - /** @var \Magento\Quote\Model\Quote\Address\Total $total */ - $total = $this->objectManager->create(\Magento\Quote\Model\Quote\Address\Total::class); - /** @var \Magento\Quote\Model\Quote\Address\Total\Subtotal $addressSubtotalCollector */ - $addressSubtotalCollector = $this->objectManager->create( - \Magento\Quote\Model\Quote\Address\Total\Subtotal::class - ); - $addressSubtotalCollector->collect($quote, $shippingAssignment, $total); - - /** @var \Magento\Tax\Model\Sales\Total\Quote\Subtotal $subtotalCollector */ - $subtotalCollector = $this->objectManager->create(\Magento\Tax\Model\Sales\Total\Quote\Subtotal::class); - $subtotalCollector->collect($quote, $shippingAssignment, $total); - - $this->assertEquals($expected['subtotal'], $total->getSubtotal()); - $this->assertEquals($expected['subtotal'] + $expected['tax_amount'], $total->getSubtotalInclTax()); - $this->assertEquals($expected['subtotal'] + $expected['tax_amount'], $address->getBaseSubtotalTotalInclTax()); - $this->assertEquals($expected['discount_amount'], $total->getDiscountAmount()); - $items = $address->getAllItems(); - /** @var \Magento\Quote\Model\Quote\Address\Item $item */ - $item = $items[0]; - $this->assertEquals($expected['items'][0]['price'], $item->getPrice()); - $this->assertEquals($expected['items'][0]['price_incl_tax'], $item->getPriceInclTax()); - $this->assertEquals($expected['items'][0]['row_total'], $item->getRowTotal()); - $this->assertEquals($expected['items'][0]['row_total_incl_tax'], $item->getRowTotalInclTax()); - $this->assertEquals($expected['items'][0]['tax_percent'], $item->getTaxPercent()); + $childProduct = $this->productRepository->get('simple'); + $childProduct->setTaxClassId($productTaxClassId)->save(); + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->productRepository->get('bundle-product'); + $product->setTaxClassId($productTaxClassId) + ->setPriceType(\Magento\Catalog\Model\Product\Type\AbstractType::CALCULATE_CHILD) + ->save(); + $quoteItems[0]['product'] = $product; + $this->quote($quoteItems, $expected); } - public function collectUnitBasedDataProvider() + public function collectUnitBasedBundleProductDataProvider(): array { return [ 'one_item' => [ [ - 'subtotal' => 20, - 'tax_amount' => 1.5, - 'discount_amount' => 0, - 'items' => [ + [ + 'sku' => 'bundle-product', + 'qty' => 2 + ], + ], + [ + [ + 'subtotal' => 20, + 'subtotal_incl_tax' => 21.5, + 'base_subtotal_total_incl_tax' => 21.5, + 'tax_amount' => 1.5, + 'discount_amount' => 0, + ], + [ [ 'tax_amount' => 1.5, 'price' => 10, 'price_incl_tax' => 10.75, 'row_total' => 20, 'row_total_incl_tax' => 21.5, - 'taxable_amount' => 10, - 'code' => 'simple', - 'type' => 'product', - 'tax_percent' => 7.5, + 'tax_percent' => null, ], + [ + 'tax_amount' => 1.5, + 'price' => 10, + 'price_incl_tax' => 10.75, + 'row_total' => 20, + 'row_total_incl_tax' => 21.5, + 'tax_percent' => 7.5, + ] ], ], ], @@ -178,16 +178,96 @@ public function collectUnitBasedDataProvider() } /** - * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoConfigFixture current_store tax/calculation/cross_border_trade_enabled 1 * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Tax/_files/tax_classes.php * @magentoDataFixture Magento/Customer/_files/customer_group.php - * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoDataFixture Magento/Catalog/_files/products.php * @magentoConfigFixture current_store tax/calculation/algorithm UNIT_BASE_CALCULATION - * @dataProvider collectUnitBasedDataProvider + * @magentoConfigFixture current_store tax/calculation/price_includes_tax 1 + * @dataProvider collectUnitBasedPriceIncludesTaxDataProvider + * @param array $quoteItems + * @param array $expected + */ + public function testCollectUnitBasedPriceIncludesTax(array $quoteItems, array $expected): void + { + $this->quote($quoteItems, $expected); + } + + /** + * @return array + */ + public function collectUnitBasedPriceIncludesTaxDataProvider(): array + { + return [ + [ + [ + [ + 'sku' => 'simple', + 'qty' => 1 + ], + ], + [ + [ + 'subtotal' => 9.3, + 'subtotal_incl_tax' => 10, + 'base_subtotal_total_incl_tax' => 10, + 'tax_amount' => 0.7, + 'discount_amount' => 0, + ], + [ + [ + 'tax_amount' => 0.7, + 'price' => 9.3, + 'price_incl_tax' => 10, + 'row_total' => 9.3, + 'row_total_incl_tax' => 10, + 'tax_percent' => 7.5, + ], + ], + ], + ], + [ + [ + [ + 'sku' => 'simple', + 'qty' => 2 + ], + ], + [ + [ + 'subtotal' => 18.6, + 'subtotal_incl_tax' => 20, + 'base_subtotal_total_incl_tax' => 20, + 'tax_amount' => 1.4, + 'discount_amount' => 0, + ], + [ + [ + 'tax_amount' => 1.4, + 'price' => 9.3, + 'price_incl_tax' => 10, + 'row_total' => 18.6, + 'row_total_incl_tax' => 20, + 'tax_percent' => 7.5, + ], + ], + ], + ], + ]; + } + + /** + * Create quote and assert totals values + * + * @param array $quoteItems + * @param array $expected + * @return void */ - public function testCollectUnitBasedBundleProduct($expected) + private function quote(array $quoteItems, array $expected): void { $customerTaxClassId = $this->getCustomerTaxClassId(); $fixtureCustomerId = 1; @@ -202,23 +282,14 @@ public function testCollectUnitBasedBundleProduct($expected) ); $customerGroup->setTaxClassId($customerTaxClassId)->save(); $customer->setGroupId($customerGroup->getId())->save(); - $productTaxClassId = $this->getProductTaxClassId(); - /** @var \Magento\Catalog\Model\Product $product */ - $childProduct = $this->productRepository->get('simple'); - $childProduct->setTaxClassId($productTaxClassId)->save(); - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->productRepository->get('bundle-product'); - $product->setTaxClassId($productTaxClassId) - ->setPriceType(\Magento\Catalog\Model\Product\Type\AbstractType::CALCULATE_CHILD) - ->save(); + $quoteShippingAddressDataObject = $this->getShippingAddressDataObject($fixtureCustomerId); /** @var \Magento\Quote\Model\Quote\Address $quoteShippingAddress */ $quoteShippingAddress = $this->objectManager->create(\Magento\Quote\Model\Quote\Address::class); $quoteShippingAddress->importCustomerAddressData($quoteShippingAddressDataObject); - $quantity = 2; /** @var \Magento\Quote\Model\Quote $quote */ $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class); @@ -238,17 +309,25 @@ public function testCollectUnitBasedBundleProduct($expected) $customer->getMode() )->setPasswordHash( $customer->encryptPassword($customer->getPassword()) - )->addProduct( - $product->load($product->getId()), - $quantity ); + + foreach ($quoteItems as $quoteItem) { + $product = $quoteItem['product'] ?? null; + if ($product === null) { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->productRepository->get($quoteItem['sku'] ?? 'simple'); + $product->setTaxClassId($productTaxClassId)->save(); + } + $quote->addProduct($product, $quoteItem['qty']); + } + $address = $quote->getShippingAddress(); /** @var \Magento\Quote\Model\ShippingAssignment $shippingAssignment */ $shippingAssignment = $this->objectManager->create(\Magento\Quote\Model\ShippingAssignment::class); $shipping = $this->objectManager->create(\Magento\Quote\Model\Shipping::class); $shipping->setAddress($address); $shippingAssignment->setShipping($shipping); - $shippingAssignment->setItems($quote->getAllItems()); + $shippingAssignment->setItems($address->getAllItems()); /** @var \Magento\Quote\Model\Quote\Address\Total $total */ $total = $this->objectManager->create(\Magento\Quote\Model\Quote\Address\Total::class); /** @var \Magento\Quote\Model\Quote\Address\Total\Subtotal $addressSubtotalCollector */ @@ -261,16 +340,20 @@ public function testCollectUnitBasedBundleProduct($expected) $subtotalCollector = $this->objectManager->create(\Magento\Tax\Model\Sales\Total\Quote\Subtotal::class); $subtotalCollector->collect($quote, $shippingAssignment, $total); - $this->assertEquals($expected['subtotal'], $total->getSubtotal()); - $this->assertEquals($expected['subtotal'] + $expected['tax_amount'], $total->getSubtotalInclTax()); - $this->assertEquals($expected['discount_amount'], $total->getDiscountAmount()); - $items = $address->getAllItems(); - /** @var \Magento\Quote\Model\Quote\Address\Item $item */ - $item = $items[0]; - $this->assertEquals($expected['items'][0]['price'], $item->getPrice()); - $this->assertEquals($expected['items'][0]['price_incl_tax'], $item->getPriceInclTax()); - $this->assertEquals($expected['items'][0]['row_total'], $item->getRowTotal()); - $this->assertEquals($expected['items'][0]['row_total_incl_tax'], $item->getRowTotalInclTax()); + $this->assertEquals($address->getSubtotal(), $total->getSubtotal()); + $this->assertEquals($address->getBaseSubtotal(), $total->getBaseSubtotal()); + $this->assertEquals($address->getBaseSubtotalTotalInclTax(), $total->getBaseSubtotalTotalInclTax()); + + $this->assertEquals($expected[0], $total->toArray(array_keys($expected[0]))); + $actualAddressItemsData = []; + if ($expected[1]) { + $keys = array_keys($expected[1][0]); + /** @var \Magento\Quote\Model\Quote\Address\Item $addressItem */ + foreach ($address->getAllItems() as $addressItem) { + $actualAddressItemsData[] = array_intersect_key($addressItem->toArray($keys), array_flip($keys)); + } + } + $this->assertEquals($expected[1], $actualAddressItemsData); } /** diff --git a/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de.php b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de.php new file mode 100644 index 0000000000000..d2115e4488b14 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Tax\Api\TaxClassManagementInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\ObjectManagerInterface; +use Magento\Tax\Api\TaxClassRepositoryInterface; +use Magento\Tax\Api\TaxRuleRepositoryInterface; +use Magento\Tax\Api\Data\TaxClassInterfaceFactory; +use Magento\Tax\Api\Data\TaxClassInterface; +use Magento\Tax\Api\Data\TaxRateInterfaceFactory; +use Magento\Tax\Api\Data\TaxRateInterface; +use Magento\Tax\Api\TaxRateRepositoryInterface; +use Magento\Tax\Api\Data\TaxRuleInterfaceFactory; +use Magento\Tax\Api\Data\TaxRuleInterface; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var TaxClassRepositoryInterface $taxClassRepository */ +$taxClassRepository = $objectManager->get(TaxClassRepositoryInterface::class); +$taxClassFactory = $objectManager->get(TaxClassInterfaceFactory::class); +/** @var TaxClassInterface $taxClassDataObject */ +$taxClassDataObject = $taxClassFactory->create(); +$taxClassDataObject->setClassName('CustomerTaxClass') + ->setClassType(TaxClassManagementInterface::TYPE_CUSTOMER); +$taxCustomerClassId = $taxClassRepository->save($taxClassDataObject); +$taxClassDataObject = $taxClassFactory->create(); +$taxClassDataObject->setClassName('ProductTaxClass') + ->setClassType(TaxClassManagementInterface::TYPE_PRODUCT); +$taxProductClassId = $taxClassRepository->save($taxClassDataObject); + +$taxRateFactory = $objectManager->get(TaxRateInterfaceFactory::class); +/** @var TaxRateInterface $taxRate */ +$taxRate = $taxRateFactory->create(); +$taxRate->setTaxCountryId('DE') + ->setTaxRegionId(0) + ->setTaxPostcode('*') + ->setCode('Denmark') + ->setRate('21'); +/** @var TaxRateRepositoryInterface $taxRateRepository */ +$taxRateRepository = $objectManager->get(TaxRateRepositoryInterface::class); +$taxRate = $taxRateRepository->save($taxRate); + +/** @var TaxRuleRepositoryInterface $taxRuleRepository */ +$taxRuleRepository = $objectManager->get(TaxRuleRepositoryInterface::class); +$taxRuleFactory = $objectManager->get(TaxRuleInterfaceFactory::class); +/** @var TaxRuleInterface $taxRule */ +$taxRule = $taxRuleFactory->create(); +$taxRule->setCode('Test Rule') + ->setCustomerTaxClassIds([$taxCustomerClassId]) + ->setProductTaxClassIds([$taxProductClassId]) + ->setTaxRateIds([$taxRate->getId()]) + ->setPriority(0); +$taxRuleRepository->save($taxRule); 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 new file mode 100644 index 0000000000000..a87a031ee78a6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_classes_de_rollback.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Tax\Model\ClassModel; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\ObjectManagerInterface; +use Magento\Tax\Api\TaxClassRepositoryInterface; +use Magento\Tax\Api\TaxRuleRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Tax\Model\Calculation\Rule; +use Magento\Tax\Model\Calculation\Rate; +use Magento\Tax\Api\TaxRateRepositoryInterface; + +/** @var ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +$taxClasses = [ + 'CustomerTaxClass', + 'ProductTaxClass', +]; +$taxRuleRepository = $objectManager->get(TaxRuleRepositoryInterface::class); +/** @var SearchCriteriaBuilder $searchBuilder */ +$searchBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchBuilder->addFilter(Rule::KEY_CODE, 'Test Rule') + ->create(); +$taxRules = $taxRuleRepository->getList($searchCriteria) + ->getItems(); +foreach ($taxRules as $taxRule) { + try { + $taxRuleRepository->delete($taxRule); + } catch (NoSuchEntityException $exception) { + //Rule already removed + } +} +$searchCriteria = $searchBuilder->addFilter(ClassModel::KEY_NAME, $taxClasses, 'in') + ->create(); +/** @var TaxClassRepositoryInterface $groupRepository */ +$taxClassRepository = $objectManager->get(TaxClassRepositoryInterface::class); +$taxClasses = $taxClassRepository->getList($searchCriteria) + ->getItems(); +foreach ($taxClasses as $taxClass) { + try { + $taxClassRepository->delete($taxClass); + } catch (NoSuchEntityException $exception) { + //TaxClass already removed + } +} +$searchCriteria = $searchBuilder->addFilter(Rate::KEY_CODE, 'Denmark') + ->create(); +/** @var TaxRateRepositoryInterface $groupRepository */ +$taxRateRepository = $objectManager->get(TaxRateRepositoryInterface::class); +$taxRates = $taxRateRepository->getList($searchCriteria) + ->getItems(); +foreach ($taxRates as $taxRate) { + try { + $taxRateRepository->delete($taxRate); + } catch (NoSuchEntityException $exception) { + //TaxRate already removed + } +} diff --git a/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Save/InputType/FixedProductTaxTest.php b/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Save/InputType/FixedProductTaxTest.php index 5a6065d249b51..0c9b648a6123b 100644 --- a/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Save/InputType/FixedProductTaxTest.php +++ b/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Save/InputType/FixedProductTaxTest.php @@ -7,19 +7,20 @@ namespace Magento\Weee\Controller\Adminhtml\Product\Attribute\Save\InputType; -use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\InputType\AbstractSaveAttributeTest; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save\AbstractSaveAttributeTest; /** * Test cases related to create attribute with input type fixed product tax. * * @magentoDbIsolation enabled + * @magentoAppArea adminhtml */ class FixedProductTaxTest extends AbstractSaveAttributeTest { /** * Test create attribute and compare attribute data and input data. * - * @dataProvider \Magento\TestFramework\Weee\Model\Attribute\DataProvider\FixedProductTax::getAttributeDataWithCheckArray() + * @dataProvider \Magento\TestFramework\Weee\Model\Attribute\DataProvider\FixedProductTax::getAttributeDataWithCheckArray * * @param array $attributePostData * @param array $checkArray @@ -33,7 +34,7 @@ public function testCreateAttribute(array $attributePostData, array $checkArray) /** * Test create attribute with error. * - * @dataProvider \Magento\TestFramework\Weee\Model\Attribute\DataProvider\FixedProductTax::getAttributeDataWithErrorMessage() + * @dataProvider \Magento\TestFramework\Weee\Model\Attribute\DataProvider\FixedProductTax::getAttributeDataWithErrorMessage * * @param array $attributePostData * @param string $errorMessage diff --git a/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Update/InputType/FixedProductTaxTest.php b/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Update/InputType/FixedProductTaxTest.php new file mode 100644 index 0000000000000..ec788bde0a002 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Weee/Controller/Adminhtml/Product/Attribute/Update/InputType/FixedProductTaxTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Weee\Controller\Adminhtml\Product\Attribute\Update\InputType; + +use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Update\AbstractUpdateAttributeTest; + +/** + * Test cases related to update attribute with input type fixed product tax. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + */ +class FixedProductTaxTest extends AbstractUpdateAttributeTest +{ + /** + * Test update attribute. + * + * @dataProvider \Magento\TestFramework\Weee\Model\Attribute\DataProvider\FixedProductTax::getUpdateProvider + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateAttribute(array $postData, array $expectedData): void + { + $this->updateAttributeUsingData('fixed_product_attribute', $postData); + $this->assertUpdateAttributeProcess('fixed_product_attribute', $postData, $expectedData); + } + + /** + * Test update attribute with error. + * + * @dataProvider \Magento\TestFramework\Weee\Model\Attribute\DataProvider\FixedProductTax::getUpdateProviderWithErrorMessage + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * + * @param array $postData + * @param string $errorMessage + * @return void + */ + public function testUpdateAttributeWithError(array $postData, string $errorMessage): void + { + $this->updateAttributeUsingData('fixed_product_attribute', $postData); + $this->assertErrorSessionMessages($errorMessage); + } + + /** + * Test update attribute frontend labels on stores. + * + * @dataProvider \Magento\TestFramework\Weee\Model\Attribute\DataProvider\FixedProductTax::getUpdateFrontendLabelsProvider + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @magentoDataFixture Magento/Weee/_files/fixed_product_attribute.php + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testUpdateFrontendLabelOnStores(array $postData, array $expectedData): void + { + $this->processUpdateFrontendLabelOnStores('fixed_product_attribute', $postData, $expectedData); + } +} diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js index 46d9e1974bdb7..f6f4927aaeda2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/view/shipping.test.js @@ -12,7 +12,7 @@ require.config({ } }); -define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Squire, ko, $, registry) { +define(['squire', 'ko', 'jquery', 'jquery/validate'], function (Squire, ko, $) { 'use strict'; var injector = new Squire(), @@ -20,6 +20,16 @@ define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Sq openModal: jasmine.createSpy(), closeModal: jasmine.createSpy() }, + country = { + /** Stub */ + on: function () {}, + + /** Stub */ + get: function () {}, + + /** Stub */ + set: function () {} + }, mocks = { 'Magento_Customer/js/model/customer': { isLoggedIn: ko.observable() @@ -28,17 +38,7 @@ define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Sq 'Magento_Checkout/js/model/address-converter': jasmine.createSpy(), 'Magento_Checkout/js/model/quote': { isVirtual: jasmine.createSpy(), - shippingMethod: ko.observable(), - - /** - * Stub - */ - shippingAddress: function () { - - return { - 'countryId': 'AD' - }; - } + shippingMethod: ko.observable() }, 'Magento_Checkout/js/action/create-shipping-address': jasmine.createSpy().and.returnValue( jasmine.createSpyObj('newShippingAddress', ['getKey']) @@ -62,7 +62,18 @@ define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Sq 'checkoutData', ['setSelectedShippingAddress', 'setNewCustomerShippingAddress', 'setSelectedShippingRate'] ), - 'Magento_Ui/js/lib/registry/registry': registry, + 'Magento_Ui/js/lib/registry/registry': { + async: jasmine.createSpy().and.returnValue(function () {}), + create: jasmine.createSpy(), + set: jasmine.createSpy(), + get: jasmine.createSpy().and.callFake(function (query) { + if (query === 'test.shippingAddress.shipping-address-fieldset.country_id') { + return country; + } else if (query === 'checkout.errors') { + return {}; + } + }) + }, 'Magento_Checkout/js/model/shipping-rate-service': jasmine.createSpy() }, obj; @@ -73,7 +84,6 @@ define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Sq obj = new Constr({ provider: 'provName', name: '', - parentName: 'test', index: '', popUpForm: { options: { @@ -174,16 +184,6 @@ define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Sq describe('"validateShippingInformation" method', function () { it('Check method call on negative cases.', function () { - /* jscs:disable */ - var country = { - on: function () {}, - get: function () {}, - set: function () {} - }; - /* jscs:enable */ - - registry.set('test.shippingAddress.shipping-address-fieldset.country_id', country); - registry.set('checkout.errors', {}); obj.source = { get: jasmine.createSpy().and.returnValue(true), set: jasmine.createSpy(), @@ -199,20 +199,10 @@ define(['squire', 'ko', 'jquery', 'uiRegistry', 'jquery/validate'], function (Sq expect(obj.validateShippingInformation()).toBeFalsy(); }); it('Check method call on positive case.', function () { - /* jscs:disable */ - var country = { - on: function () {}, - get: function () {}, - set: function () {} - }; - /* jscs:enable */ - $('body').append('<form data-role="email-with-possible-login">' + '<input type="text" name="username" />' + '</form>'); - registry.set('test.shippingAddress.shipping-address-fieldset.country_id', country); - registry.set('checkout.errors', {}); obj.source = { get: jasmine.createSpy().and.returnValue(true), set: jasmine.createSpy(), diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/qty-configurable.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/qty-configurable.test.js new file mode 100644 index 0000000000000..1c81c9fabf85e --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/qty-configurable.test.js @@ -0,0 +1,107 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define(['squire'], function (Squire) { + 'use strict'; + + var injector = new Squire(), + Component, + params = { + name: 'qty-element', + dataScope: '', + value: 1000, + setListeners: jasmine.createSpy().and.callFake(function () { + return this; + }), + setLinks: jasmine.createSpy().and.callFake(function () { + this.isConfigurable = false; + + return this; + }) + }; + + beforeEach(function (done) { + injector.require( + ['Magento_ConfigurableProduct/js/components/qty-configurable'], function (QtyConfigurable) { + Component = QtyConfigurable; + done(); + }); + }); + + afterEach(function () { + try { + injector.remove(); + injector.clean(); + } catch (e) { + } + }); + + describe('Magento_ConfigurableProduct/js/components/qty-configurable', function () { + it('Product is not configurable by default', function () { + var component = new Component(params); + + expect(component.disabled()).toBeFalsy(); + expect(component.value()).toEqual(1000); + }); + + it('State of component does not changed', function () { + var component = new Component(params); + + expect(component.disabled()).toBeFalsy(); + + component.value(99); + component.handleQtyValue(false); + + expect(component.disabled()).toBeFalsy(); + expect(component.value()).toEqual(99); + }); + + it('Product changed to configurable', function () { + var component = new Component(params); + + expect(component.disabled()).toBeFalsy(); + expect(component.value()).toEqual(1000); + + component.handleQtyValue(true); + + expect(component.disabled()).toBeTruthy(); + expect(component.value()).toEqual(''); + }); + + it('Product is configurable by default', function () { + var component = new Component($.extend({}, params, { + // eslint-disable-next-line max-nested-callbacks + setLinks: jasmine.createSpy().and.callFake(function () { + this.isConfigurable = true; + + return this; + }) + })); + + expect(component.disabled()).toBeTruthy(); + expect(component.value()).toEqual(''); + }); + + it('Product changed from configurable to another one', function () { + var component = new Component($.extend({}, params, { + // eslint-disable-next-line max-nested-callbacks + setLinks: jasmine.createSpy().and.callFake(function () { + this.isConfigurable = true; + + return this; + }) + })); + + expect(component.disabled()).toBeTruthy(); + expect(component.value()).toEqual(''); + + component.value(100); + component.handleQtyValue(false); + + expect(component.disabled()).toBeFalsy(); + expect(component.value()).toEqual(100); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/frontend/js/configurable.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/frontend/js/configurable.test.js new file mode 100644 index 0000000000000..21492b20b779c --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/frontend/js/configurable.test.js @@ -0,0 +1,69 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_ConfigurableProduct/js/configurable' +], function ($, Configurable) { + 'use strict'; + + var widget, + option = '<select name=\'super_attribute[93]\'' + + 'data-selector=\'super_attribute[93]\'' + + 'data-validate=\'{required:true}\'' + + 'id=\'attribute93\'' + + 'class=\'super-attribute-select\'>' + + '<option value=\'\'></option>' + + '</select>', + selectElement = $(option); + + beforeEach(function () { + widget = new Configurable(); + widget.options = { + spConfig: { + chooseText: 'Chose an Option...', + attributes: + { + 'size': { + options: [ + { + id: '2', + value: '2' + }, + { + id: 3, + value: 'red' + + } + ] + } + }, + prices: { + finalPrice: { + amount: 12 + } + } + }, + values: { + } + }; + }); + + describe('Magento_ConfigurableProduct/js/configurable', function () { + + it('check if attribute value is possible to be set as configurable option', function () { + expect($.mage.configurable).toBeDefined(); + widget._parseQueryParams('size=2'); + expect(widget.options.values.size).toBe('2'); + }); + + it('check that attribute value is not set if provided option does not exists', function () { + expect($.mage.configurable).toBeDefined(); + widget._parseQueryParams('size=10'); + widget._fillSelect(selectElement[0]); + expect(widget.options.values.size).toBe(undefined); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Signifyd/frontend/js/Fingerprint.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Signifyd/frontend/js/Fingerprint.test.js index 0be178c5a31f0..a9d9fe8d08047 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Signifyd/frontend/js/Fingerprint.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Signifyd/frontend/js/Fingerprint.test.js @@ -10,6 +10,16 @@ define([ /*eslint max-nested-callbacks: ["error", 5]*/ describe('Signifyd device fingerprint client script', function () { + var originalTimeout; + + beforeEach(function () { + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 12000; + }); + + afterEach(function () { + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; + }); it('SIGNIFYD_GLOBAL object initialization check', function (done) { var script = document.createElement('script'); @@ -32,7 +42,6 @@ define([ expect(signifyd.scriptTagHasLoaded()).toBe(true); done(); }, 10000); - - }, 12000); + }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js index fa149f285c0e3..5726184df2103 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js @@ -3,30 +3,50 @@ * See COPYING.txt for license details. */ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + define([ - 'underscore', - 'uiRegistry', - 'Magento_Ui/js/form/form' -], function (_, registry, Constr) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/form', function () { - - var obj = new Constr({ - provider: 'provName', - name: '', - index: '' - }); - - registry.set('provName', { - /** Stub */ - on: function () {}, - - /** Stub */ - get: function () {}, - - /** Stub */ - set: function () {} + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + options: jasmine.createSpy(), + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + } + }, + obj, + dataScope = 'dataScope'; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/form' + ], function (Constr) { + obj = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }); + + done(); + }); }); describe('"initAdapter" method', function () { diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php index 438d9530ec5d5..e45bb324306c0 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php @@ -45,6 +45,11 @@ class ComposerTest extends \PHPUnit\Framework\TestCase */ private static $moduleNameBlacklist; + /** + * @var string + */ + private static $magentoFrameworkLibraryName = 'magento/framework'; + public static function setUpBeforeClass() { self::$root = BP; @@ -68,6 +73,7 @@ public static function getBlacklist(string $pattern) { $blacklist = []; foreach (glob($pattern) as $list) { + //phpcs:ignore Magento2.Performance.ForeachArrayMerge $blacklist = array_merge($blacklist, file($list, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); } return $blacklist; @@ -311,9 +317,9 @@ private function assertDependsOnPhp(\StdClass $json) private function assertDependsOnFramework(\StdClass $json) { $this->assertObjectHasAttribute( - 'magento/framework', + self::$magentoFrameworkLibraryName, $json, - 'This component is expected to depend on magento/framework' + 'This component is expected to depend on ' . self::$magentoFrameworkLibraryName ); } @@ -496,4 +502,42 @@ private function checkProject() ); } } + + /** + * Check the correspondence between the root composer file and magento/framework composer file. + */ + public function testConsistencyOfDeclarationsInComposerFiles() + { + if (strpos(self::$rootJson['name'], 'magento/project-') !== false) { + // The Dependency test is skipped for vendor/magento build + self::markTestSkipped( + 'The build is running for composer installation. Consistency test for composer files is skipped.' + ); + } + + $componentRegistrar = new ComponentRegistrar(); + $magentoFrameworkLibraryDir = + $componentRegistrar->getPath(ComponentRegistrar::LIBRARY, self::$magentoFrameworkLibraryName); + $magentoFrameworkComposerFile = + json_decode( + file_get_contents($magentoFrameworkLibraryDir . DIRECTORY_SEPARATOR . 'composer.json'), + true + ); + + $inconsistentDependencies = []; + foreach ($magentoFrameworkComposerFile['require'] as $dependency => $constraint) { + if (isset(self::$rootJson['require'][$dependency]) + && self::$rootJson['require'][$dependency] !== $constraint + ) { + $inconsistentDependencies[] = $dependency; + } + } + + $this->assertEmpty( + $inconsistentDependencies, + 'There is a discrepancy between the declared versions of the following modules in "' + . self::$magentoFrameworkLibraryName . '" and the root composer.json: ' + . implode(', ', $inconsistentDependencies) + ); + } } diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php b/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php index 876944e0027b4..15b3dc0e0a899 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php @@ -64,21 +64,6 @@ public static function setUpBeforeClass() } } - /** - * Test changes for module.xml files - */ - public function testModuleXmlFiles() - { - if (!self::$actualBranch) { - preg_match_all('|etc/module\.xml$|mi', self::$changedFileList, $matches); - $this->assertEmpty( - reset($matches), - 'module.xml changes for patch releases in non-actual branches are not allowed:' . PHP_EOL . - implode(PHP_EOL, array_values(reset($matches))) - ); - } - } - /** * Test changes for files in Module Setup dir */ diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 59b171a86e1cd..411f02e2c5930 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -217,4 +217,5 @@ Magento/Config/App/Config/Type Magento/InventoryReservationCli/Test/Integration Magento/InventoryAdminUi/Controller/Adminhtml Magento/Newsletter/Model/Queue -Magento/Framework/Mail/Template \ No newline at end of file +Magento/Framework/Mail/Template +Magento/CheckoutAgreements/Model/Checkout/Plugin diff --git a/lib/internal/Magento/Framework/App/Router/Base.php b/lib/internal/Magento/Framework/App/Router/Base.php index 9c0d1633e8bba..a0a3f6f8fd4aa 100644 --- a/lib/internal/Magento/Framework/App/Router/Base.php +++ b/lib/internal/Magento/Framework/App/Router/Base.php @@ -374,6 +374,6 @@ protected function _checkShouldBeSecure(\Magento\Framework\App\RequestInterface */ protected function _shouldRedirectToSecure() { - return $this->_url->getUseSession(); + return false; } } diff --git a/lib/internal/Magento/Framework/HTTP/Client/Curl.php b/lib/internal/Magento/Framework/HTTP/Client/Curl.php index 8b90897481259..60825e231504d 100644 --- a/lib/internal/Magento/Framework/HTTP/Client/Curl.php +++ b/lib/internal/Magento/Framework/HTTP/Client/Curl.php @@ -107,9 +107,9 @@ class Curl implements \Magento\Framework\HTTP\ClientInterface protected $_headerCount = 0; /** - * Set request timeout, msec + * Set request timeout * - * @param int $value + * @param int $value value in seconds * @return void */ public function setTimeout($value) @@ -423,6 +423,7 @@ protected function makeRequest($method, $uri, $params = []) */ public function doError($string) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception($string); } diff --git a/lib/internal/Magento/Framework/Image.php b/lib/internal/Magento/Framework/Image.php index b3867c0197b79..64cd009a84a3c 100644 --- a/lib/internal/Magento/Framework/Image.php +++ b/lib/internal/Magento/Framework/Image.php @@ -49,7 +49,7 @@ public function open() $this->_adapter->checkDependencies(); if (!file_exists($this->_fileName)) { - throw new \Exception("File '{$this->_fileName}' does not exist."); + throw new \RuntimeException("File '{$this->_fileName}' does not exist."); } $this->_adapter->open($this->_fileName); @@ -85,6 +85,7 @@ public function save($destination = null, $newFileName = null) * @param int $angle * @access public * @return void + * @deprecated unused */ public function rotate($angle) { @@ -94,7 +95,7 @@ public function rotate($angle) /** * Crop an image. * - * @param int $top Default value is 0 + * @param int $top Default value is 0 * @param int $left Default value is 0 * @param int $right Default value is 0 * @param int $bottom Default value is 0 @@ -194,7 +195,7 @@ public function quality($value) * @param int $watermarkImageOpacity Watermark image opacity. * @param bool $repeat Enable or disable watermark brick. * @access public - * @throws \Exception + * @throws \RuntimeException * @return void */ public function watermark( @@ -205,7 +206,7 @@ public function watermark( $repeat = false ) { if (!file_exists($watermarkImage)) { - throw new \Exception("Required file '{$watermarkImage}' does not exists."); + throw new \RuntimeException("Required file '{$watermarkImage}' does not exists."); } $this->_adapter->watermark($watermarkImage, $positionX, $positionY, $watermarkImageOpacity, $repeat); } @@ -232,16 +233,19 @@ public function getImageType() return $this->_adapter->getImageType(); } + // phpcs:disable Magento2.CodeAnalysis.EmptyBlock /** * Process * - * @access public + * @access public, * @return void */ public function process() { } + // phpcs:enable Magento2.CodeAnalysis.EmptyBlock + // phpcs:disable Magento2.CodeAnalysis.EmptyBlock /** * Instruction * @@ -251,6 +255,7 @@ public function process() public function instruction() { } + // phpcs:enable Magento2.CodeAnalysis.EmptyBlock /** * Set image background color diff --git a/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php b/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php index b06f2f9e62397..88dbd69405471 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php +++ b/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php @@ -41,9 +41,6 @@ abstract class AbstractAdapter implements AdapterInterface const POSITION_CENTER = 'center'; - /** - * Default font size - */ const DEFAULT_FONT_SIZE = 15; /** @@ -204,6 +201,7 @@ abstract public function resize($width = null, $height = null); * * @param int $angle * @return void + * @deprecated unused */ abstract public function rotate($angle); diff --git a/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php b/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php index b31ed5c773495..736686968b374 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php +++ b/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php @@ -28,6 +28,8 @@ interface AdapterInterface public function getColorAt($x, $y); /** + * Render image and return its binary contents + * * @see \Magento\Framework\Image\Adapter\AbstractAdapter::getImage * @return string */ @@ -99,6 +101,7 @@ public function crop($top = 0, $left = 0, $right = 0, $bottom = 0); /** * Save image to specific path. + * * If some folders of path does not exist they will be created * * @param null|string $destination @@ -113,6 +116,7 @@ public function save($destination = null, $newName = null); * * @param int $angle * @return void + * @deprecated unused */ public function rotate($angle); } diff --git a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php index 7e92b336cfdc0..caa080c02e255 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php +++ b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php @@ -411,6 +411,7 @@ public function resize($frameWidth = null, $frameHeight = null) * * @param int $angle * @return void + * @deprecated unused */ public function rotate($angle) { @@ -822,8 +823,9 @@ protected function _createEmptyImage($width, $height) * @param int $src_w * @param int $src_h * @param int $pct - * * @return bool + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ private function imagecopymergeWithAlphaFix( $dst_im, @@ -859,12 +861,24 @@ private function imagecopymergeWithAlphaFix( return false; } + if (false === imagesavealpha($tmpImg, true)) { + return false; + } + if (false === imagecopy($tmpImg, $src_im, 0, 0, 0, 0, $sizeX, $sizeY)) { return false; } - $transparancy = 127 - (($pct*127)/100); - if (false === imagefilter($tmpImg, IMG_FILTER_COLORIZE, 0, 0, 0, $transparancy)) { + $transparency = 127 - (($pct*127)/100); + if (false === imagefilter($tmpImg, IMG_FILTER_COLORIZE, 0, 0, 0, $transparency)) { + return false; + } + + if (false === imagealphablending($dst_im, true)) { + return false; + } + + if (false === imagesavealpha($dst_im, true)) { return false; } diff --git a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php index cd49f283d33a7..a08d83d33b0ef 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php +++ b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php @@ -5,6 +5,11 @@ */ namespace Magento\Framework\Image\Adapter; +/** + * Wrapper for Imagick image processing PHP Extension. + * + * @link https://www.php.net/manual/en/book.imagick.php + */ class ImageMagick extends \Magento\Framework\Image\Adapter\AbstractAdapter { /** @@ -77,7 +82,11 @@ public function open($filename) try { $this->_imageHandler = new \Imagick($this->_fileName); } catch (\ImagickException $e) { - throw new \Exception(sprintf('Unsupported image format. File: %s', $this->_fileName), $e->getCode(), $e); + throw new \RuntimeException( + sprintf('Unsupported image format. File: %s', $this->_fileName), + $e->getCode(), + $e + ); } $this->backgroundColor(); @@ -86,6 +95,7 @@ public function open($filename) /** * Save image to specific path. + * * If some folders of path does not exist they will be created * * @param null|string $destination @@ -124,6 +134,8 @@ protected function _applyOptions() } /** + * Render image binary content and return it. + * * @see \Magento\Framework\Image\Adapter\AbstractAdapter::getImage * @return string */ @@ -195,6 +207,7 @@ public function resize($frameWidth = null, $frameHeight = null) * * @param int $angle * @return void + * @deprecated unused */ public function rotate($angle) { @@ -333,7 +346,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = ); } } catch (\ImagickException $e) { - throw new \Exception('Unable to create watermark.', $e->getCode(), $e); + throw new \RuntimeException('Unable to create watermark.', $e->getCode(), $e); } // merge layers @@ -346,12 +359,12 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = * Checks required dependencies * * @return void - * @throws \Exception If some of dependencies are missing + * @throws \RuntimeException If some of dependencies are missing */ public function checkDependencies() { if (!class_exists('\Imagick', false)) { - throw new \Exception("Required PHP extension 'Imagick' was not loaded."); + throw new \RuntimeException("Required PHP extension 'Imagick' was not loaded."); } } diff --git a/lib/internal/Magento/Framework/Session/SidResolver.php b/lib/internal/Magento/Framework/Session/SidResolver.php index 041995d20fcd2..fd7af781981a9 100644 --- a/lib/internal/Magento/Framework/Session/SidResolver.php +++ b/lib/internal/Magento/Framework/Session/SidResolver.php @@ -10,7 +10,8 @@ use Magento\Framework\App\State; /** - * Class SidResolver + * Resolves SID by processing request parameters. + * * @deprecated 2.3.3 SIDs in URLs are no longer used */ class SidResolver implements SidResolverInterface @@ -27,11 +28,13 @@ class SidResolver implements SidResolverInterface /** * @var \Magento\Framework\UrlInterface + * @deprecated Not used anymore. */ protected $urlBuilder; /** * @var \Magento\Framework\App\RequestInterface + * @deprecated Not used anymore. */ protected $request; @@ -60,11 +63,6 @@ class SidResolver implements SidResolverInterface */ protected $_scopeType; - /** - * @var State - */ - private $appState; - /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\UrlInterface $urlBuilder @@ -72,6 +70,7 @@ class SidResolver implements SidResolverInterface * @param string $scopeType * @param array $sidNameMap * @param State|null $appState + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, @@ -86,34 +85,19 @@ public function __construct( $this->request = $request; $this->sidNameMap = $sidNameMap; $this->_scopeType = $scopeType; - $this->appState = $appState ?: \Magento\Framework\App\ObjectManager::getInstance()->get(State::class); } /** * Get Sid * * @param SessionManagerInterface $session - * * @return string|null * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getSid(SessionManagerInterface $session) { - if ($this->appState->getAreaCode() !== \Magento\Framework\App\Area::AREA_FRONTEND) { - return null; - } - - $sidKey = null; - - $useSidOnFrontend = $this->getUseSessionInUrl(); - if ($useSidOnFrontend && $this->request->getQuery( - $this->getSessionIdQueryParam($session), - false - ) && $this->urlBuilder->isOwnOriginUrl() - ) { - $sidKey = $this->request->getQuery($this->getSessionIdQueryParam($session)); - } - return $sidKey; + return null; } /** diff --git a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php index 046a9d63fc8f4..ee39b612852b9 100644 --- a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php @@ -108,7 +108,7 @@ protected function getRouteParamsResolverFactory($resolve = true) { $routeParamsResolverFactoryMock = $this->createMock(\Magento\Framework\Url\RouteParamsResolverFactory::class); if ($resolve) { - $routeParamsResolverFactoryMock->expects($this->once())->method('create') + $routeParamsResolverFactoryMock->expects($this->any())->method('create') ->will($this->returnValue($this->routeParamsResolverMock)); } return $routeParamsResolverFactoryMock; @@ -183,7 +183,7 @@ public function testGetUseSession() $this->assertFalse((bool)$model->getUseSession()); $model->setUseSession(true); - $this->assertTrue($model->getUseSession()); + $this->assertFalse($model->getUseSession()); } public function testGetBaseUrlNotLinkType() @@ -467,10 +467,10 @@ public function testGetRedirectUrl() ] ); - $this->sidResolverMock->expects($this->once())->method('getUseSessionInUrl')->will($this->returnValue(true)); - $this->sessionMock->expects($this->once())->method('getSessionIdForHost')->will($this->returnValue(false)); - $this->sidResolverMock->expects($this->once())->method('getUseSessionVar')->will($this->returnValue(true)); - $this->routeParamsResolverMock->expects($this->once())->method('hasData')->with('secure_is_forced') + $this->sidResolverMock->expects($this->any())->method('getUseSessionInUrl')->will($this->returnValue(true)); + $this->sessionMock->expects($this->any())->method('getSessionIdForHost')->will($this->returnValue(false)); + $this->sidResolverMock->expects($this->any())->method('getUseSessionVar')->will($this->returnValue(true)); + $this->routeParamsResolverMock->expects($this->any())->method('hasData')->with('secure_is_forced') ->will($this->returnValue(true)); $this->sidResolverMock->expects($this->never())->method('getSessionIdQueryParam'); $this->queryParamsResolverMock->expects($this->once()) @@ -491,11 +491,11 @@ public function testGetRedirectUrlWithSessionId() ] ); - $this->sidResolverMock->expects($this->once())->method('getUseSessionInUrl')->will($this->returnValue(true)); - $this->sessionMock->expects($this->once())->method('getSessionIdForHost') + $this->sidResolverMock->expects($this->never())->method('getUseSessionInUrl')->will($this->returnValue(true)); + $this->sessionMock->expects($this->never())->method('getSessionIdForHost') ->will($this->returnValue('session-id')); - $this->sidResolverMock->expects($this->once())->method('getUseSessionVar')->will($this->returnValue(false)); - $this->sidResolverMock->expects($this->once())->method('getSessionIdQueryParam'); + $this->sidResolverMock->expects($this->never())->method('getUseSessionVar')->will($this->returnValue(false)); + $this->sidResolverMock->expects($this->never())->method('getSessionIdQueryParam'); $this->queryParamsResolverMock->expects($this->once()) ->method('getQuery') ->will($this->returnValue('foo=bar')); @@ -540,10 +540,10 @@ public function testAddSessionParam() 'queryParamsResolver' => $this->queryParamsResolverMock, ]); - $this->sidResolverMock->expects($this->once())->method('getSessionIdQueryParam')->with($this->sessionMock) + $this->sidResolverMock->expects($this->never())->method('getSessionIdQueryParam')->with($this->sessionMock) ->will($this->returnValue('sid')); - $this->sessionMock->expects($this->once())->method('getSessionId')->will($this->returnValue('session-id')); - $this->queryParamsResolverMock->expects($this->once())->method('setQueryParam')->with('sid', 'session-id'); + $this->sessionMock->expects($this->never())->method('getSessionId')->will($this->returnValue('session-id')); + $this->queryParamsResolverMock->expects($this->never())->method('setQueryParam')->with('sid', 'session-id'); $model->addSessionParam(); } @@ -681,10 +681,10 @@ public function testSessionUrlVarWithMatchedHostsAndBaseUrl($html, $result) ] ); - $requestMock->expects($this->once()) + $requestMock->expects($this->any()) ->method('getHttpHost') ->will($this->returnValue('localhost')); - $this->scopeMock->expects($this->once()) + $this->scopeMock->expects($this->any()) ->method('getBaseUrl') ->will($this->returnValue('http://localhost')); $this->scopeResolverMock->expects($this->any()) @@ -709,20 +709,20 @@ public function testSessionUrlVarWithoutMatchedHostsAndBaseUrl() ] ); - $requestMock->expects($this->once())->method('getHttpHost')->will($this->returnValue('localhost')); - $this->scopeMock->expects($this->once()) + $requestMock->expects($this->never())->method('getHttpHost')->will($this->returnValue('localhost')); + $this->scopeMock->expects($this->any()) ->method('getBaseUrl') ->will($this->returnValue('http://example.com')); $this->scopeResolverMock->expects($this->any()) ->method('getScope') ->will($this->returnValue($this->scopeMock)); - $this->sidResolverMock->expects($this->once())->method('getSessionIdQueryParam') + $this->sidResolverMock->expects($this->never())->method('getSessionIdQueryParam') ->will($this->returnValue('SID')); - $this->sessionMock->expects($this->once())->method('getSessionId') + $this->sessionMock->expects($this->never())->method('getSessionId') ->will($this->returnValue('session-id')); $this->assertEquals( - '<a href="http://example.com/?SID=session-id">www.example.com</a>', + '<a href="http://example.com/">www.example.com</a>', $model->sessionUrlVar('<a href="http://example.com/?___SID=U">www.example.com</a>') ); } diff --git a/lib/internal/Magento/Framework/Url.php b/lib/internal/Magento/Framework/Url.php index c67a20f0a157d..a2318f1169715 100644 --- a/lib/internal/Magento/Framework/Url.php +++ b/lib/internal/Magento/Framework/Url.php @@ -111,7 +111,7 @@ class Url extends \Magento\Framework\DataObject implements \Magento\Framework\Ur * * @var bool */ - protected $_useSession; + protected $_useSession = false; /** * Url security info list @@ -292,10 +292,7 @@ public function setUseSession($useSession) */ public function getUseSession() { - if ($this->_useSession === null) { - $this->_useSession = $this->_sidResolver->getUseSessionInUrl(); - } - return $this->_useSession; + return false; } /** @@ -763,10 +760,6 @@ public function getRouteUrl($routePath = null, $routeParams = null) */ public function addSessionParam() { - $this->setQueryParam( - $this->_sidResolver->getSessionIdQueryParam($this->_session), - $this->_session->getSessionId() - ); return $this; } @@ -945,10 +938,6 @@ private function createUrl($routePath = null, array $routeParams = null) } } - if ($noSid !== true) { - $this->_prepareSessionUrl($url); - } - $query = $this->_getQuery($escapeQuery); if ($query) { $mark = strpos($url, '?') === false ? '?' : ($escapeQuery ? '&' : '&'); @@ -972,18 +961,10 @@ private function createUrl($routePath = null, array $routeParams = null) * @param string $url * * @return \Magento\Framework\UrlInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _prepareSessionUrl($url) { - if (!$this->getUseSession()) { - return $this; - } - $sessionId = $this->_session->getSessionIdForHost($url); - if ($this->_sidResolver->getUseSessionVar() && !$sessionId) { - $this->setQueryParam('___SID', $this->_isSecure() ? 'S' : 'U'); - } elseif ($sessionId) { - $this->setQueryParam($this->_sidResolver->getSessionIdQueryParam($this->_session), $sessionId); - } return $this; } @@ -1004,8 +985,6 @@ public function getRebuiltUrl($url) } $url = $this->getScheme() . '://' . $this->getHost() . $port . $this->getPath(); - $this->_prepareSessionUrl($url); - $query = $this->_getQuery(); if ($query) { $url .= '?' . $query; @@ -1067,15 +1046,10 @@ public function sessionUrlVar($html) */ // @codingStandardsIgnoreEnd function ($match) { - if ($this->useSessionIdForUrl($match[2] == 'S')) { - return $match[1] . $this->_sidResolver->getSessionIdQueryParam($this->_session) . '=' - . $this->_session->getSessionId() . (isset($match[3]) ? $match[3] : ''); - } else { - if ($match[1] == '?') { - return isset($match[3]) ? '?' : ''; - } elseif ($match[1] == '&' || $match[1] == '&') { - return $match[3] ?? ''; - } + if ($match[1] == '?') { + return isset($match[3]) ? '?' : ''; + } elseif ($match[1] == '&' || $match[1] == '&') { + return $match[3] ?? ''; } }, $html @@ -1118,7 +1092,7 @@ public function isOwnOriginUrl() } /** - * Return frontend redirect URL with SID and other session parameters if any + * Return frontend redirect URL without SID * * @param string $url * diff --git a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php index 811f96140a93b..95ff209dd4571 100644 --- a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php +++ b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php @@ -55,6 +55,7 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl * SID Resolver * * @var \Magento\Framework\Session\SidResolverInterface + * @deprecated Not used anymore. */ protected $_sidResolver; @@ -1117,18 +1118,7 @@ protected function _loadCache() return $html; } $loadAction = function () { - $cacheKey = $this->getCacheKey(); - $cacheData = $this->_cache->load($cacheKey); - if ($cacheData) { - $cacheData = str_replace( - $this->_getSidPlaceholder($cacheKey), - $this->_sidResolver->getSessionIdQueryParam($this->_session) - . '=' - . $this->_session->getSessionId(), - $cacheData - ); - } - return $cacheData; + return $this->_cache->load($this->getCacheKey()); }; $saveAction = function ($data) { @@ -1158,11 +1148,6 @@ protected function _saveCache($data) return false; } $cacheKey = $this->getCacheKey(); - $data = str_replace( - $this->_sidResolver->getSessionIdQueryParam($this->_session) . '=' . $this->_session->getSessionId(), - $this->_getSidPlaceholder($cacheKey), - $data - ); $this->_cache->save($data, $cacheKey, array_unique($this->getCacheTags()), $this->getCacheLifetime()); return $this; diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FilterPool.php b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FilterPool.php index 24ea542649875..2c0b16f27df8b 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FilterPool.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/FilterPool.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework\View\Element\UiComponent\DataProvider; @@ -10,19 +11,19 @@ use Magento\Framework\Api\Search\SearchCriteriaInterface; /** - * Class FilterPool + * Filter poll apply filters from search criteria * * @api */ class FilterPool { /** - * @var array + * @var FilterApplierInterface[] */ protected $appliers; /** - * @param array $appliers + * @param FilterApplierInterface[] $appliers */ public function __construct(array $appliers = []) { @@ -30,6 +31,8 @@ public function __construct(array $appliers = []) } /** + * Apply filters from search criteria + * * @param Collection $collection * @param SearchCriteriaInterface $criteria * @return void @@ -38,12 +41,7 @@ public function applyFilters(Collection $collection, SearchCriteriaInterface $cr { foreach ($criteria->getFilterGroups() as $filterGroup) { foreach ($filterGroup->getFilters() as $filter) { - /** @var $filterApplier FilterApplierInterface*/ - if (isset($this->appliers[$filter->getConditionType()])) { - $filterApplier = $this->appliers[$filter->getConditionType()]; - } else { - $filterApplier = $this->appliers['regular']; - } + $filterApplier = $this->appliers[$filter->getConditionType()] ?? $this->appliers['regular']; $filterApplier->apply($collection, $filter); } } diff --git a/lib/internal/Magento/Framework/View/TemplateEngine/Xhtml/Template.php b/lib/internal/Magento/Framework/View/TemplateEngine/Xhtml/Template.php index e75c80777ec0c..64cdf543a7571 100644 --- a/lib/internal/Magento/Framework/View/TemplateEngine/Xhtml/Template.php +++ b/lib/internal/Magento/Framework/View/TemplateEngine/Xhtml/Template.php @@ -6,7 +6,7 @@ namespace Magento\Framework\View\TemplateEngine\Xhtml; /** - * Class Template + * XML Template Engine */ class Template { @@ -34,7 +34,7 @@ public function __construct( ) { $this->logger = $logger; $document = new \DOMDocument(static::XML_VERSION, static::XML_ENCODING); - $document->loadXML($content); + $document->loadXML($content, LIBXML_PARSEHUGE); $this->templateNode = $document->documentElement; } @@ -56,9 +56,12 @@ public function getDocumentElement() */ public function append($content) { - $newFragment = $this->templateNode->ownerDocument->createDocumentFragment(); - $newFragment->appendXML($content); - $this->templateNode->appendChild($newFragment); + $ownerDocument= $this->templateNode->ownerDocument; + $document = new \DOMDocument(); + $document->loadXml($content, LIBXML_PARSEHUGE); + $this->templateNode->appendChild( + $ownerDocument->importNode($document->documentElement, true) + ); } /** diff --git a/lib/internal/Magento/Framework/View/Test/Unit/TemplateEngine/Xhtml/TemplateTest.php b/lib/internal/Magento/Framework/View/Test/Unit/TemplateEngine/Xhtml/TemplateTest.php new file mode 100644 index 0000000000000..3a3a7de47fbab --- /dev/null +++ b/lib/internal/Magento/Framework/View/Test/Unit/TemplateEngine/Xhtml/TemplateTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\View\Test\Unit\TemplateEngine\Xhtml; + +use Magento\Framework\View\TemplateEngine\Xhtml\Template; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +/** + * Test XML template engine + */ +class TemplateTest extends TestCase +{ + /** + * @var Template + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp() + { + parent::setUp(); + $this->model = new Template( + $this->getMockForAbstractClass(LoggerInterface::class), + file_get_contents(__DIR__ . '/../_files/simple.xml') + ); + } + + /** + * Test that xml content is correctly appended to the current element + */ + public function testAppend() + { + $body = <<<HTML +<body> + <h1>Home Page</h1> + <p>CMS homepage content goes here.</p> +</body> +HTML; + $expected = <<<HTML +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--><html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Home Page + + +

Home Page

+

CMS homepage content goes here.

+ + +HTML; + + $this->model->append($body); + $this->assertEquals($expected, (string) $this->model); + } +} diff --git a/lib/internal/Magento/Framework/View/Test/Unit/TemplateEngine/_files/simple.xml b/lib/internal/Magento/Framework/View/Test/Unit/TemplateEngine/_files/simple.xml new file mode 100644 index 0000000000000..0c73702b572c0 --- /dev/null +++ b/lib/internal/Magento/Framework/View/Test/Unit/TemplateEngine/_files/simple.xml @@ -0,0 +1,12 @@ + + + + + Home Page + + diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index dfbfb5a25debe..36cb23943010e 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -27,8 +27,8 @@ "magento/zendframework1": "~1.14.2", "monolog/monolog": "^1.17", "wikimedia/less.php": "~1.8.0", - "symfony/console": "~4.1.0", - "symfony/process": "~4.1.0", + "symfony/console": "~4.4.0", + "symfony/process": "~4.4.0", "tedivm/jshrink": "~1.3.0", "zendframework/zend-code": "~3.3.0", "zendframework/zend-crypt": "^2.6.0", diff --git a/lib/web/requirejs/domReady.js b/lib/web/requirejs/domReady.js index 31bd0d77697ca..099c6f7b79e15 100644 --- a/lib/web/requirejs/domReady.js +++ b/lib/web/requirejs/domReady.js @@ -78,7 +78,7 @@ define(function () { } } - //Check if document already complete, and if so, just trigger page load + //Check if document is no longer loading, and if so, just trigger page load //listeners. Latest webkit browsers also use "interactive", and //will fire the onDOMContentLoaded before "interactive" but not after //entering "interactive" or "complete". More details: @@ -89,8 +89,9 @@ define(function () { //so removing the || document.readyState === "interactive" test. //There is still a window.onload binding that should get fired if //DOMContentLoaded is missed. - if (document.readyState === "complete") { - pageLoaded(); + if (document.readyState !== "loading") { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout(pageLoaded); } } @@ -126,4 +127,4 @@ define(function () { /** END OF PUBLIC API **/ return domReady; -}); \ No newline at end of file +}); diff --git a/nginx.conf.sample b/nginx.conf.sample index 9219400f6aacd..f045edb46a1c2 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -26,6 +26,9 @@ ## ## In production mode, you should uncomment the 'expires' directive in the /static/ location block +# Modules can be loaded only at the very beginning of the Nginx config file, please move the line below to the main config file +# load_module /etc/nginx/modules/ngx_http_image_filter_module.so; + root $MAGE_ROOT/pub; index index.php; @@ -134,6 +137,28 @@ location /static/ { } location /media/ { + +## The following section allows to offload image resizing from Magento instance to the Nginx. +## Catalog image URL format should be set accordingly. +## See https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options +# location ~* ^/media/catalog/.* { +# +# # Replace placeholders and uncomment the line below to serve product images from public S3 +# # See examples of S3 authentication at https://github.com/anomalizer/ngx_aws_auth +# # proxy_pass https://..amazonaws.com; +# +# set $width "-"; +# set $height "-"; +# if ($arg_width != '') { +# set $width $arg_width; +# } +# if ($arg_height != '') { +# set $height $arg_height; +# } +# image_filter resize $width $height; +# image_filter_jpeg_quality 90; +# } + try_files $uri $uri/ /get.php$is_args$args; location ~ ^/media/theme_customization/.*\.xml { diff --git a/setup/src/Magento/Setup/Fixtures/AttributeSet/Pattern.php b/setup/src/Magento/Setup/Fixtures/AttributeSet/Pattern.php index 1d582862c2428..2947bd352aab3 100644 --- a/setup/src/Magento/Setup/Fixtures/AttributeSet/Pattern.php +++ b/setup/src/Magento/Setup/Fixtures/AttributeSet/Pattern.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Setup\Fixtures\AttributeSet; /** @@ -32,7 +34,7 @@ class Pattern * @param string $name * @param int $attributesPerSet * @param int $optionsPerAttribute - * @param callable $attributePattern callback in f($index, $attributeData) format + * @param callable $attributePattern callback in f($index, $attributeData) format * @return array */ public function generateAttributeSet( @@ -46,9 +48,9 @@ public function generateAttributeSet( 'attributes' => [] ]; for ($index = 1; $index <= $attributesPerSet; $index++) { - $attributeData = $this->generateAttribute( + $attributeData = $this->generateAttribute( $index, - is_array($optionsPerAttribute) ? $optionsPerAttribute[$index-1] : $optionsPerAttribute + is_array($optionsPerAttribute) ? $optionsPerAttribute[$index - 1] : $optionsPerAttribute ); if (is_callable($attributePattern)) { $attributeData = $attributePattern($index, $attributeData); @@ -72,7 +74,7 @@ private function generateAttribute($index, $optionsPerAttribute) $attribute['attribute_code'] = $attribute['attribute_code'] . $index; $attribute['frontend_label'] = $attribute['frontend_label'] . $index; $attribute['options'] = ['option' => $this->generateOptions($optionsPerAttribute)]; - $attribute['default_option'] = $attribute['options']['option'][0]['label']; + $attribute['default_value'] = $attribute['options']['option'][0]['value']; return $attribute; } diff --git a/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php b/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php index 8e2e842a7d805..84fb2e5beed4b 100644 --- a/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php +++ b/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php @@ -3,13 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Setup\Fixtures; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\ProductFactory; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection; use Magento\Eav\Model\ResourceModel\Entity\Attribute\CollectionFactory; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection as AttributeSetCollection; use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory; use Magento\Setup\Model\FixtureGenerator\ProductGenerator; use Magento\Setup\Model\SearchTermDescriptionGeneratorFactory; @@ -68,7 +72,7 @@ class SimpleProductsFixture extends Fixture private $defaultAttributeSetId; /** - * @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection + * @var Collection */ private $attributeCollectionFactory; @@ -97,6 +101,11 @@ class SimpleProductsFixture extends Fixture */ private $priceProvider; + /** + * @var int[] + */ + private $additionalAttributeSetIds; + /** * @param FixtureModel $fixtureModel * @param ProductFactory $productFactory @@ -184,35 +193,38 @@ public function execute() 'Short simple product Description %s' ); - $additionalAttributeSets = $this->getAdditionalAttributeSets(); - $attributeSet = function ($index) use ($defaultAttributeSets, $additionalAttributeSets) { + $additionalAttributeSetIds = $this->getAdditionalAttributeSetIds(); + $attributeSet = function ($index) use ($defaultAttributeSets, $additionalAttributeSetIds) { // phpcs:ignore mt_srand($index); $attributeSetCount = count(array_keys($defaultAttributeSets)); if ($attributeSetCount > (($index - 1) % (int)$this->fixtureModel->getValue('categories', 30))) { - // phpcs:ignore Magento2.Functions.DiscouragedFunction + // phpcs:ignore Magento2.Security.InsecureFunction return array_keys($defaultAttributeSets)[mt_rand(0, count(array_keys($defaultAttributeSets)) - 1)]; } else { - $customSetsAmount = count($additionalAttributeSets); + $customSetsAmount = count($additionalAttributeSetIds); return $customSetsAmount - ? $additionalAttributeSets[$index % count($additionalAttributeSets)]['attribute_set_id'] + ? $additionalAttributeSetIds[$index % $customSetsAmount] : $this->getDefaultAttributeSetId(); } }; + $additionalAttributeValues = $this->getAdditionalAttributeValues(); $additionalAttributes = function ( $attributeSetId, $index ) use ( $defaultAttributeSets, - $additionalAttributeSets + $additionalAttributeValues ) { $attributeValues = []; // phpcs:ignore mt_srand($index); - if (isset($defaultAttributeSets[$attributeSetId])) { - foreach ($defaultAttributeSets[$attributeSetId] as $attributeCode => $values) { - // phpcs:ignore Magento2.Functions.DiscouragedFunction + $attributeValuesByAttributeSet = $defaultAttributeSets[$attributeSetId] + ?? $additionalAttributeValues[$attributeSetId]; + if (!empty($attributeValuesByAttributeSet)) { + foreach ($attributeValuesByAttributeSet as $attributeCode => $values) { + // phpcs:ignore Magento2.Security.InsecureFunction $attributeValues[$attributeCode] = $values[mt_rand(0, count($values) - 1)]; } } @@ -279,10 +291,10 @@ private function getDefaultAttributeSetId() } /** - * Get default attribute sets with attributes + * Get default attribute sets with attributes. * - * @see config/attributeSets.xml * @return array + * @see config/attributeSets.xml */ private function getDefaultAttributeSets() { @@ -301,17 +313,7 @@ private function getDefaultAttributeSets() 'attribute_code', array_column($attributesData, 'attribute_code') ); - /** @var \Magento\Eav\Model\Entity\Attribute $attribute */ - foreach ($attributeCollection as $attribute) { - $values = []; - $options = $attribute->getOptions(); - foreach (($options ?: []) as $option) { - if ($option->getValue()) { - $values[] = $option->getValue(); - } - } - $attributes[$attribute->getAttributeSetId()][$attribute->getAttributeCode()] = $values; - } + $attributes = $this->processAttributeValues($attributeCollection, $attributes); } } $attributes[$this->getDefaultAttributeSetId()] = []; @@ -381,16 +383,64 @@ private function readDescriptionConfig($configSrc) } /** - * Get additional attribute sets + * Get additional attribute set ids. + * + * @return int[] + */ + private function getAdditionalAttributeSetIds() + { + if (null === $this->additionalAttributeSetIds) { + /** @var AttributeSetCollection $sets */ + $sets = $this->attributeSetCollectionFactory->create(); + $sets->addFieldToFilter( + 'attribute_set_name', + ['like' => AttributeSetsFixture::PRODUCT_SET_NAME . '%'] + ); + $this->additionalAttributeSetIds = $sets->getAllIds(); + } + + return $this->additionalAttributeSetIds; + } + + /** + * Get values of additional attributes. * - * @return \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection[] + * @return array */ - private function getAdditionalAttributeSets() + private function getAdditionalAttributeValues(): array { - /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection $sets */ - $sets = $this->attributeSetCollectionFactory->create(); - $sets->addFieldToFilter('attribute_set_name', ['like' => AttributeSetsFixture::PRODUCT_SET_NAME . '%']); + $attributeCollection = $this->attributeCollectionFactory->create(); + $attributeCollection->setAttributeSetsFilter($this->getAdditionalAttributeSetIds()) + ->addFieldToFilter('attribute_code', ['like' => 'attribute_set%']); + $attributeCollection->getSelect()->columns(['entity_attribute.attribute_set_id']); + + return $this->processAttributeValues($attributeCollection); + } - return $sets->getData(); + /** + * Maps attribute values by attribute set and attribute code. + * + * @param Collection $attributeCollection + * @param array $attributes + * @return array + */ + private function processAttributeValues( + Collection $attributeCollection, + array $attributes = [] + ): array { + /** @var Attribute $attribute */ + foreach ($attributeCollection as $attribute) { + $values = []; + $options = $attribute->getOptions() ?? []; + $attributeSetId = $attribute->getAttributeSetId() ?? $this->getDefaultAttributeSetId(); + foreach ($options as $option) { + if ($option->getValue()) { + $values[] = $option->getValue(); + } + } + $attributes[$attributeSetId][$attribute->getAttributeCode()] = $values; + } + + return $attributes; } } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/PatternTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/PatternTest.php index f267343bd3fd3..dcb7430c0e82a 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/PatternTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSet/PatternTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Setup\Test\Unit\Fixtures\AttributeSet; @@ -28,7 +29,7 @@ public function testGenerateAttributeSet() 'frontend_label' => 'Attribute 1', 'frontend_input' => 'select', 'backend_type' => 1, - 'default_option' => 'option 1', + 'default_value' => 'option_1', 'options' => [ 'option' => [ [