Skip to content

Commit

Permalink
ENH Create Requirements::customScriptWithAttributes (#11076)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update src/View/Requirements_Backend.php

Co-authored-by: Steve Boyd <[email protected]>

* Update tests/php/View/RequirementsTest.php

Co-authored-by: Steve Boyd <[email protected]>

* FIX Removed extra closing brace in customScriptWithAttributes

* Update src/View/Requirements_Backend.php

Co-authored-by: Steve Boyd <[email protected]>

* Update src/View/Requirements.php

Co-authored-by: Guy Sartorelli <[email protected]>

* 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 <[email protected]>
Co-authored-by: Guy Sartorelli <[email protected]>
  • Loading branch information
3 people authored Dec 21, 2023
1 parent c003dfd commit 2487c40
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 2 deletions.
15 changes: 15 additions & 0 deletions src/View/Requirements.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ public static function customScript($script, $uniquenessID = null)
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
*
Expand Down
50 changes: 48 additions & 2 deletions src/View/Requirements_Backend.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -494,12 +502,43 @@ protected function getAllJavascript()
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
*
Expand Down Expand Up @@ -791,10 +830,17 @@ public function includeInHTML($content)
}

// 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";
Expand Down
81 changes: 81 additions & 0 deletions tests/php/View/RequirementsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,8 @@ public function testSriAttributes()
$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);

Expand All @@ -1413,11 +1415,90 @@ public function testSriAttributes()
'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"#',
$html,
'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'
);
}
}

0 comments on commit 2487c40

Please sign in to comment.