| @@ -298,11 +298,20 @@ | ||
| 298 | 298 | ob_start([__CLASS__, 'update_tracking_scripts']); |
| 299 | 299 | } |
| 300 | 300 | |
| 301 | 301 | static function update_tracking_scripts($html){ |
| 302 | + // Never let a blocking failure blank the page, serve it unmodified instead | |
| 303 | + try{ | |
| 304 | + return self::block_tracking_scripts($html); | |
| 305 | + }catch(\Throwable $e){ | |
| 306 | + return $html; | |
| 307 | + } | |
| 308 | + } | |
| 309 | + | |
| 310 | + private static function block_tracking_scripts($html){ | |
| 302 | 311 | global $cookieadmin_settings; |
| 303 | 312 | |
| 304 | - if(stripos($html, '<script') === false){ | |
| 313 | + if(!is_string($html) || stripos($html, '<script') === false){ | |
| 305 | 314 | return $html; |
| 306 | 315 | } |
| 307 | 316 | |
| 308 | 317 | if(empty(self::$categorized_cookies)){ |
| @@ -308,93 +317,152 @@ | ||
| 308 | 317 | if(empty(self::$categorized_cookies)){ |
| 309 | 318 | return $html; |
| 310 | 319 | } |
| 311 | 320 | |
| 312 | - $cookieadmin_consent = isset($_COOKIE['cookieadmin_consent']) | |
| 313 | - ? json_decode(wp_unslash($_COOKIE['cookieadmin_consent']), true) | |
| 314 | - : []; | |
| 321 | + $cookieadmin_consent = self::get_consent(); | |
| 315 | 322 | |
| 316 | - // Sanitizing cookies | |
| 317 | - array_walk( $cookieadmin_consent, function( $value, $key ) use ( &$cookieadmin_consent ) { | |
| 318 | - $sanitized_key = sanitize_key( $key ); | |
| 319 | - $cookieadmin_consent[ $sanitized_key ] = sanitize_text_field($value); | |
| 320 | - } ); | |
| 323 | + $len = strlen($html); | |
| 324 | + $cursor = 0; | |
| 325 | + $output = ''; | |
| 321 | 326 | |
| 322 | - $html = preg_replace_callback( | |
| 323 | - '/<script\b([^>]*)>([\s\S]*?)<\/script>/i', | |
| 324 | - function($match) use ($cookieadmin_consent, $cookieadmin_settings){ | |
| 325 | - $attrs = $match[1]; | |
| 326 | - $content = $match[2]; | |
| 327 | - $full_tag = $match[0]; | |
| 327 | + while ($cursor < $len) { | |
| 328 | + // Find next <script tag | |
| 329 | + $script_pos = stripos($html, '<script', $cursor); | |
| 330 | + if ($script_pos === false) { | |
| 331 | + $output .= substr($html, $cursor); | |
| 332 | + break; | |
| 333 | + } | |
| 328 | 334 | |
| 329 | - if(preg_match('/\btype\s*=\s*["\']text\/plain["\']/i', $attrs)){ | |
| 330 | - return $full_tag; | |
| 335 | + // Append everything prior to this <script tag | |
| 336 | + $output .= substr($html, $cursor, $script_pos - $cursor); | |
| 337 | + | |
| 338 | + // Check word boundary after '<script' (must be followed by whitespace, '>', or '/') | |
| 339 | + $after_script = $script_pos + 7; // strlen('<script') | |
| 340 | + if ($after_script < $len) { | |
| 341 | + $next_char = $html[$after_script]; | |
| 342 | + if ($next_char !== ' ' && $next_char !== "\t" && $next_char !== "\n" && $next_char !== "\r" && $next_char !== '>' && $next_char !== '/') { | |
| 343 | + // e.g. <scripture> or <scripts> | |
| 344 | + $output .= substr($html, $script_pos, 7); | |
| 345 | + $cursor = $after_script; | |
| 346 | + continue; | |
| 331 | 347 | } |
| 348 | + } | |
| 332 | 349 | |
| 333 | - if(preg_match('/\b(id|src)\s*=\s*["\'][^"\']*cookieadmin[^"\']*["\']/i', $attrs)){ | |
| 334 | - return $full_tag; | |
| 335 | - } | |
| 350 | + // Find end of opening tag '>' while respecting quotes | |
| 351 | + $open_tag_end = false; | |
| 352 | + $in_quote = false; | |
| 353 | + $quote_char = ''; | |
| 336 | 354 | |
| 337 | - if(preg_match('/\btype\s*=\s*["\']([^"\']+)["\']/i', $attrs, $type_match)){ | |
| 338 | - $type = strtolower(trim($type_match[1])); | |
| 339 | - if($type !== 'text/javascript' && $type !== 'module'){ | |
| 340 | - return $full_tag; | |
| 355 | + for ($i = $after_script; $i < $len; $i++) { | |
| 356 | + $char = $html[$i]; | |
| 357 | + if ($in_quote) { | |
| 358 | + if ($char === $quote_char) { | |
| 359 | + $in_quote = false; | |
| 341 | 360 | } |
| 361 | + } else { | |
| 362 | + if ($char === '"' || $char === "'") { | |
| 363 | + $in_quote = true; | |
| 364 | + $quote_char = $char; | |
| 365 | + } elseif ($char === '>') { | |
| 366 | + $open_tag_end = $i; | |
| 367 | + break; | |
| 368 | + } | |
| 342 | 369 | } |
| 370 | + } | |
| 343 | 371 | |
| 344 | - $src = ''; | |
| 345 | - if(preg_match('/\bsrc\s*=\s*["\']([^"\']*)["\']/i', $attrs, $src_match)){ | |
| 346 | - $src = $src_match[1]; | |
| 347 | - } | |
| 372 | + if ($open_tag_end === false) { | |
| 373 | + // Unclosed opening tag; append remainder and exit | |
| 374 | + $output .= substr($html, $script_pos); | |
| 375 | + break; | |
| 376 | + } | |
| 348 | 377 | |
| 349 | - $match_against = !empty($src) ? $src : trim($attrs . ' ' . $content); | |
| 378 | + $attrs = substr($html, $after_script, $open_tag_end - $after_script); | |
| 350 | 379 | |
| 351 | - if(empty($match_against)){ | |
| 352 | - return $full_tag; | |
| 353 | - } | |
| 354 | - | |
| 355 | - if(!empty($cookieadmin_settings['cookieadmin_google_advance_consent_mode'])){ | |
| 356 | - // External Google tag loader | |
| 357 | - if(!empty($src) && stripos($src, 'googletagmanager.com/gtag/js') !== false){ | |
| 358 | - return $full_tag; | |
| 359 | - } | |
| 360 | - // Inline gtag() snippet | |
| 361 | - if(empty($src) && stripos($content, 'gtag(') !== false){ | |
| 362 | - return $full_tag; | |
| 363 | - } | |
| 364 | - } | |
| 380 | + // Find closing </script> | |
| 381 | + $close_tag_pos = stripos($html, '</script>', $open_tag_end + 1); | |
| 382 | + if ($close_tag_pos === false) { | |
| 383 | + // Unclosed script tag; append remainder and exit | |
| 384 | + $output .= substr($html, $script_pos); | |
| 385 | + break; | |
| 386 | + } | |
| 365 | 387 | |
| 366 | - foreach (self::$categorized_cookies as $item) { | |
| 367 | - $category = !empty($item->category) ? strtolower($item->category) : ''; | |
| 368 | - $patterns = !empty($item->patterns) ? json_decode($item->patterns, true) : ''; | |
| 388 | + $content = substr($html, $open_tag_end + 1, $close_tag_pos - ($open_tag_end + 1)); | |
| 389 | + $close_tag_end = $close_tag_pos + 9; // strlen('</script>') | |
| 390 | + $full_tag = substr($html, $script_pos, $close_tag_end - $script_pos); | |
| 369 | 391 | |
| 370 | - if(empty($patterns) || empty($category)){ | |
| 371 | - continue; | |
| 372 | - } | |
| 392 | + $output .= self::process_script_tag($attrs, $content, $full_tag, $cookieadmin_consent, $cookieadmin_settings); | |
| 393 | + $cursor = $close_tag_end; | |
| 394 | + } | |
| 373 | 395 | |
| 374 | - foreach ($patterns as $pattern) { | |
| 375 | - if(strpos($match_against, $pattern) !== false){ | |
| 376 | - if($category !== 'necessary' && | |
| 377 | - (empty($cookieadmin_consent) || | |
| 378 | - (!empty($cookieadmin_consent[$category]) && $cookieadmin_consent[$category] == 'false') || | |
| 379 | - (!empty($cookieadmin_consent['reject']) && $cookieadmin_consent['reject'] == 'true') | |
| 380 | - ) | |
| 381 | - ){ | |
| 382 | - if($attrs === ''){ | |
| 383 | - return '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '">' . $content . '</script>'; | |
| 384 | - } | |
| 385 | - return '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '"' . $attrs . '>' . $content . '</script>'; | |
| 386 | - } | |
| 387 | - } | |
| 396 | + return $output; | |
| 397 | + } | |
| 398 | + | |
| 399 | + private static function process_script_tag($attrs, $content, $full_tag, $cookieadmin_consent, $cookieadmin_settings){ | |
| 400 | + if(preg_match('/(?<![\w-])type\s*=\s*["\']text\/plain["\']/i', $attrs)){ | |
| 401 | + return $full_tag; | |
| 402 | + } | |
| 403 | + | |
| 404 | + if(preg_match('/(?<![\w-])(id|src)\s*=\s*["\'][^"\']*cookieadmin[^"\']*["\']/i', $attrs)){ | |
| 405 | + return $full_tag; | |
| 406 | + } | |
| 407 | + | |
| 408 | + if(preg_match('/(?<![\w-])type\s*=\s*["\']([^"\']+)["\']/i', $attrs, $type_match)){ | |
| 409 | + $type = strtolower(trim($type_match[1])); | |
| 410 | + if($type !== 'text/javascript' && $type !== 'module'){ | |
| 411 | + return $full_tag; | |
| 412 | + } | |
| 413 | + } | |
| 414 | + | |
| 415 | + $src = ''; | |
| 416 | + if(preg_match('/(?<![\w-])src\s*=\s*["\']([^"\']*)["\']/i', $attrs, $src_match)){ | |
| 417 | + $src = $src_match[1]; | |
| 418 | + } | |
| 419 | + | |
| 420 | + $match_against = !empty($src) ? $src : trim($attrs . ' ' . $content); | |
| 421 | + | |
| 422 | + if(empty($match_against)){ | |
| 423 | + return $full_tag; | |
| 424 | + } | |
| 425 | + | |
| 426 | + if(!empty($cookieadmin_settings['cookieadmin_google_advance_consent_mode'])){ | |
| 427 | + // External Google tag loader | |
| 428 | + if(!empty($src) && stripos($src, 'googletagmanager.com/gtag/js') !== false){ | |
| 429 | + return $full_tag; | |
| 430 | + } | |
| 431 | + // Inline gtag() snippet | |
| 432 | + if(empty($src) && stripos($content, 'gtag(') !== false){ | |
| 433 | + return $full_tag; | |
| 434 | + } | |
| 435 | + } | |
| 436 | + | |
| 437 | + foreach (self::$categorized_cookies as $item) { | |
| 438 | + $category = !empty($item->category) ? strtolower($item->category) : ''; | |
| 439 | + $patterns = !empty($item->patterns) ? json_decode($item->patterns, true) : ''; | |
| 440 | + | |
| 441 | + if(empty($patterns) || empty($category)){ | |
| 442 | + continue; | |
| 443 | + } | |
| 444 | + | |
| 445 | + foreach ($patterns as $pattern) { | |
| 446 | + if(strpos($match_against, $pattern) !== false){ | |
| 447 | + if($category !== 'necessary' && | |
| 448 | + (empty($cookieadmin_consent) || | |
| 449 | + (!empty($cookieadmin_consent[$category]) && $cookieadmin_consent[$category] == 'false') || | |
| 450 | + (!empty($cookieadmin_consent['reject']) && $cookieadmin_consent['reject'] == 'true') | |
| 451 | + ) | |
| 452 | + ){ | |
| 453 | + // Remove any existing type attribute to prevent duplicate type="..." attributes | |
| 454 | + $sanitized_attrs = preg_replace('/(?<![\w-])type\s*=\s*["\'][^"\']*["\']/i', '', $attrs); | |
| 455 | + $sanitized_attrs = trim($sanitized_attrs); | |
| 456 | + $attr_str = !empty($sanitized_attrs) ? ' ' . $sanitized_attrs : ''; | |
| 457 | + | |
| 458 | + return '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '"' . $attr_str . '>' . $content . '</script>'; | |
| 388 | 459 | } |
| 389 | 460 | } |
| 461 | + } | |
| 462 | + } | |
| 390 | 463 | |
| 391 | - return $full_tag; | |
| 392 | - }, | |
| 393 | - $html | |
| 394 | - ); | |
| 395 | - | |
| 396 | - return $html; | |
| 464 | + return $full_tag; | |
| 397 | 465 | } |
| 398 | 466 | |
| 399 | 467 | static function cookieadmin_show_banner(){ |
| 400 | 468 | |