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 +351 -76 1.2.0 → 1.2.6 View file →
@@ -12,10 +12,10 @@
12 12 static $categorized_cookies = array();
13 13
14 14 static function enqueue_scripts(){
15 15 global $wpdb;
16 -
17 - $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
16 +
17 + $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
18 18 $policy = cookieadmin_load_policy();
19 19 $table_name = esc_sql($wpdb->prefix . 'cookieadmin_cookies');
20 20 //cookieadmin_r_print($view);
21 21 //cookieadmin_r_print($policy);
@@ -20,8 +20,10 @@
20 20 //cookieadmin_r_print($view);
21 21 //cookieadmin_r_print($policy);
22 22
23 23 if(!empty($policy) && !empty($view) && !cookieadmin_is_editor_mode()){
24 +
25 + add_filter('mepr_design_style_handle_prefixes', '\CookieAdmin\Enduser::memberpress_allow_style_prefix');
24 26
25 27 wp_enqueue_style('cookieadmin-style', COOKIEADMIN_PLUGIN_URL . 'assets/css/consent.css', [], COOKIEADMIN_VERSION);
26 28
27 29 $js_deps = [];
@@ -47,8 +49,9 @@
47 49
48 50 // Used for setting cookie
49 51 $policy[$view]['base_path'] = $base_path;
50 52
53 + // NOTE: Check the polylang string registration if changing these
51 54 $policy[$view]['lang']['show_less'] = __('Show less', 'cookieadmin');
52 55 $policy[$view]['lang']['duration'] = __('Duration', 'cookieadmin');
53 56 $policy[$view]['lang']['session'] = __('Session', 'cookieadmin');
54 57 $policy[$view]['lang']['days'] = __('Days', 'cookieadmin');
@@ -63,33 +66,217 @@
63 66 }
64 67
65 68 $policy[$view]['categorized_cookies'] = self::$categorized_cookies = $cookie_data;
66 69
70 + $policy[$view] = apply_filters('cookieadmin_before_localize', $policy[$view]);
71 +
67 72 wp_localize_script('cookieadmin_js', 'cookieadmin_policy', $policy[$view]);
68 73
69 74 }
70 75 }
71 76
72 - /* static function cookieadmin_block_cookie_init_php(){
77 + static function cookieadmin_block_cookie_init_php(){
73 78
74 - //New - To catch, remove and send cookies in WP enqueue
79 + if(headers_sent() || (is_admin() && !wp_doing_ajax()) || defined('COOKIEADMIN_SCANNER') || cookieadmin_is_editor_mode()){
80 + return;
81 + }
82 +
83 + $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
84 +
85 + if(empty($view)){
86 + return;
87 + }
88 +
75 89 $http_cookies = array();
76 - $headers = headers_list();
90 + $set_cookie_headers = array();
77 91
78 - foreach($headers as $header) {
79 -
80 - if (stripos(trim($header), 'Set-Cookie:') === 0) {
92 + foreach(headers_list() as $header) {
93 + if(stripos(trim($header), 'Set-Cookie:') === 0){
81 94 $header = trim(substr($header, strlen('Set-Cookie:')));
82 - $name = trim(explode('=', $header)[0]);
83 - $http_cookies[$name]['string'] = trim($header);
84 - setcookie($name, '', time() - 999999, '/');
95 + $set_cookie_headers[] = $header;
85 96 }
86 97 }
87 98
99 + if(empty($set_cookie_headers)){
100 + return;
101 + }
102 +
103 + $policy = cookieadmin_load_policy();
104 +
105 + if(empty($policy) || empty($policy[$view])){
106 + return;
107 + }
108 +
109 + $categories = self::get_cookie_categories();
110 + $consent = self::get_consent();
111 + $preload = self::get_preload_categories($policy, $view);
112 + $allowed_headers = array();
113 +
114 + foreach($set_cookie_headers as $set_cookie){
115 + $name = self::get_cookie_name_from_header($set_cookie);
116 +
117 + if(empty($name)){
118 + continue;
119 + }
120 +
121 + if(self::is_response_cookie_allowed($set_cookie, $categories, $consent, $preload)){
122 + $allowed_headers[] = $set_cookie;
123 + continue;
124 + }
125 +
126 + $http_cookies[$name]['string'] = trim($set_cookie);
127 + }
128 +
129 + header_remove('Set-Cookie');
130 +
131 + foreach($allowed_headers as $set_cookie){
132 + header('Set-Cookie: ' . $set_cookie, false);
133 + }
134 +
88 135 $http_cookies['cookieadmin_consent'] = ["string" => "cookieadmin_consent=CookieAdmin Cookie Initialization"];
89 136
90 137 self::$http_cookies = $http_cookies;
91 - } */
138 + }
139 +
140 + static function get_cookie_categories(){
141 + global $wpdb;
142 +
143 + $table_name = $wpdb->prefix . 'cookieadmin_cookies';
144 + if(!self::cookieadmin_table_exists($table_name)){
145 + return array();
146 + }
147 +
148 + $categories = array(
149 + 'exact' => array(),
150 + 'prefix' => array(),
151 + );
152 + $rows = $wpdb->get_results("SELECT cookie_name, raw_name, category FROM {$table_name}");
153 +
154 + foreach($rows as $row){
155 + if(!empty($row->cookie_name) && !empty($row->category)){
156 + $category = strtolower($row->category);
157 + $categories['exact'][$row->cookie_name] = $category;
158 +
159 + if(!empty($row->raw_name)){
160 + $categories['exact'][$row->raw_name] = $category;
161 +
162 + if($row->raw_name !== $row->cookie_name && strpos($row->raw_name, $row->cookie_name) === 0){
163 + $categories['prefix'][$row->cookie_name] = $category;
164 + }
165 + }
166 + }
167 + }
168 +
169 + uksort($categories['prefix'], function($a, $b){
170 + return strlen($b) - strlen($a);
171 + });
172 +
173 + return $categories;
174 + }
175 +
176 + static function get_consent(){
177 + if(empty($_COOKIE['cookieadmin_consent'])){
178 + return array();
179 + }
180 +
181 + $consent = json_decode(wp_unslash($_COOKIE['cookieadmin_consent']), true);
182 + if(!is_array($consent)){
183 + return array();
184 + }
185 +
186 + $sanitized = array();
187 + foreach($consent as $key => $value){
188 + $sanitized[sanitize_key($key)] = sanitize_text_field($value);
189 + }
190 +
191 + return $sanitized;
192 + }
193 +
194 + static function get_preload_categories($policy, $view){
195 + if(empty($policy) || empty($policy[$view]['preload']) || !is_array($policy[$view]['preload'])){
196 + return array();
197 + }
198 +
199 + $preload = array();
200 + foreach($policy[$view]['preload'] as $category){
201 + $preload[] = strtolower(sanitize_key($category));
202 + }
203 +
204 + return $preload;
205 + }
206 +
207 + static function get_cookie_name_from_header($set_cookie){
208 + $parts = explode('=', $set_cookie, 2);
209 + $name = trim($parts[0]);
210 +
211 + if(empty($name)){
212 + return '';
213 + }
214 +
215 + return $name;
216 + }
217 +
218 + static function is_response_cookie_allowed($set_cookie, $categories, $consent, $preload = array()){
219 + $name = self::get_cookie_name_from_header($set_cookie);
220 +
221 + if(empty($name)){
222 + return false;
223 + }
224 +
225 + // A deletion never adds a cookie and must not be prevented.
226 + if(preg_match('/(?:^|;)\s*max-age\s*=\s*(-?\d+)/i', $set_cookie, $age) && (int) $age[1] <= 0){
227 + return true;
228 + }
229 +
230 + if(preg_match('/(?:^|;)\s*expires\s*=\s*([^;]+)/i', $set_cookie, $expires)){
231 + $expires_at = strtotime($expires[1]);
232 + if($expires_at !== false && $expires_at < time()){
233 + return true;
234 + }
235 + }
236 +
237 + if($name === 'cookieadmin_consent'){
238 + return true;
239 + }
240 +
241 + $category = self::get_cookie_category($name, $categories);
242 +
243 + if(empty($category)){
244 + return !empty($consent['accept']) && $consent['accept'] === 'true';
245 + }
246 +
247 + if($category === 'necessary' || in_array($category, $preload, true)){
248 + return true;
249 + }
250 +
251 + if(!empty($consent['reject']) && $consent['reject'] === 'true'){
252 + return false;
253 + }
254 +
255 + if(!empty($consent['accept']) && $consent['accept'] === 'true'){
256 + return true;
257 + }
258 +
259 + return !empty($consent[$category]) && $consent[$category] === 'true';
260 + }
261 +
262 + static function get_cookie_category($name, $categories){
263 + if(!empty($categories['exact'][$name])){
264 + return $categories['exact'][$name];
265 + }
266 +
267 + if(empty($categories['prefix'])){
268 + return '';
269 + }
270 +
271 + foreach($categories['prefix'] as $prefix => $category){
272 + if(strpos($name, $prefix) === 0){
273 + return $category;
274 + }
275 + }
276 +
277 + return '';
278 + }
92 279
93 280 static function block_scripts(){
94 281
95 282 if(wp_doing_ajax() || is_admin() || defined('REST_REQUEST') || defined('COOKIEADMIN_SCANNER') || cookieadmin_is_editor_mode()){
@@ -96,12 +283,11 @@
96 283 return;
97 284 }
98 285
99 286 $settings = get_option('cookieadmin_settings');
100 -
101 - // If block scripts is disabled, we don't need to make any changes
287 +
102 288 if(empty($settings) || empty($settings['block_scripts'])){
103 - return;
289 + return;
104 290 }
105 291
106 292 $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
107 293 $policy = cookieadmin_load_policy();
@@ -112,10 +298,20 @@
112 298 ob_start([__CLASS__, 'update_tracking_scripts']);
113 299 }
114 300
115 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 + }
116 309
117 - if(stripos($html, '<script') === false){
310 + private static function block_tracking_scripts($html){
311 + global $cookieadmin_settings;
312 +
313 + if(!is_string($html) || stripos($html, '<script') === false){
118 314 return $html;
119 315 }
120 316
121 317 if(empty(self::$categorized_cookies)){
@@ -121,88 +317,163 @@
121 317 if(empty(self::$categorized_cookies)){
122 318 return $html;
123 319 }
124 320
125 - $cookieadmin_consent = isset($_COOKIE['cookieadmin_consent'])
126 - ? json_decode(wp_unslash($_COOKIE['cookieadmin_consent']), true)
127 - : [];
321 + $cookieadmin_consent = self::get_consent();
128 322
129 - // Sanitizing cookies
130 - array_walk( $cookieadmin_consent, function( $value, $key ) use ( &$cookieadmin_consent ) {
131 - $sanitized_key = sanitize_key( $key );
132 - $cookieadmin_consent[ $sanitized_key ] = sanitize_text_field($value);
133 - } );
323 + $len = strlen($html);
324 + $cursor = 0;
325 + $output = '';
134 326
135 - $html = preg_replace_callback(
136 - '/<script\b([^>]*)>([\s\S]*?)<\/script>/i',
137 - function($match) use ($cookieadmin_consent){
138 - $attrs = $match[1];
139 - $content = $match[2];
140 - $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 + }
141 334
142 - if(preg_match('/\btype\s*=\s*["\']text\/plain["\']/i', $attrs)){
143 - 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;
144 347 }
348 + }
145 349
146 - if(preg_match('/\b(id|src)\s*=\s*["\'][^"\']*cookieadmin[^"\']*["\']/i', $attrs)){
147 - return $full_tag;
148 - }
350 + // Find end of opening tag '>' while respecting quotes
351 + $open_tag_end = false;
352 + $in_quote = false;
353 + $quote_char = '';
149 354
150 - if(preg_match('/\btype\s*=\s*["\']([^"\']+)["\']/i', $attrs, $type_match)){
151 - $type = strtolower(trim($type_match[1]));
152 - if($type !== 'text/javascript' && $type !== 'module'){
153 - 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;
154 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 + }
155 369 }
370 + }
156 371
157 - $src = '';
158 - if(preg_match('/\bsrc\s*=\s*["\']([^"\']*)["\']/i', $attrs, $src_match)){
159 - $src = $src_match[1];
160 - }
372 + if ($open_tag_end === false) {
373 + // Unclosed opening tag; append remainder and exit
374 + $output .= substr($html, $script_pos);
375 + break;
376 + }
161 377
162 - $match_against = !empty($src) ? $src : trim($attrs . ' ' . $content);
378 + $attrs = substr($html, $after_script, $open_tag_end - $after_script);
163 379
164 - if(empty($match_against)){
165 - return $full_tag;
166 - }
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 + }
167 387
168 - foreach (self::$categorized_cookies as $item) {
169 - $category = !empty($item->category) ? strtolower($item->category) : '';
170 - $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);
171 391
172 - if(empty($patterns) || empty($category)){
173 - continue;
174 - }
392 + $output .= self::process_script_tag($attrs, $content, $full_tag, $cookieadmin_consent, $cookieadmin_settings);
393 + $cursor = $close_tag_end;
394 + }
175 395
176 - foreach ($patterns as $pattern) {
177 - if(strpos($match_against, $pattern) !== false){
178 - if($category !== 'necessary' &&
179 - (empty($cookieadmin_consent) ||
180 - (!empty($cookieadmin_consent[$category]) && $cookieadmin_consent[$category] == 'false') ||
181 - (!empty($cookieadmin_consent['reject']) && $cookieadmin_consent['reject'] == 'true')
182 - )
183 - ){
184 - if($attrs === ''){
185 - return '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '">' . $content . '</script>';
186 - }
187 - return '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '"' . $attrs . '>' . $content . '</script>';
188 - }
189 - }
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>';
190 459 }
191 460 }
461 + }
462 + }
192 463
193 - return $full_tag;
194 - },
195 - $html
196 - );
197 -
198 - return $html;
464 + return $full_tag;
199 465 }
200 466
201 467 static function cookieadmin_show_banner(){
202 -
203 - $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
468 +
469 + $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
204 470 $policy = cookieadmin_load_policy();
471 +
472 + // Do not show banner if banner is off via Geo rule
473 + if(empty($view)){
474 + return;
475 + }
205 476
206 477 $raw_template = cookieadmin_load_consent_template($policy[$view], $view);
207 478
208 479 if(!is_array($raw_template) || empty($raw_template)){
@@ -225,6 +496,10 @@
225 496 $query = $wpdb->prepare("SHOW TABLES LIKE %s", $table_name);
226 497
227 498 return $wpdb->get_var($query) === $table_name;
228 499 }
500 +
501 + static function memberpress_allow_style_prefix($prefix_arr){
502 + $prefix_arr[] = 'cookieadmin';
503 + return $prefix_arr;
504 + }
229 505 }
230 -