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 +406 -54 1.1.0 → 1.2.6 View file →
@@ -12,20 +12,30 @@
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);
22 22
23 - if(!empty($policy) && !empty($view)){
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 - wp_enqueue_script('cookieadmin_js', COOKIEADMIN_PLUGIN_URL . 'assets/js/consent.js', [], COOKIEADMIN_VERSION, 'async');
29 + $js_deps = [];
30 + // Free consent.js is the base script from where the functionality gets triggered
31 + // So we need to make sure the dependencies of free script gets loaded first
32 + // Like the pro/consent.js is a dependency of the free one.
33 + if(defined('COOKIEADMIN_PREMIUM')){
34 + $js_deps[] = 'cookieadmin_pro_js';
35 + }
36 +
37 + wp_enqueue_script('cookieadmin_js', COOKIEADMIN_PLUGIN_URL . 'assets/js/consent.js', $js_deps, COOKIEADMIN_VERSION);
28 38
29 39 $policy[$view]['ajax_url'] = admin_url('admin-ajax.php');
30 40 $policy[$view]['nonce'] = wp_create_nonce('cookieadmin_js_nonce');
31 41 $policy[$view]['http_cookies'] = self::$http_cookies;
@@ -39,12 +49,13 @@
39 49
40 50 // Used for setting cookie
41 51 $policy[$view]['base_path'] = $base_path;
42 52
43 - $policy[$view]['lang']['show_more'] = __('show more', 'cookieadmin');
44 - $policy[$view]['lang']['show_less'] = __('show less', 'cookieadmin');
53 + // NOTE: Check the polylang string registration if changing these
54 + $policy[$view]['lang']['show_less'] = __('Show less', 'cookieadmin');
45 55 $policy[$view]['lang']['duration'] = __('Duration', 'cookieadmin');
46 56 $policy[$view]['lang']['session'] = __('Session', 'cookieadmin');
57 + $policy[$view]['lang']['days'] = __('Days', 'cookieadmin');
47 58
48 59 // cookieadmin_r_print($policy);die();
49 60
50 61 $rows = $wpdb->get_results("SELECT cookie_name, category, expires, description, patterns FROM {$table_name}");
@@ -55,90 +66,427 @@
55 66 }
56 67
57 68 $policy[$view]['categorized_cookies'] = self::$categorized_cookies = $cookie_data;
58 69
70 + $policy[$view] = apply_filters('cookieadmin_before_localize', $policy[$view]);
71 +
59 72 wp_localize_script('cookieadmin_js', 'cookieadmin_policy', $policy[$view]);
60 73
61 74 }
62 75 }
63 76
64 - /* static function cookieadmin_block_cookie_init_php(){
77 + static function cookieadmin_block_cookie_init_php(){
65 78
66 - //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 +
67 89 $http_cookies = array();
68 - $headers = headers_list();
90 + $set_cookie_headers = array();
69 91
70 - foreach($headers as $header) {
71 -
72 - if (stripos(trim($header), 'Set-Cookie:') === 0) {
92 + foreach(headers_list() as $header) {
93 + if(stripos(trim($header), 'Set-Cookie:') === 0){
73 94 $header = trim(substr($header, strlen('Set-Cookie:')));
74 - $name = trim(explode('=', $header)[0]);
75 - $http_cookies[$name]['string'] = trim($header);
76 - setcookie($name, '', time() - 999999, '/');
95 + $set_cookie_headers[] = $header;
77 96 }
78 97 }
79 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 +
80 135 $http_cookies['cookieadmin_consent'] = ["string" => "cookieadmin_consent=CookieAdmin Cookie Initialization"];
81 136
82 137 self::$http_cookies = $http_cookies;
83 - } */
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 + }
84 279
85 - static function check_if_cookies_allowed($tag, $handle, $src){
280 + static function block_scripts(){
86 281
87 - $cookieadmin_consent = isset($_COOKIE['cookieadmin_consent'])
88 - ? json_decode(wp_unslash($_COOKIE['cookieadmin_consent']), true)
89 - : [];
282 + if(wp_doing_ajax() || is_admin() || defined('REST_REQUEST') || defined('COOKIEADMIN_SCANNER') || cookieadmin_is_editor_mode()){
283 + return;
284 + }
285 +
286 + $settings = get_option('cookieadmin_settings');
90 287
91 - array_walk( $cookieadmin_consent, function( $value, $key ) use ( &$cookieadmin_consent ) {
92 - $sanitized_key = sanitize_key( $key );
93 - $cookieadmin_consent[ $sanitized_key ] = sanitize_text_field($value);
94 - } );
288 + if(empty($settings) || empty($settings['block_scripts'])){
289 + return;
290 + }
291 +
292 + $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
293 + $policy = cookieadmin_load_policy();
294 + if(empty($policy) || empty($view)){
295 + return;
296 + }
297 +
298 + ob_start([__CLASS__, 'update_tracking_scripts']);
299 + }
300 +
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){
311 + global $cookieadmin_settings;
312 +
313 + if(!is_string($html) || stripos($html, '<script') === false){
314 + return $html;
315 + }
316 +
317 + if(empty(self::$categorized_cookies)){
318 + return $html;
319 + }
320 +
321 + $cookieadmin_consent = self::get_consent();
322 +
323 + $len = strlen($html);
324 + $cursor = 0;
325 + $output = '';
326 +
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 + }
334 +
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;
347 + }
348 + }
349 +
350 + // Find end of opening tag '>' while respecting quotes
351 + $open_tag_end = false;
352 + $in_quote = false;
353 + $quote_char = '';
354 +
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;
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 + }
369 + }
370 + }
371 +
372 + if ($open_tag_end === false) {
373 + // Unclosed opening tag; append remainder and exit
374 + $output .= substr($html, $script_pos);
375 + break;
376 + }
377 +
378 + $attrs = substr($html, $after_script, $open_tag_end - $after_script);
379 +
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 + }
387 +
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);
391 +
392 + $output .= self::process_script_tag($attrs, $content, $full_tag, $cookieadmin_consent, $cookieadmin_settings);
393 + $cursor = $close_tag_end;
394 + }
395 +
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 + }
95 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 +
96 437 foreach (self::$categorized_cookies as $item) {
97 - $category = strtolower($item->category);
98 - $patterns = json_decode($item->patterns, true);
99 -
100 - if (!empty($patterns) && !empty($category)) {
101 - foreach ($patterns as $pattern) {
102 - if (strpos($src, $pattern) !== false) {
103 -
104 - if ( $category !== 'necessary' &&
105 - (empty($cookieadmin_consent) ||
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) ||
106 449 (!empty($cookieadmin_consent[$category]) && $cookieadmin_consent[$category] == 'false') ||
107 - $cookieadmin_consent['reject'] == 'true')
108 - ) {
109 -
110 - // User has NOT consented -> block the script
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 : '';
111 457
112 - // Option 1 - completely remove script:
113 - // return '';
114 -
115 - // Option 2 - transform to type="text/plain"
116 - $tag = str_replace(
117 - '<script ',
118 - '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '" ',
119 - $tag
120 - );
121 -
122 - return $tag;
123 - }
458 + return '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '"' . $attr_str . '>' . $content . '</script>';
124 459 }
125 460 }
126 461 }
127 462 }
128 463
129 - return $tag;
464 + return $full_tag;
130 465 }
131 466
132 467 static function cookieadmin_show_banner(){
468 +
469 + $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
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 + }
133 476
134 - $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
135 - $policy = cookieadmin_load_policy();
477 + $raw_template = cookieadmin_load_consent_template($policy[$view], $view);
478 +
479 + if(!is_array($raw_template) || empty($raw_template)){
480 + return false;
481 + }
136 482
137 - $templates = implode("", cookieadmin_load_consent_template($policy[$view], $view));
483 + $templates = implode('', $raw_template);
138 484
139 485 $allowed_tags = cookieadmin_kses_allowed_html();
140 486
487 + $templates = apply_filters('cookieadmin_after_banner', $templates);
488 +
141 489 // var_dump($policy[$view]);
142 490 echo wp_kses($templates, $allowed_tags);
143 491 }
144 492
@@ -148,6 +496,10 @@
148 496 $query = $wpdb->prepare("SHOW TABLES LIKE %s", $table_name);
149 497
150 498 return $wpdb->get_var($query) === $table_name;
151 499 }
500 +
501 + static function memberpress_allow_style_prefix($prefix_arr){
502 + $prefix_arr[] = 'cookieadmin';
503 + return $prefix_arr;
504 + }
152 505 }
153 -