diff --git a/src/View/Requirements.php b/src/View/Requirements.php index 262a264a6..159bc97d8 100644 --- a/src/View/Requirements.php +++ b/src/View/Requirements.php @@ -194,10 +194,13 @@ class Requirements implements Flushable * @param string $file The CSS file to load, relative to site root * @param string $media Comma-separated list of media types to use in the link tag * (e.g. 'screen,projector') + * @param array $options List of options. Available options include: + * - 'integrity' : SubResource Integrity hash + * - 'crossorigin' : Cross-origin policy for the resource */ - public static function css($file, $media = null) + public static function css($file, $media = null, $options = []) { - self::backend()->css($file, $media); + self::backend()->css($file, $media, $options); } /** diff --git a/src/View/Requirements_Backend.php b/src/View/Requirements_Backend.php index 795379ff1..1fb42251d 100644 --- a/src/View/Requirements_Backend.php +++ b/src/View/Requirements_Backend.php @@ -400,6 +400,8 @@ class Requirements_Backend * - 'async' : Boolean value to set async attribute to script tag * - 'defer' : Boolean value to set defer attribute to script tag * - 'type' : Override script type= value. + * - 'integrity' : SubResource Integrity hash + * - 'crossorigin' : Cross-origin policy for the resource */ public function javascript($file, $options = array()) { @@ -431,10 +433,15 @@ class Requirements_Backend && $this->javascript[$file]['defer'] == true ) ); + $integrity = $options['integrity'] ?? null; + $crossorigin = $options['crossorigin'] ?? null; + $this->javascript[$file] = array( 'async' => $async, 'defer' => $defer, 'type' => $type, + 'integrity' => $integrity, + 'crossorigin' => $crossorigin, ); // Record scripts included in this file @@ -631,13 +638,21 @@ class Requirements_Backend * @param string $file The CSS file to load, relative to site root * @param string $media Comma-separated list of media types to use in the link tag * (e.g. 'screen,projector') + * @param array $options List of options. Available options include: + * - 'integrity' : SubResource Integrity hash + * - 'crossorigin' : Cross-origin policy for the resource */ - public function css($file, $media = null) + public function css($file, $media = null, $options = []) { $file = ModuleResourceLoader::singleton()->resolvePath($file); + $integrity = $options['integrity'] ?? null; + $crossorigin = $options['crossorigin'] ?? null; + $this->css[$file] = [ - "media" => $media + "media" => $media, + "integrity" => $integrity, + "crossorigin" => $crossorigin, ]; } @@ -814,6 +829,12 @@ class Requirements_Backend if (!empty($attributes['defer'])) { $htmlAttributes['defer'] = 'defer'; } + if (!empty($attributes['integrity'])) { + $htmlAttributes['integrity'] = $attributes['integrity']; + } + if (!empty($attributes['crossorigin'])) { + $htmlAttributes['crossorigin'] = $attributes['crossorigin']; + } $jsRequirements .= HTML::createTag('script', $htmlAttributes); $jsRequirements .= "\n"; } @@ -838,6 +859,12 @@ class Requirements_Backend if (!empty($params['media'])) { $htmlAttributes['media'] = $params['media']; } + if (!empty($params['integrity'])) { + $htmlAttributes['integrity'] = $params['integrity']; + } + if (!empty($params['crossorigin'])) { + $htmlAttributes['crossorigin'] = $params['crossorigin']; + } $requirements .= HTML::createTag('link', $htmlAttributes); $requirements .= "\n"; } @@ -1136,7 +1163,7 @@ class Requirements_Backend } switch ($type) { case 'css': - $this->css($path, (isset($options['media']) ? $options['media'] : null)); + $this->css($path, (isset($options['media']) ? $options['media'] : null), $options); break; case 'js': $this->javascript($path, $options); @@ -1283,7 +1310,11 @@ class Requirements_Backend if (!in_array($css, $fileList)) { $newCSS[$css] = $spec; } elseif (!$included && $combinedURL) { - $newCSS[$combinedURL] = array('media' => (isset($options['media']) ? $options['media'] : null)); + $newCSS[$combinedURL] = array( + 'media' => $options['media'] ?? null, + 'integrity' => $options['integrity'] ?? null, + 'crossorigin' => $options['crossorigin'] ?? null, + ); $included = true; } // If already included, or otherwise blocked, then don't add into CSS diff --git a/tests/php/View/RequirementsTest.php b/tests/php/View/RequirementsTest.php index f2ccd5902..cb26e57f2 100644 --- a/tests/php/View/RequirementsTest.php +++ b/tests/php/View/RequirementsTest.php @@ -1183,4 +1183,29 @@ EOS $this->assertArrayHasKey('i18n/en-us.js', $actual); $this->assertArrayHasKey('i18n/fr.js', $actual); } + + public function testSriAttributes() + { + /** @var Requirements_Backend $backend */ + $backend = Injector::inst()->create(Requirements_Backend::class); + $this->setupRequirements($backend); + + $backend->javascript('javascript/RequirementsTest_a.js', ['integrity' => 'abc', 'crossorigin' => 'use-credentials']); + $backend->css('css/RequirementsTest_a.css', null, ['integrity' => 'def', 'crossorigin' => 'anonymous']); + $html = $backend->includeInHTML(self::$html_template); + + /* Javascript has correct attributes */ + $this->assertRegExp( + '#