Skip to content

Commit

Permalink
[FEATURE] Introduce additional attributes for all page.include** TS c…
Browse files Browse the repository at this point in the history
…onfigurations

Introduce tag attributes for the following TypoScript configurations:

* page.includeCSS
* page.includeCSSLibs
* page.includeJS
* page.includeJSFooter
* page.includeJSLibs
* page.includeJSFooterlibs

The syntax can then be

* page.includeCSS.my-attribute = my-value

Resolves: #91499
Releases: main
Change-Id: Icadaa53d379eb5417ea995dff7b5a2b8ade4ec9b
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65008
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Simon Schaufelberger <simonschaufi+typo3@gmail.com>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Simon Schaufelberger <simonschaufi+typo3@gmail.com>
Reviewed-by: Benni Mack <benni@typo3.org>
  • Loading branch information
Jan Stockfisch authored and bmack committed Oct 7, 2022
1 parent be7b2f2 commit 365aa64
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 37 deletions.
115 changes: 84 additions & 31 deletions typo3/sysext/core/Classes/Page/PageRenderer.php
Expand Up @@ -1074,8 +1074,9 @@ public function addFooterData($data)
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
* @param bool $nomodule Flag if property 'nomodule="nomodule"' should be added to JavaScript tags
* @param array<string, string> $tagAttributes Key => value list of tag attributes
*/
public function addJsLibrary($name, $file, $type = '', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false)
public function addJsLibrary($name, $file, $type = '', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false, array $tagAttributes = [])
{
if (!isset($this->jsLibs[strtolower($name)])) {
$this->jsLibs[strtolower($name)] = [
Expand All @@ -1092,6 +1093,7 @@ public function addJsLibrary($name, $file, $type = '', $compress = false, $force
'defer' => $defer,
'crossorigin' => $crossorigin,
'nomodule' => $nomodule,
'tagAttributes' => $tagAttributes,
];
}
}
Expand All @@ -1112,8 +1114,9 @@ public function addJsLibrary($name, $file, $type = '', $compress = false, $force
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
* @param bool $nomodule Flag if property 'nomodule="nomodule"' should be added to JavaScript tags
* @param array<string, string> $tagAttributes Key => value list of tag attributes
*/
public function addJsFooterLibrary($name, $file, $type = '', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false)
public function addJsFooterLibrary($name, $file, $type = '', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false, array $tagAttributes = [])
{
$name .= '_jsFooterLibrary';
if (!isset($this->jsLibs[strtolower($name)])) {
Expand All @@ -1131,6 +1134,7 @@ public function addJsFooterLibrary($name, $file, $type = '', $compress = false,
'defer' => $defer,
'crossorigin' => $crossorigin,
'nomodule' => $nomodule,
'tagAttributes' => $tagAttributes,
];
}
}
Expand All @@ -1150,8 +1154,9 @@ public function addJsFooterLibrary($name, $file, $type = '', $compress = false,
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
* @param bool $nomodule Flag if property 'nomodule="nomodule"' should be added to JavaScript tags
* @param array<string, string> $tagAttributes Key => value list of tag attributes
*/
public function addJsFile($file, $type = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false)
public function addJsFile($file, $type = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false, array $tagAttributes = [])
{
if (!isset($this->jsFiles[$file])) {
$this->jsFiles[$file] = [
Expand All @@ -1168,6 +1173,7 @@ public function addJsFile($file, $type = '', $compress = true, $forceOnTop = fal
'defer' => $defer,
'crossorigin' => $crossorigin,
'nomodule' => $nomodule,
'tagAttributes' => $tagAttributes,
];
}
}
Expand All @@ -1187,8 +1193,9 @@ public function addJsFile($file, $type = '', $compress = true, $forceOnTop = fal
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
* @param bool $nomodule Flag if property 'nomodule="nomodule"' should be added to JavaScript tags
* @param array<string, string> $tagAttributes Key => value list of tag attributes
*/
public function addJsFooterFile($file, $type = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false)
public function addJsFooterFile($file, $type = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '', $nomodule = false, array $tagAttributes = [])
{
if (!isset($this->jsFiles[$file])) {
$this->jsFiles[$file] = [
Expand All @@ -1205,6 +1212,7 @@ public function addJsFooterFile($file, $type = '', $compress = true, $forceOnTop
'defer' => $defer,
'crossorigin' => $crossorigin,
'nomodule' => $nomodule,
'tagAttributes' => $tagAttributes,
];
}
}
Expand Down Expand Up @@ -1262,8 +1270,9 @@ public function addJsFooterInlineCode($name, $block, $compress = true, $forceOnT
* @param bool $excludeFromConcatenation
* @param string $splitChar The char used to split the allWrap value, default is "|"
* @param bool $inline
* @param array<string, string> $tagAttributes Key => value list of tag attributes
*/
public function addCssFile($file, $rel = 'stylesheet', $media = 'all', $title = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $inline = false)
public function addCssFile($file, $rel = 'stylesheet', $media = 'all', $title = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $inline = false, array $tagAttributes = [])
{
if (!isset($this->cssFiles[$file])) {
$this->cssFiles[$file] = [
Expand All @@ -1277,6 +1286,7 @@ public function addCssFile($file, $rel = 'stylesheet', $media = 'all', $title =
'excludeFromConcatenation' => $excludeFromConcatenation,
'splitChar' => $splitChar,
'inline' => $inline,
'tagAttributes' => $tagAttributes,
];
}
}
Expand All @@ -1294,8 +1304,9 @@ public function addCssFile($file, $rel = 'stylesheet', $media = 'all', $title =
* @param bool $excludeFromConcatenation
* @param string $splitChar The char used to split the allWrap value, default is "|"
* @param bool $inline
* @param array<string, string> $tagAttributes Key => value list of tag attributes
*/
public function addCssLibrary($file, $rel = 'stylesheet', $media = 'all', $title = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $inline = false)
public function addCssLibrary($file, $rel = 'stylesheet', $media = 'all', $title = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $inline = false, array $tagAttributes = [])
{
if (!isset($this->cssLibs[$file])) {
$this->cssLibs[$file] = [
Expand All @@ -1309,6 +1320,7 @@ public function addCssLibrary($file, $rel = 'stylesheet', $media = 'all', $title
'excludeFromConcatenation' => $excludeFromConcatenation,
'splitChar' => $splitChar,
'inline' => $inline,
'tagAttributes' => $tagAttributes,
];
}
}
Expand Down Expand Up @@ -2205,11 +2217,19 @@ private function createCssTag(array $properties, string $file): string
if ($includeInline && @is_file($file)) {
$tag = $this->createInlineCssTagFromFile($file, $properties);
} else {
$tag = '<link rel="' . htmlspecialchars($properties['rel'] ?? '')
. '" href="' . htmlspecialchars($file)
. '" media="' . htmlspecialchars($properties['media'] ?? '') . '"'
. (($properties['title'] ?? false) ? ' title="' . htmlspecialchars($properties['title']) . '"' : '')
. $this->endingSlash . '>';
$tagAttributes = [];
if ($properties['rel'] ?? false) {
$tagAttributes['rel'] = $properties['rel'];
}
$tagAttributes['href'] = $file;
if ($properties['media'] ?? false) {
$tagAttributes['media'] = $properties['media'];
}
if ($properties['title'] ?? false) {
$tagAttributes['title'] = $properties['title'];
}
$tagAttributes = array_merge($tagAttributes, $properties['tagAttributes'] ?? []);
$tag = '<link ' . GeneralUtility::implodeAttributes($tagAttributes, true, true) . $this->endingSlash . '>';
}
if ($properties['allWrap'] ?? false) {
$wrapArr = explode(($properties['splitChar'] ?? false) ?: '|', $properties['allWrap'], 2);
Expand Down Expand Up @@ -2253,14 +2273,28 @@ protected function renderAdditionalJavaScriptLibraries()
$jsFooterLibs = '';
if (!empty($this->jsLibs)) {
foreach ($this->jsLibs as $properties) {
$properties['file'] = $this->getStreamlinedFileName($properties['file'] ?? '');
$type = ($properties['type'] ?? false) ? ' type="' . htmlspecialchars($properties['type']) . '"' : '';
$async = ($properties['async'] ?? false) ? ' async="async"' : '';
$defer = ($properties['defer'] ?? false) ? ' defer="defer"' : '';
$nomodule = ($properties['nomodule'] ?? false) ? ' nomodule="nomodule"' : '';
$integrity = ($properties['integrity'] ?? false) ? ' integrity="' . htmlspecialchars($properties['integrity']) . '"' : '';
$crossorigin = ($properties['crossorigin'] ?? false) ? ' crossorigin="' . htmlspecialchars($properties['crossorigin']) . '"' : '';
$tag = '<script src="' . htmlspecialchars($properties['file']) . '"' . $type . $async . $defer . $integrity . $crossorigin . $nomodule . '></script>';
$tagAttributes = [];
$tagAttributes['src'] = $this->getStreamlinedFileName($properties['file'] ?? '');
if ($properties['type'] ?? false) {
$tagAttributes['type'] = $properties['type'];
}
if ($properties['async'] ?? false) {
$tagAttributes['async'] = 'async';
}
if ($properties['defer'] ?? false) {
$tagAttributes['defer'] = 'defer';
}
if ($properties['nomodule'] ?? false) {
$tagAttributes['nomodule'] = 'nomodule';
}
if ($properties['integrity'] ?? false) {
$tagAttributes['integrity'] = $properties['integrity'];
}
if ($properties['crossorigin'] ?? false) {
$tagAttributes['crossorigin'] = $properties['crossorigin'];
}
$tagAttributes = array_merge($tagAttributes, $properties['tagAttributes'] ?? []);
$tag = '<script ' . GeneralUtility::implodeAttributes($tagAttributes, true, true) . '></script>';
if ($properties['allWrap'] ?? false) {
$wrapArr = explode(($properties['splitChar'] ?? false) ?: '|', $properties['allWrap'], 2);
$tag = $wrapArr[0] . $tag . $wrapArr[1];
Expand Down Expand Up @@ -2297,14 +2331,28 @@ protected function renderJavaScriptFiles()
$jsFooterFiles = '';
if (!empty($this->jsFiles)) {
foreach ($this->jsFiles as $file => $properties) {
$file = $this->getStreamlinedFileName($file);
$type = ($properties['type'] ?? false) ? ' type="' . htmlspecialchars($properties['type']) . '"' : '';
$async = ($properties['async'] ?? false) ? ' async="async"' : '';
$defer = ($properties['defer'] ?? false) ? ' defer="defer"' : '';
$nomodule = ($properties['nomodule'] ?? false) ? ' nomodule="nomodule"' : '';
$integrity = ($properties['integrity'] ?? false) ? ' integrity="' . htmlspecialchars($properties['integrity']) . '"' : '';
$crossorigin = ($properties['crossorigin'] ?? false) ? ' crossorigin="' . htmlspecialchars($properties['crossorigin']) . '"' : '';
$tag = '<script src="' . htmlspecialchars($file) . '"' . $type . $async . $defer . $integrity . $crossorigin . $nomodule . '></script>';
$tagAttributes = [];
$tagAttributes['src'] = $this->getStreamlinedFileName($file);
if ($properties['type'] ?? false) {
$tagAttributes['type'] = $properties['type'];
}
if ($properties['async'] ?? false) {
$tagAttributes['async'] = 'async';
}
if ($properties['defer'] ?? false) {
$tagAttributes['defer'] = 'defer';
}
if ($properties['nomodule'] ?? false) {
$tagAttributes['nomodule'] = 'nomodule';
}
if ($properties['integrity'] ?? false) {
$tagAttributes['integrity'] = $properties['integrity'];
}
if ($properties['crossorigin'] ?? false) {
$tagAttributes['crossorigin'] = $properties['crossorigin'];
}
$tagAttributes = array_merge($tagAttributes, $properties['tagAttributes'] ?? []);
$tag = '<script ' . GeneralUtility::implodeAttributes($tagAttributes, true, true) . '></script>';
if ($properties['allWrap'] ?? false) {
$wrapArr = explode(($properties['splitChar'] ?? false) ?: '|', $properties['allWrap'], 2);
$tag = $wrapArr[0] . $tag . $wrapArr[1];
Expand Down Expand Up @@ -2772,10 +2820,15 @@ protected function createInlineCssTagFromFile(string $file, array $properties):
return '';
}
$cssInlineFix = $this->relativeCssPathFixer->fixRelativeUrlPaths($cssInline, '/' . PathUtility::dirname($file) . '/');
return '<style'
. ' media="' . htmlspecialchars($properties['media'] ?? '') . '"'
. (($properties['title'] ?? false) ? ' title="' . htmlspecialchars($properties['title']) . '"' : '')
. '>' . LF
$tagAttributes = [];
if ($properties['media'] ?? false) {
$tagAttributes['media'] = $properties['media'];
}
if ($properties['title'] ?? false) {
$tagAttributes['title'] = $properties['title'];
}
$tagAttributes = array_merge($tagAttributes, $properties['tagAttributes'] ?? []);
return '<style ' . GeneralUtility::implodeAttributes($tagAttributes, true, true) . '>' . LF
. '/*<![CDATA[*/' . LF . '<!-- ' . LF
. $cssInlineFix
. '-->' . LF . '/*]]>*/' . LF . '</style>' . LF;
Expand Down
@@ -0,0 +1,91 @@
.. include:: /Includes.rst.txt

==============================================================================================
Feature: #91499 - Additional attributes for includeJS, includeCSS and all other page.include**
==============================================================================================

See :issue:`91499`

Description
===========

PageRenderer supports additional tag attributes for CSS and JavaScript files.
These data attributes can be configured using a key-value list via TypoScript.

* :ts:`page.includeCSS`
* :ts:`page.includeCSSLibs`
* :ts:`page.includeJS`
* :ts:`page.includeJSFooter`
* :ts:`page.includeJSLibs`
* :ts:`page.includeJSFooterlibs`


Impact
======

It is now possible to extend script and link tags with any kind of
HTML tag attributes, which is very useful for integration with
external scripts such as Consent manager.

Example
^^^^^^^

Configuration:

.. code-block:: ts
page = PAGE
page {
includeCSSLibs {
someIncludeFile = fileadmin/someIncludeFile1
someIncludeFile.data-foo = includeCSSLibs
}
includeCSS {
someIncludeFile = fileadmin/someIncludeFile2
someIncludeFile.data-foo = includeCSS
}
includeJSLibs {
someIncludeFile = fileadmin/someIncludeFile3
someIncludeFile.data-consent-type = marketing
}
includeJS {
someIncludeFile = fileadmin/someIncludeFile4
someIncludeFile.data-consent-type = essential
}
includeJSFooterlibs {
someIncludeFile = fileadmin/someIncludeFile5
someIncludeFile.data-my-attribute = foo
}
includeJSFooter {
someIncludeFile = fileadmin/someIncludeFile6
someIncludeFile.data-foo = includeJSFooter
}
}
Reserved keywords which will not be mapped to attributes are:
- compress
- forceOnTop
- allWrap
- type (set automatically, depending on config.doctype)
- disableCompression
- excludeFromConcatenation
- external
- inline

Resulting HTML:

.. code-block:: html

<head>
<link rel="stylesheet" type="text/css" href="/typo3conf/ext/myext/Resources/Public/someIncludeFile1" media="all" data-foo="includeCSS">
<link rel="stylesheet" type="text/css" href="/typo3conf/ext/myext/Resources/Public/someIncludeFile2" media="all" data-foo="includeCSSLibs">

<script src="/typo3conf/ext/myext/Resources/Public/someIncludeFile3" data-consent-type="marketing"></script>
<script src="/typo3conf/ext/myext/Resources/Public/someIncludeFile4" data-consent-type="essential"></script>
</head>
<body>
<script src="/typo3conf/ext/myext/Resources/Public/someIncludeFile5" data-my-attribute="foo"></script>
<script src="/typo3conf/ext/myext/Resources/Public/someIncludeFile6" data-foo="includeJSFooteribs"></script>
</body>

.. index:: Frontend, TypoScript, ext:frontend

0 comments on commit 365aa64

Please sign in to comment.