PluginProbe
CookieAdmin – Cookie Consent Banner / 1.2.6
CookieAdmin – Cookie Consent Banner v1.2.6
1.2.6 1.2.5 1.2.4 1.2.3 1.2.2 1.2.1 1.2.0 1.1.9 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 All 26 releases
← All changes | includes/enduser.php +137 -69 1.2.4 → 1.2.6 View file →
@@ -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