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 -55 1.1.2 → 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,91 +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) ||
106 - (!empty($cookieadmin_consent[$category]) && $cookieadmin_consent[$category] == 'false') ||
107 - (!empty($cookieadmin_consent['reject']) && $cookieadmin_consent['reject'] == 'true')
108 - )
109 - ) {
110 -
111 - // User has NOT consented -> block the script
438 + $category = !empty($item->category) ? strtolower($item->category) : '';
439 + $patterns = !empty($item->patterns) ? json_decode($item->patterns, true) : '';
112 440
113 - // Option 1 - completely remove script:
114 - // return '';
441 + if(empty($patterns) || empty($category)){
442 + continue;
443 + }
115 444
116 - // Option 2 - transform to type="text/plain"
117 - $tag = str_replace(
118 - '<script ',
119 - '<script type="text/plain" data-cookieadmin-category="' . esc_attr($category) . '" ',
120 - $tag
121 - );
122 -
123 - return $tag;
124 - }
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>';
125 459 }
126 460 }
127 461 }
128 462 }
129 463
130 - return $tag;
464 + return $full_tag;
131 465 }
132 466
133 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 + }
134 476
135 - $view = get_option('cookieadmin_law', 'cookieadmin_gdpr');
136 - $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 + }
137 482
138 - $templates = implode("", cookieadmin_load_consent_template($policy[$view], $view));
483 + $templates = implode('', $raw_template);
139 484
140 485 $allowed_tags = cookieadmin_kses_allowed_html();
141 486
487 + $templates = apply_filters('cookieadmin_after_banner', $templates);
488 +
142 489 // var_dump($policy[$view]);
143 490 echo wp_kses($templates, $allowed_tags);
144 491 }
145 492
@@ -149,6 +496,10 @@
149 496 $query = $wpdb->prepare("SHOW TABLES LIKE %s", $table_name);
150 497
151 498 return $wpdb->get_var($query) === $table_name;
152 499 }
500 +
501 + static function memberpress_allow_style_prefix($prefix_arr){
502 + $prefix_arr[] = 'cookieadmin';
503 + return $prefix_arr;
504 + }
153 505 }
154 -