feat: third-party-scripts blockieren (server + client-guard)
Loest die YouTube/Vimeo-API-Luecke: viele dienste laden ueber <script> (z. B. youtube.com/iframe_api, www-widgetapi.js, analytics) statt iframes - oft per JavaScript nachgeladen, daher fuer den scanner unsichtbar. - Pro dienst aktivierbar ueber das (umbenannte) feld "Zugehoerige Skripte blockieren (z. B. YouTube-/Vimeo-API)" = das vorhandene loads_script-flag. Presets (YouTube, Vimeo, Maps) haben es bereits an. - Server-seitig: passende <script src> werden zu type="text/plain" (src -> data-cb-src) neutralisiert, laden also nicht. - Client-seitig: winziger guard ganz frueh im <head> patcht appendChild/insertBefore/replaceChild und neutralisiert dynamisch injizierte scripts VOR dem einfuegen -> kein request. Faengt damit auch die per JS nachgeladene iframe_api ab. - Einwilligung (per-dienst-consent, z. B. ueber den video-platzhalter) schaltet die scripts via cbActivateScripts frei und laedt sie nach. - Neuer shortcode [content_blocker_consent id="…"] als einwilligungs-button fuer reine skript-dienste ohne sichtbaren platzhalter. - guard-logik mit DOM-mock getestet (block + reinject), server-regex isoliert geprueft. i18n DE/EN ergaenzt (127 strings). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,23 +47,78 @@ class CB_Autodetect {
|
||||
}
|
||||
|
||||
public static function process( string $html, array $services ): string {
|
||||
if ( $html === '' || stripos( $html, '<iframe' ) === false ) {
|
||||
if ( $html === '' ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Locate iframe blocks only. Attribute parsing happens via DOMDocument below.
|
||||
$out = preg_replace_callback(
|
||||
'#<iframe\b[^>]*>.*?</iframe>#is',
|
||||
function ( array $m ) use ( $services ): string {
|
||||
return self::maybe_replace_iframe( $m[0], $services );
|
||||
},
|
||||
$html
|
||||
);
|
||||
// Pass 1 — iframes: replace matched embeds with a consent placeholder.
|
||||
if ( stripos( $html, '<iframe' ) !== false ) {
|
||||
$out = preg_replace_callback(
|
||||
'#<iframe\b[^>]*>.*?</iframe>#is',
|
||||
function ( array $m ) use ( $services ): string {
|
||||
return self::maybe_replace_iframe( $m[0], $services );
|
||||
},
|
||||
$html
|
||||
);
|
||||
// On a PCRE error (e.g. backtrack/recursion limit on a huge page),
|
||||
// preg_replace_callback returns null. Never blank the page.
|
||||
if ( $out !== null ) {
|
||||
$html = $out;
|
||||
}
|
||||
}
|
||||
|
||||
// On a PCRE error (e.g. backtrack/recursion limit on a huge page),
|
||||
// preg_replace_callback returns null. Never blank the page — fall back to
|
||||
// the original, unmodified HTML.
|
||||
return $out === null ? $html : $out;
|
||||
// Pass 2 — scripts: neutralise <script src> of services that block scripts
|
||||
// so they don't execute/fetch until consent (the JS guard re-injects them).
|
||||
$script_services = array_values( array_filter(
|
||||
$services,
|
||||
static fn( $s ) => ! empty( $s['loads_script'] )
|
||||
) );
|
||||
if ( $script_services && stripos( $html, '<script' ) !== false ) {
|
||||
$out = preg_replace_callback(
|
||||
'#<script\b[^>]*\bsrc\s*=\s*["\'][^"\']+["\'][^>]*>#i',
|
||||
function ( array $m ) use ( $script_services ): string {
|
||||
return self::maybe_block_script( $m[0], $script_services );
|
||||
},
|
||||
$html
|
||||
);
|
||||
if ( $out !== null ) {
|
||||
$html = $out;
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Neutralise a <script src="…"> opening tag if its src matches a script-
|
||||
* blocking service: rename src→data-cb-src, force type="text/plain", and tag
|
||||
* it with the service id. The early head guard re-injects it on consent.
|
||||
*/
|
||||
private static function maybe_block_script( string $tag, array $services ): string {
|
||||
if ( ! preg_match( '/\bsrc\s*=\s*["\']([^"\']+)["\']/i', $tag, $m ) ) {
|
||||
return $tag;
|
||||
}
|
||||
$src = str_replace( '&', '&', $m[1] );
|
||||
|
||||
foreach ( $services as $svc ) {
|
||||
$pattern = $svc['match_pattern'] ?? '';
|
||||
if ( $pattern === '' || ! str_contains( $src, $pattern ) ) {
|
||||
continue;
|
||||
}
|
||||
$id = (string) ( $svc['id'] ?? '' );
|
||||
// src → data-cb-src (first occurrence), drop any existing type, then
|
||||
// force type="text/plain" + the service id on the opening tag.
|
||||
$t = preg_replace( '#\bsrc(\s*=\s*)#i', 'data-cb-src$1', $tag, 1 );
|
||||
$t = preg_replace( '#\btype\s*=\s*("[^"]*"|\'[^\']*\'|\S+)#i', '', $t );
|
||||
$t = preg_replace(
|
||||
'#^<script\b#i',
|
||||
'<script type="text/plain" data-cb-id="' . esc_attr( $id ) . '"',
|
||||
$t,
|
||||
1
|
||||
);
|
||||
return $t ?? $tag;
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user