Skip to content

Commit

Permalink
🔃 [Magento Community Engineering] Community Contributions - 2.4.1-dev…
Browse files Browse the repository at this point in the history
…elop

Accepted Community Pull Requests:
 - #27622: Prevent leaking of variables to global scope (by @krzksz)
 - #27270: Adjust mechanism that moves all scripts to the end of the page (by @krzksz)


Fixed GitHub Issues:
 - #28110: [Issue] Prevent leaking of variables to global scope (reported by @m2-backlog[bot]) has been fixed in #27622 by @krzksz in 2.4.1-develop branch
   Related commits:
     1. 3292638
     2. 503b89a
     3. ab550b0
     4. 829f3b9
     5. 632529f
     6. c8fffc3

 - #26026: JsFooterPlugin sets Http Response content empty if PCRE limit reached (reported by @cundd) has been fixed in #27270 by @krzksz in 2.4.1-develop branch
   Related commits:
     1. 31eeaa7
     2. 4fa5447
     3. 08f2bd4
     4. 2c5bc50
     5. 27805df
     6. 693507f
     7. 31b0456
     8. b54ce80
     9. 036f99f
     10. 9687370
     11. e38b1a1
     12. 1b780be
     13. b7ff624
     14. ffd392c
     15. d963abc
     16. 44131f8
  • Loading branch information
magento-engcom-team authored May 19, 2020
2 parents b7c9e07 + 0d5d09b commit 2d09485
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?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="AdminCreateSimpleProductActionGroup">
<annotations>
<description>Goes to the Admin Product grid page. Clicks on Add. Fills the provided Product details (Name, SKU, Price, Quantity, Category and URL). Clicks on Save. Validates that the Product details are present and correct.</description>
</annotations>
<arguments>
<argument name="category"/>
<argument name="simpleProduct"/>
</arguments>

<amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/>
<click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductDropdown"/>
<click selector="{{AdminProductGridActionSection.addSimpleProduct}}" stepKey="clickAddSimpleProduct"/>
<fillField userInput="{{simpleProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillName"/>
<fillField userInput="{{simpleProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="fillSKU"/>
<fillField userInput="{{simpleProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillPrice"/>
<fillField userInput="{{simpleProduct.quantity}}" selector="{{AdminProductFormSection.productQuantity}}" stepKey="fillQuantity"/>
<searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[{{category.name}}]" stepKey="searchAndSelectCategory"/>
<click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSection"/>
<fillField userInput="{{simpleProduct.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="fillUrlKey"/>

<click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/>
<seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="assertSaveMessageSuccess"/>
<seeInField userInput="{{simpleProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="assertFieldName"/>
<seeInField userInput="{{simpleProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="assertFieldSku"/>
<seeInField userInput="{{simpleProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="assertFieldPrice"/>
<click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSectionAssert"/>
<seeInField userInput="{{simpleProduct.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="assertFieldUrlKey"/>
</actionGroup>
</actionGroups>
22 changes: 22 additions & 0 deletions app/code/Magento/Sales/Test/Mftf/Data/DeveloperConfigData.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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="StorefrontDisableMoveJsCodeBottom">
<!-- Magento default value -->
<data key="path">dev/js/move_script_to_bottom</data>
<data key="label">No</data>
<data key="value">0</data>
</entity>
<entity name="StorefrontEnableMoveJsCodeBottom">
<data key="path">dev/js/move_script_to_bottom</data>
<data key="label">Yes</data>
<data key="value">1</data>
</entity>
</entities>
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?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="StorefrontCreateOrdersWithMoveJSCodeBottomTest">
<annotations>
<title value="Create a product and orders with set 'Move Js code to the bottom' to 'Yes'."/>
<description value="Create a product and orders with a set 'Move JS code to the bottom of the page' to 'Yes' for registered customers and guests."/>
</annotations>
<before>
<magentoCLI command="config:set {{StorefrontEnableMoveJsCodeBottom.path}} {{StorefrontEnableMoveJsCodeBottom.value}}" stepKey="moveJsCodeBottomEnable"/>
<magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/>
<actionGroup ref="AdminLoginActionGroup" stepKey="logInAsAdmin"/>
<actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="goToCategoryPage"/>
<actionGroup ref="CreateCategoryActionGroup" stepKey="createCategory">
<argument name="categoryEntity" value="_defaultCategory"/>
</actionGroup>
<actionGroup ref="AdminCreateSimpleProductActionGroup" stepKey="createSimpleProduct">
<argument name="category" value="_defaultCategory"/>
<argument name="simpleProduct" value="_defaultProduct"/>
</actionGroup>
</before>
<after>
<actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="goToCategoryPage"/>
<actionGroup ref="DeleteCategoryActionGroup" stepKey="deleteCategory">
<argument name="categoryEntity" value="_defaultCategory"/>
</actionGroup>
<actionGroup ref="DeleteProductActionGroup" stepKey="deleteSimpleProduct">
<argument name="productName" value="_defaultProduct.name"/>
</actionGroup>
<actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer">
<argument name="customerEmail" value="Simple_US_Customer.email"/>
</actionGroup>
<actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/>

<magentoCLI command="config:set {{StorefrontDisableMoveJsCodeBottom.path}} {{StorefrontDisableMoveJsCodeBottom.value}}" stepKey="moveJsCodeBottomDisable"/>
<magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/>
</after>

<!-- Go to Storefront and place order for guest -->
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage">
<argument name="productUrl" value="{{_defaultProduct.urlKey}}"/>
</actionGroup>

<actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart">
<argument name="product" value="_defaultProduct"/>
<argument name="productCount" value="1"/>
</actionGroup>
<actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="guestGoToCheckoutFromCart"/>
<actionGroup ref="GuestCheckoutFillNewShippingAddressActionGroup" stepKey="fillNewShippingAddress">
<argument name="customer" value="Simple_Customer_Without_Address" />
<argument name="address" value="US_Address_TX"/>
</actionGroup>
<actionGroup ref="StorefrontSetShippingMethodActionGroup" stepKey="setShippingMethodFreeShipping">
<argument name="shippingMethodName" value="Flat Rate"/>
</actionGroup>
<actionGroup ref="StorefrontCheckoutClickNextOnShippingStepActionGroup" stepKey="goToCheckoutReview"/>
<actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/>
<actionGroup ref="ClickPlaceOrderActionGroup" stepKey="guestPlaceOrder" />

<!-- Go to frontend and make a user account and login with it -->
<actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/>
<actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm">
<argument name="customer" value="Simple_US_Customer"/>
</actionGroup>
<actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/>
<actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="seeSuccessMessage">
<argument name="messageType" value="success"/>
<argument name="message" value="Thank you for registering with Main Website Store."/>
</actionGroup>
<actionGroup ref="StorefrontAddNewCustomerAddressActionGroup" stepKey="AddNewAddress">
<argument name="Address" value="US_Address_TX"/>
</actionGroup>

<!-- Go to Storefront and place order for customer -->
<actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage2">
<argument name="productUrl" value="{{_defaultProduct.urlKey}}"/>
</actionGroup>

<actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart2">
<argument name="product" value="_defaultProduct"/>
<argument name="productCount" value="1"/>
</actionGroup>
<actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="customerGoToCheckoutFromCart"/>

<actionGroup ref="StorefrontSetShippingMethodActionGroup" stepKey="setShippingMethodFreeShipping2">
<argument name="shippingMethodName" value="Flat Rate"/>
</actionGroup>
<actionGroup ref="StorefrontCheckoutClickNextOnShippingStepActionGroup" stepKey="goToCheckoutReview2"/>
<actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment2"/>
<actionGroup ref="ClickPlaceOrderActionGroup" stepKey="customerPlaceOrder" />
<actionGroup ref="StorefrontSignOutActionGroup" stepKey="singOutCustomer" />
</test>
</tests>
97 changes: 71 additions & 26 deletions app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
namespace Magento\Theme\Controller\Result;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Response\HttpInterface as HttpResponseInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\View\Result\Layout;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\App\Response\Http;

/**
* Plugin for putting all js to footer.
* Plugin for putting all JavaScript tags to the end of body.
*/
class JsFooterPlugin
{
Expand All @@ -32,34 +34,77 @@ public function __construct(ScopeConfigInterface $scopeConfig)
}

/**
* Put all javascript to footer before sending the response.
* Moves all JavaScript tags to the end of body if this feature is enabled.
*
* @param Http $subject
* @return void
* @param Layout $subject
* @param Layout $result
* @param HttpResponseInterface|ResponseInterface $httpResponse
* @return Layout (That should be void, actually)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function beforeSendResponse(Http $subject)
public function afterRenderResult(Layout $subject, Layout $result, ResponseInterface $httpResponse)
{
$content = $subject->getContent();
$script = [];
if (is_string($content) && strpos($content, '</body') !== false) {
if ($this->scopeConfig->isSetFlag(
self::XML_PATH_DEV_MOVE_JS_TO_BOTTOM,
ScopeInterface::SCOPE_STORE
)
) {
$pattern = '#<script[^>]*+(?<!text/x-magento-template.)>.*?</script>#is';
$content = preg_replace_callback(
$pattern,
function ($matchPart) use (&$script) {
$script[] = $matchPart[0];
return '';
},
$content
);
$subject->setContent(
str_replace('</body', implode("\n", $script) . "\n</body", $content)
);
if (!$this->isDeferEnabled()) {
return $result;
}

$content = (string)$httpResponse->getContent();
$bodyEndTag = '</body';
$bodyEndTagFound = strrpos($content, $bodyEndTag) !== false;

if ($bodyEndTagFound) {
$scripts = $this->extractScriptTags($content);
if ($scripts) {
$newBodyEndTagPosition = strrpos($content, $bodyEndTag);
$content = substr_replace($content, $scripts . "\n", $newBodyEndTagPosition, 0);
$httpResponse->setContent($content);
}
}

return $result;
}

/**
* Extracts and returns script tags found in given content.
*
* @param string $content
*/
private function extractScriptTags(&$content): string
{
$scripts = '';
$scriptOpen = '<script';
$scriptClose = '</script>';
$scriptOpenPos = strpos($content, $scriptOpen);

while ($scriptOpenPos !== false) {
$scriptClosePos = strpos($content, $scriptClose, $scriptOpenPos);
$script = substr($content, $scriptOpenPos, $scriptClosePos - $scriptOpenPos + strlen($scriptClose));
$isXMagentoTemplate = strpos($script, 'text/x-magento-template') !== false;

if ($isXMagentoTemplate) {
$scriptOpenPos = strpos($content, $scriptOpen, $scriptClosePos);
continue;
}

$scripts .= "\n" . $script;
$content = str_replace($script, '', $content);
// Script cut out, continue search from its position.
$scriptOpenPos = strpos($content, $scriptOpen, $scriptOpenPos);
}

return $scripts;
}

/**
* Returns information whether moving JS to footer is enabled
*
* @return bool
*/
private function isDeferEnabled(): bool
{
return $this->scopeConfig->isSetFlag(
self::XML_PATH_DEV_MOVE_JS_TO_BOTTOM,
ScopeInterface::SCOPE_STORE
);
}
}
Loading

0 comments on commit 2d09485

Please sign in to comment.