ENH Create Requirements::customScriptWithAttributes (#11076)

* ENH Create Requirements::customScriptWithAttributes

* MNT PHP Lint failures corrected

* ENH Refactored attribute handling to avoid API changes, auto lowercase, strong typing

* FIX Updated default value handling for type in customScriptWithAttributes

* DOC Removed white space

* MNT PHP Lint Failures Corrected

* Update src/View/Requirements_Backend.php

Co-authored-by: Steve Boyd <emteknetnz@gmail.com>

* Update src/View/Requirements_Backend.php

Co-authored-by: Steve Boyd <emteknetnz@gmail.com>

* Update tests/php/View/RequirementsTest.php

Co-authored-by: Steve Boyd <emteknetnz@gmail.com>

* FIX Removed extra closing brace in customScriptWithAttributes

* Update src/View/Requirements_Backend.php

Co-authored-by: Steve Boyd <emteknetnz@gmail.com>

* Update src/View/Requirements.php

Co-authored-by: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com>

* MNT Fixed left over content definition and created tests for uniquenessIDs

* MNT Fixed PHP Lint Error

* MNT Fix PHP Lint Error

* FIX Remove attribute when calling customScript with the same uniquenessID

---------

Co-authored-by: Steve Boyd <emteknetnz@gmail.com>
Co-authored-by: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com>
This commit is contained in:
Finlay Metcalfe 2023-12-22 12:00:33 +13:00 committed by GitHub
parent c003dfd4b1
commit 2487c4085d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 144 additions and 2 deletions

View File

@ -142,6 +142,21 @@ class Requirements implements Flushable
self::backend()->customScript($script, $uniquenessID);
}
/**
* Register the given Javascript code into the list of requirements with optional tag
* attributes.
*
* @param string $script The script content as a string (without enclosing `<script>` tag)
* @param array $options List of options. Available options include:
* - 'type' : Specifies the type of script
* - 'crossorigin' : Cross-origin policy for the resource
* @param string|int|null $uniquenessID A unique ID that ensures a piece of code is only added once
*/
public static function customScriptWithAttributes(string $script, array $options = [], string|int|null $uniquenessID = null)
{
self::backend()->customScriptWithAttributes($script, $options, $uniquenessID);
}
/**
* Return all registered custom scripts
*

View File

@ -95,6 +95,14 @@ class Requirements_Backend
*/
protected $customScript = [];
/**
* Maintains attributes for each entry in the `$customScript` array, indexed by either a
* unique identifier (uniquenessID) or the script's array position.
*
* @var array
*/
private array $customScriptAttributes = [];
/**
* All custom CSS rules which are inserted directly at the bottom of the HTML `<head>` tag
*
@ -494,12 +502,43 @@ class Requirements_Backend
public function customScript($script, $uniquenessID = null)
{
if ($uniquenessID) {
if (isset($this->customScriptAttributes[$uniquenessID])) {
unset($this->customScriptAttributes[$uniquenessID]);
}
$this->customScript[$uniquenessID] = $script;
} else {
$this->customScript[] = $script;
}
}
/**
* Register the given Javascript code into the list of requirements with optional tag
* attributes.
*
* @param string $script The script content as a string (without enclosing `<script>` tag)
* @param array $options List of options. Available options include:
* - 'type' : Specifies the type of script
* - 'crossorigin' : Cross-origin policy for the resource
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/
public function customScriptWithAttributes(string $script, array $attributes = [], string|int|null $uniquenessID = null)
{
$attrs = [];
foreach (['type', 'crossorigin'] as $attrKey) {
if (isset($attributes[$attrKey])) {
$attrs[$attrKey] = strtolower($attributes[$attrKey]);
}
}
if ($uniquenessID) {
$this->customScript[$uniquenessID] = $script;
$this->customScriptAttributes[$uniquenessID] = $attrs;
} else {
$this->customScript[] = $script;
$index = count($this->customScript) - 1;
$this->customScriptAttributes[$index] = $attrs;
}
}
/**
* Return all registered custom scripts
*
@ -791,10 +830,17 @@ class Requirements_Backend
}
// Add all inline JavaScript *after* including external files they might rely on
foreach ($this->getCustomScripts() as $script) {
foreach ($this->getCustomScripts() as $key => $script) {
// Build html attributes
$customHtmlAttributes = ['type' => 'application/javascript'];
if (isset($this->customScriptAttributes[$key])) {
foreach ($this->customScriptAttributes[$key] as $attrKey => $attrValue) {
$customHtmlAttributes[$attrKey] = $attrValue;
}
}
$jsRequirements .= HTML::createTag(
'script',
[ 'type' => 'application/javascript' ],
$customHtmlAttributes,
"//<![CDATA[\n{$script}\n//]]>"
);
$jsRequirements .= "\n";

View File

@ -1403,6 +1403,8 @@ EOS
$this->setupRequirements($backend);
$backend->javascript('javascript/RequirementsTest_a.js', ['integrity' => 'abc', 'crossorigin' => 'use-credentials']);
// Tests attribute appending AND lowercase string conversion
$backend->customScriptWithAttributes("//TEST", ['type' => 'module', 'crossorigin' => 'Anonymous']);
$backend->css('css/RequirementsTest_a.css', null, ['integrity' => 'def', 'crossorigin' => 'anonymous']);
$html = $backend->includeInHTML(self::$html_template);
@ -1413,6 +1415,12 @@ EOS
'javascript has correct sri attributes'
);
/* Custom Javascript has correct attribute */
$this->assertMatchesRegularExpression(
'#<script type="module" crossorigin="anonymous"#',
$html,
'custom javascript has correct sri attributes'
);
/* CSS has correct attributes */
$this->assertMatchesRegularExpression(
'#<link .*href=".*/RequirementsTest_a\.css.*" integrity="def" crossorigin="anonymous"#',
@ -1420,4 +1428,77 @@ EOS
'css has correct sri attributes'
);
}
public function testUniquenessID()
{
/** @var Requirements_Backend $backend */
$backend = Injector::inst()->create(Requirements_Backend::class);
$this->setupRequirements($backend);
// Create requirements that are to be overwritten
$backend->customScript("Do Not Display", 42);
$backend->customScriptWithAttributes("Do Not Display", ['type' => 'module', 'crossorigin' => 'use-credentials'], 84);
$backend->customCSS("Do Not Display", 42);
$backend->insertHeadTags("<span>Do Not Display</span>", 42);
// Override
$backend->customScriptWithAttributes("Override", ['type' => 'module', 'crossorigin' => 'use-credentials'], 42);
$backend->customScript("Override", 84);
$backend->customCSS("Override", 42);
$backend->insertHeadTags("<span>Override</span>", 42);
$html = $backend->includeInHTML(self::$html_template);
/* customScript is overwritten by customScriptWithAttributes */
$this->assertMatchesRegularExpression(
"#<script type=\"module\" crossorigin=\"use-credentials\">//<!\[CDATA\[\s*Override\s*//\]\]></script>#s",
$html,
'customScript is displaying latest write'
);
$this->assertDoesNotMatchRegularExpression(
"#<script type=\"application/javascript\">//<!\[CDATA\[\s*Do Not Display\s*//\]\]></script>#s",
$html,
'customScript is correctly not displaying original write'
);
/* customScriptWithAttributes is overwritten by customScript */
$this->assertMatchesRegularExpression(
"#<script type=\"application/javascript\">//<!\[CDATA\[\s*Override\s*//\]\]></script>#s",
$html,
'customScript is displaying latest write and clearing attributes'
);
$this->assertDoesNotMatchRegularExpression(
"#<script type=\"module\" crossorigin=\"use-credentials\">//<!\[CDATA\[\s*Do Not Display\s*//\]\]></script>#s",
$html,
'customScript is displaying latest write'
);
/* customCSS is overwritten */
$this->assertMatchesRegularExpression(
"#<style type=\"text/css\">\s*Override\s*</style>#",
$html,
'customCSS is displaying latest write'
);
$this->assertDoesNotMatchRegularExpression(
"#<style type=\"text/css\">\s*Do Not Display\s*</style>#",
$html,
'customCSS is correctly not displaying original write'
);
/* Head Tags is overwritten */
$this->assertMatchesRegularExpression(
'#<span>Override</span>#',
$html,
'Head Tag is displaying latest write'
);
$this->assertDoesNotMatchRegularExpression(
'#<span>Do Not Display</span>#',
$html,
'Head Tag is correctly not displaying original write'
);
}
}