Refactor script injection to type="text/plain" inert pattern

Custom scripts are now rendered as inert <script type="text/plain"
data-att-cc-category="..."> tags in the page HTML. On consent,
consent-manager.js scans the DOM and activates matching elements.
This replaces the JSON-in-config approach and allows third-party
plugins (e.g. HFCM) to output consent-gated scripts using the
same data attribute convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 09:28:11 +00:00
parent 256a4a9dec
commit 0a73181ea7
4 changed files with 131 additions and 22 deletions

View File

@@ -165,4 +165,60 @@ class ATT_Consent_Scripts_Manager {
return $grouped;
}
/**
* Render scripts as inert HTML for a given placement.
*
* Script tags are output with type="text/plain" so they don't execute
* until activated by consent-manager.js. Non-script HTML (pixels, etc.)
* is wrapped in <template> tags.
*
* @param string $placement 'head' or 'footer'.
* @return string Combined HTML string.
*/
public static function render_scripts_as_html( $placement ) {
$scripts = self::get_scripts( null, $placement );
if ( empty( $scripts ) ) {
return '';
}
$output = '';
foreach ( $scripts as $script ) {
$snippet = trim( $script['snippet'] );
$category = esc_attr( $script['category'] );
if ( '' === $snippet ) {
continue;
}
// Check if snippet contains <script> tags.
if ( preg_match( '/<script[\s>]/i', $snippet ) ) {
// Rewrite each <script> tag to type="text/plain" with category attribute.
$snippet = preg_replace_callback(
'/<script(\s[^>]*)?>|<script>/i',
function ( $matches ) use ( $category ) {
$attrs = isset( $matches[1] ) ? $matches[1] : '';
// Strip any existing type attribute.
$attrs = preg_replace( '/\s*type\s*=\s*(["\'])[^"\']*\1/i', '', $attrs );
return '<script type="text/plain" data-att-cc-category="' . $category . '"' . $attrs . '>';
},
$snippet
);
$output .= $snippet . "\n";
} elseif ( preg_match( '/^\s*</', $snippet ) ) {
// Non-script HTML (img pixels, noscript, iframes, etc.).
$output .= '<template data-att-cc-category="' . $category . '" data-att-cc-type="html">' . $snippet . '</template>' . "\n";
} else {
// Raw JS code without <script> wrapper.
$output .= '<script type="text/plain" data-att-cc-category="' . $category . '">' . $snippet . '</script>' . "\n";
}
}
return $output;
}
}