diff --git a/debug_dom.php b/debug_dom.php
new file mode 100644
index 00000000..6adde54a
--- /dev/null
+++ b/debug_dom.php
@@ -0,0 +1,15 @@
+';
+$wrapped_buffer = '' . $buffer . '';
+
+$dom = new DOMDocument();
+$encoded_buffer = mb_encode_numericentity( $wrapped_buffer, array( 0x80, 0x10FFFF, 0, 0x1FFFFF ), 'UTF-8' );
+$dom->loadHTML( $encoded_buffer, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
+
+$dummy = $dom->getElementsByTagName('dummy')->item(0);
+
+echo "Child Nodes: " . $dummy->childNodes->length . "\n";
+foreach ($dummy->childNodes as $node) {
+ echo "Node: " . $node->nodeName . "\n";
+ echo "Content: " . $dom->saveHTML($node) . "\n";
+}
diff --git a/src/lib/helper.php b/src/lib/helper.php
index 64501cfb..176b4a41 100644
--- a/src/lib/helper.php
+++ b/src/lib/helper.php
@@ -6,6 +6,8 @@
use Exception;
use InvalidArgumentException;
use RuntimeException;
+ use DOMDocument;
+ use DOMElement;
/**
* @param string $type
@@ -99,96 +101,79 @@ function cookiebot_addons_remove_class_action( $action, $class, $method, $priori
* @since 1.2.0
*/
function cookiebot_addons_manipulate_script( $buffer, $keywords ) {
- /**
- * normalize potential self-closing script tags
- */
+ if ( empty( $buffer ) ) {
+ return $buffer;
+ }
+
+ // Use DOMDocument to safely parse and modify the script tag
+ $dom = new DOMDocument();
+
+ // Suppress errors for partial HTML
+ $libxml_previous_state = libxml_use_internal_errors( true );
+
+ // Wrap buffer in a custom tag to ensure correct parsing of fragments (e.g. multiple siblings at root)
+ // This prevents DOMDocument from trying to fix structure by nesting siblings
+ $wrapped_buffer = '' . $buffer . '';
- $normalized_buffer = preg_replace( '/()/is', '', $buffer );
+ // Load HTML with UTF-8 encoding hack
+ // The mb_convert_encoding is to ensure we don't have encoding issues
+ // We use LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD to avoid adding
wrappers automatically
+ // Replacement for deprecated mb_convert_encoding(..., 'HTML-ENTITIES', 'UTF-8')
+ $encoded_buffer = mb_encode_numericentity( $wrapped_buffer, array( 0x80, 0x10FFFF, 0, 0x1FFFFF ), 'UTF-8' );
+ $dom->loadHTML( $encoded_buffer, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
- if ( $normalized_buffer !== null ) {
- $buffer = $normalized_buffer;
+ libxml_use_internal_errors( $libxml_previous_state );
+
+ $scripts = $dom->getElementsByTagName( 'script' );
+ $modified = false;
+
+ // Convert DOMNodeList to array to avoid modification issues during iteration
+ $script_nodes = array();
+ foreach ( $scripts as $script ) {
+ $script_nodes[] = $script;
}
- /**
- * Pattern to get all scripts
- *
- * @version 2.0.4
- * @since 1.2.0
- */
- $pattern = '/(';
+ return '';
}
+ // Check if the script should be ignored
if ( $this->check_ignore_script( $src ) ) {
- return preg_replace_callback(
- '/)
+ $html = preg_replace('/', $html);
+ // Remove spaces around =
+ $html = preg_replace('/\s*=\s*/', '=', $html);
+
+ // Sort attributes to handle order differences
+ // This is a naive implementation but should work for simple cases
+ $html = preg_replace_callback('/';
+$new = cookiebot_addons_manipulate_script($input, $keywords);
+$old = cookiebot_addons_manipulate_script_old($input, $keywords);
+assert_equivalent($new, $old, 'Simple Script');
+
+// Case 2: Script with existing attributes
+$input = '';
+$new = cookiebot_addons_manipulate_script($input, $keywords);
+$old = cookiebot_addons_manipulate_script_old($input, $keywords);
+assert_equivalent($new, $old, 'Attributes');
+
+// Case 3: Script with existing type
+$input = '';
+$new = cookiebot_addons_manipulate_script($input, $keywords);
+$old = cookiebot_addons_manipulate_script_old($input, $keywords);
+assert_equivalent($new, $old, 'Existing Type');
+
+// Case 4: Multiple scripts in buffer
+$input = '';
+$new = cookiebot_addons_manipulate_script($input, $keywords);
+$old = cookiebot_addons_manipulate_script_old($input, $keywords);
+assert_equivalent($new, $old, 'Multiple Scripts');
+
+// Case 5: Fragment (no html/body)
+$input = '';
+$new = cookiebot_addons_manipulate_script($input, $keywords);
+$old = cookiebot_addons_manipulate_script_old($input, $keywords);
+assert_equivalent($new, $old, 'Fragment');
+
+// --- Test Suite 2: Script_Loader_Tag ---
+
+echo "\nTesting Script_Loader_Tag...\n";
+
+$loader_new = new Script_Loader_Tag();
+$loader_new->ignore_script('ignore-me.js');
+
+$loader_old = new Script_Loader_Tag_Old();
+$loader_old->ignore_script('ignore-me.js');
+
+// Case 6: Ignored Script
+$tag = '';
+$handle = 'ignore-me';
+$src = 'https://example.com/ignore-me.js';
+
+$new = $loader_new->cookiebot_add_consent_attribute_to_tag($tag, $handle, $src);
+$old = $loader_old->cookiebot_add_consent_attribute_to_tag($tag, $handle, $src);
+assert_equivalent($new, $old, 'Ignored Script');
+
+// Case 7: Non-ignored script
+$tag = '';
+$handle = 'normal';
+$src = 'https://example.com/normal.js';
+
+$new = $loader_new->cookiebot_add_consent_attribute_to_tag($tag, $handle, $src);
+$old = $loader_old->cookiebot_add_consent_attribute_to_tag($tag, $handle, $src);
+assert_equivalent($new, $old, 'Non-ignored script');
+
+echo "\nSummary: $passes Passed, $fails Failed.\n";
+if ($fails > 0) {
+ exit(1);
+}
diff --git a/tests/mock_wp_functions.php b/tests/mock_wp_functions.php
new file mode 100644
index 00000000..983a8770
--- /dev/null
+++ b/tests/mock_wp_functions.php
@@ -0,0 +1,59 @@
+ []]; }
+}
+if (!function_exists('is_multisite')) {
+ function is_multisite() { return false; }
+}
+if (!function_exists('get_site_url')) {
+ function get_site_url() { return 'http://example.com'; }
+}
+if (!function_exists('get_site_option')) {
+ function get_site_option($option, $default = false) { return $default; }
+}
+if (!function_exists('get_locale')) {
+ function get_locale() { return 'en_US'; }
+}
+if (!function_exists('plugin_dir_url')) {
+ function plugin_dir_url($file) { return 'http://example.com/wp-content/plugins/cookiebot/'; }
+}
+if (!function_exists('plugin_dir_path')) {
+ function plugin_dir_path($file) { return '/path/to/plugin/'; }
+}
+
+// Constants
+if (!defined('ABSPATH')) define('ABSPATH', '/tmp/');
+if (!defined('CYBOT_COOKIEBOT_PLUGIN_DIR')) define('CYBOT_COOKIEBOT_PLUGIN_DIR', __DIR__ . '/../');
+if (!defined('CYBOT_COOKIEBOT_PLUGIN_ASSETS_DIR')) define('CYBOT_COOKIEBOT_PLUGIN_ASSETS_DIR', 'assets/');
diff --git a/tests/old_logic.php b/tests/old_logic.php
new file mode 100644
index 00000000..d1b397c5
--- /dev/null
+++ b/tests/old_logic.php
@@ -0,0 +1,101 @@
+)/is', '', $buffer );
+ if ( $normalized_buffer !== null ) {
+ $buffer = $normalized_buffer;
+ }
+ $pattern = '/(';
+ }
+
+ if ( $this->check_ignore_script( $src ) ) {
+ return preg_replace_callback(
+ '/