PluginProbe ʕ •ᴥ•ʔ
Complianz – GDPR/CCPA Cookie Consent / 7.4.6
Complianz – GDPR/CCPA Cookie Consent v7.4.6
7.4.6 trunk 6.5.6 7.0.4 7.0.5 7.1.0 7.1.4 7.1.5 7.2.0 7.3.0 7.3.1 7.4.0 7.4.0.1 7.4.1 7.4.2 7.4.3 7.4.4 7.4.4.1 7.4.4.2 7.4.5 beta
complianz-gdpr / class-cookie-blocker.php
complianz-gdpr Last commit date
DNSMPD 2 months ago assets 1 month ago config 2 months ago cookie 1 month ago cookiebanner 1 month ago cron 7 months ago documents 2 months ago gutenberg 1 month ago integrations 1 month ago languages 1 month ago mailer 1 year ago onboarding 1 year ago placeholders 7 months ago progress 2 years ago proof-of-consent 1 month ago rest-api 1 month ago settings 1 month ago templates 6 months ago upgrade 1 month ago websitescan 2 months ago LICENSE.txt 4 years ago README.md 7 months ago class-admin.php 6 months ago class-company.php 2 years ago class-cookie-blocker.php 7 months ago class-export.php 2 years ago class-installer.php 2 years ago class-review.php 11 months ago complianz-gpdr.php 1 month ago functions-legacy.php 2 years ago functions.php 2 months ago index.php 7 years ago loco.xml 4 years ago readme.txt 1 month ago security.md 2 years ago system-status.php 1 year ago uninstall.php 1 month ago upgrade.php 2 months ago
class-cookie-blocker.php
983 lines
1 <?php
2 defined( 'ABSPATH' ) or die( "you do not have access to this page!" );
3
4 if ( ! class_exists( 'cmplz_cookie_blocker' ) ) {
5 class cmplz_cookie_blocker {
6 private static $_this;
7 public $cookie_list = [];
8 public $delete_cookies_list = [];
9
10 function __construct() {
11 if ( isset( self::$_this ) ) {
12 wp_die( sprintf( '%s is a singleton class and you cannot create a second instance.',
13 get_class( $this ) ) );
14 }
15
16 add_action( 'rest_api_init', array($this, 'cmplz_cookie_data_rest_route') );
17 add_action( 'init', array( $this, 'create_delete_cookies_list'));
18 add_action( 'send_headers', array( $this, 'delete_cookies'));
19 self::$_this = $this;
20 }
21
22 static function this() {
23 return self::$_this;
24 }
25
26 /**
27 * Get list of cookies for the cookie shredder
28 * @return void
29 */
30 public function load_cookie_data(){
31 if ( cmplz_get_option( 'safe_mode' ) == 1 || cmplz_get_option( 'consent_per_service' ) !== 'yes' ) {
32 return;
33 }
34 $this->cookie_list = cmplz_get_transient('cmplz_cookie_shredder_list' );
35 if ( !$this->cookie_list ) {
36 $this->cookie_list = [];
37 $cookie_list = COMPLIANZ::$banner_loader->get_cookies( array(
38 'ignored' => false,
39 'hideEmpty' => false,
40 'language' => 'en', //only for one language
41 'showOnPolicy' => true,
42 'deleted' => false,
43 'isMembersOnly' => cmplz_get_option( 'wp_admin_access_users' ) === 'yes' ? 'all' : false,
44 ) );
45 $this->get_cookies($cookie_list, 'preferences');
46 $this->get_cookies($cookie_list, 'statistics');
47 $this->get_cookies($cookie_list, 'marketing');
48 cmplz_set_transient('cmplz_cookie_shredder_list', $this->cookie_list, HOUR_IN_SECONDS);
49 }
50 }
51
52 /**
53 * Create a list of cookies that should be deleted
54 *
55 * @return void
56 */
57 public function create_delete_cookies_list(){
58 if ( cmplz_get_option( 'safe_mode' ) == 1 || cmplz_get_option( 'consent_per_service' ) !== 'yes' ) {
59 return;
60 }
61 if ( is_admin() ) {
62 return;
63 }
64 $this->load_cookie_data();
65
66 $current_cookies = array_keys($_COOKIE);
67 foreach ( $this->cookie_list as $category => $cookies){
68 if ( cmplz_has_consent( $category)) continue;
69
70 if (!is_array($cookies) ) {
71 continue;
72 }
73
74 foreach ($cookies as $service => $cookie_list ) {
75 if (cmplz_has_service_consent($service)) continue;
76 foreach ($current_cookies as $key => $current_cookie ) {
77 $found = cmplz_strpos_arr($current_cookie, $cookie_list);
78 if ( $found ){
79 $this->delete_cookies_list[] = $current_cookie;
80 }
81 }
82 }
83 }
84 //ensure there are no duplicate arrays
85 $this->delete_cookies_list = array_unique($this->delete_cookies_list);
86 }
87
88 /**
89 * Clear cookies on header send
90 * @return void
91 */
92 public function delete_cookies(){
93 $max = 20;
94 $count=0;
95 foreach ($this->delete_cookies_list as $name ) {
96 //limit header size by limiting number of cookies to delete in one go.
97 if ($count>$max) {
98 continue;
99 }
100 $count++;
101 unset($_COOKIE[$name]);
102 $name = $this->sanitize_cookie_name($name);
103 setcookie($name, "", -1, COMPLIANZ::$banner_loader->get_cookie_path() );
104 setcookie($name, "", -1, '/' );
105 }
106 }
107
108 /**
109 * Sanitize cookie name. Remove any characters that are not alphanumeric, underscore, or dash to prevent fatal errors in the setcookie function
110 *
111 * @param string $name
112 *
113 * @return string
114 */
115
116 public function sanitize_cookie_name(string $name): string {
117 // Remove any characters that are not alphanumeric, underscore, or dash
118 return preg_replace('/[^a-zA-Z0-9_-]/', '', $name);
119 }
120
121 /**
122 * Add cookie data rest route
123 * @return void
124 */
125 public function cmplz_cookie_data_rest_route() {
126 register_rest_route( 'complianz/v1', 'cookie_data/', array(
127 'methods' => 'GET',
128 'callback' => array( $this, 'cookie_data'),
129 'permission_callback' => '__return_true',
130 ) );
131 }
132
133 /**
134 * Add cookies to list by category
135 *
136 * @param array $cookie_list
137 * @param string $category
138 *
139 * @return void
140 */
141 public function get_cookies( $cookie_list, $category) {
142 if (is_array($cookie_list)) {
143 foreach ( $cookie_list as $cookie ) {
144 if ( stripos( $cookie->purpose, $category ) !== false ) {
145 $this->cookie_list[ $category ][ $this->sanitize_service_name( $cookie->service ) ][] = str_replace( '*', '', $cookie->name );
146 }
147 }
148 }
149 }
150
151 /**
152 * Get a blocked content notice
153 * @return string
154 */
155 public function blocked_content_text(){
156 if (cmplz_get_option( 'consent_per_service' ) === 'yes') {
157 $agree_text = cmplz_get_option( 'agree_text_per_service' );
158 $placeholdertext = cmplz_get_option( 'blocked_content_text_per_service' );
159 $placeholdertext = '<div class="cmplz-blocked-content-notice-body">'.$placeholdertext.'&nbsp;<div class="cmplz-links"><a href="#" class="cmplz-link cookie-statement">{title}</a></div></div><button class="cmplz-accept-service">'.$agree_text.'</button>';
160 } else {
161 $placeholdertext = cmplz_get_option( 'blocked_content_text' );
162 }
163
164 return apply_filters('cmplz_accept_cookies_blocked_content', $placeholdertext);
165 }
166
167
168 /**
169 * REST API for cookie data
170 * @param WP_REST_Request $request
171 */
172
173 public function cookie_data( WP_REST_Request $request ){
174 $this->load_cookie_data();
175 $response = json_encode( $this->cookie_list );
176 header( "Content-Type: application/json" );
177 echo $response;
178 exit;
179 }
180 /**
181 * Get array of scripts to block in correct format
182 * This is the base array, of which dependencies and placeholder lists also are derived
183 *
184 * @return array
185 */
186 public function blocked_styles()
187 {
188 $blocked_styles = apply_filters( 'cmplz_known_style_tags', [] );
189 //make sure every item has the default array structure
190 foreach ($blocked_styles as $key => $blocked_style ){
191 $default = [
192 'name' => 'general',//default service name
193 'enable_placeholder' => 1,
194 'placeholder' => '',
195 'category' => 'marketing',
196 'urls' => array( $blocked_style ),
197 'enable' => 1,
198 'enable_dependency' => 0,
199 'dependency' => '',
200 ];
201
202 if ( !is_array($blocked_style) ){
203 $service = cmplz_get_service_by_src( $blocked_style );
204 $default['name'] = $service;
205 $default['placeholder'] = $service;
206 $blocked_styles[$key] = $default;
207 } else {
208 $blocked_styles[$key] = wp_parse_args( $blocked_style, $default);
209 }
210 }
211
212 $formatted_custom_style_tags = [];
213 foreach ( $blocked_styles as $blocked_style ) {
214 $blocked_style['name'] = $this->sanitize_service_name($blocked_style['name']);
215 if ( isset($blocked_style['urls']) ) {
216 foreach ($blocked_style['urls'] as $url ) {
217 $formatted_custom_style_tags[$url] = $blocked_style;
218 }
219 } else if (isset($blocked_style['editor'])) {
220 $formatted_custom_style_tags[$blocked_style['editor']] = $blocked_style;
221 }
222 }
223 return $formatted_custom_style_tags;
224 }
225 /**
226 * Get array of scripts to block in correct format
227 * This is the base array, of which dependencies and placeholder lists also are derived
228 *
229 * @return array
230 */
231 public function blocked_scripts()
232 {
233 $blocked_scripts = apply_filters( 'cmplz_known_script_tags', array() );
234 $scripts = get_option("complianz_options_custom-scripts");
235 if ( is_array($scripts) && isset($scripts['block_script']) && is_array($scripts['block_script']) ) {
236 $custom_script_tags = array_filter( $scripts['block_script'], function($script) {
237 return $script['enable'] == 1;
238 });
239 $blocked_scripts = array_merge($blocked_scripts, $custom_script_tags);
240 }
241
242 $blocked_scripts = apply_filters_deprecated( 'cmplz_known_iframe_tags', array($blocked_scripts), '6.0.0', 'cmplz_known_script_tags', 'The cmplz_known_iframe_tags filter is deprecated');
243
244 //make sure every item has the default array structure
245 foreach ($blocked_scripts as $key => $blocked_script ){
246 $default = [
247 'name' => 'general',//default service name
248 'enable_placeholder' => 1,
249 'placeholder' => '',
250 'category' => 'marketing',
251 'urls' => array( $blocked_script ),
252 'enable' => 1,
253 'enable_dependency' => 0,
254 'dependency' => '',
255 ];
256
257 if ( !is_array($blocked_script) ){
258 $service = cmplz_get_service_by_src( $blocked_script );
259 $default['name'] = $service;
260 $default['placeholder'] = $service;
261 $blocked_scripts[$key] = $default;
262 } else {
263 $blocked_scripts[$key] = wp_parse_args( $blocked_script, $default);
264 }
265
266 }
267
268 $formatted_custom_script_tags = [];
269 foreach ( $blocked_scripts as $blocked_script ) {
270 $blocked_script['name'] = $this->sanitize_service_name($blocked_script['name']);
271 if ( cmplz_placeholder_disabled($blocked_script['name']) ) {
272 $blocked_script['enable_placeholder'] = 0;
273 }
274 if ( isset($blocked_script['urls']) ) {
275 foreach ($blocked_script['urls'] as $url ) {
276 $formatted_custom_script_tags[$url] = $blocked_script;
277 }
278 } else if (isset($blocked_script['editor'])) {
279 $formatted_custom_script_tags[$blocked_script['editor']] = $blocked_script;
280 }
281 }
282 return $formatted_custom_script_tags;
283 }
284
285 /**
286 * @param $title
287 *
288 * @return array|string|string[]
289 */
290 public function sanitize_service_name($title) {
291 if (empty($title)) {
292 return 'general';
293 }
294 return cmplz_sanitize_title_preserve_uppercase($title);
295 }
296
297 /**
298 * Get array of placeholder - placeholder_classes for non iframe blocked content
299 * @param array $blocked_scripts
300 * @return array
301 */
302 public function placeholder_markers( $blocked_scripts )
303 {
304 $placeholder_markers = apply_filters( 'cmplz_placeholder_markers', array() );
305
306 //current format: array('facebook' = array('class1','class2') )
307 //force into new structure
308 foreach ( $placeholder_markers as $name => $placeholders ) {
309 foreach ( $placeholders as $class ) {
310 $name = $this->sanitize_service_name($name);
311 $blocked_scripts[] = [
312 'name' => $name,
313 'placeholder' => $name,
314 'placeholder_class' => $class,
315 'category' => 'marketing',
316 'enable_placeholder' => 1,
317 'iframe' => 0,
318 ];
319 }
320 }
321
322 //add script center data. add_script arrays aren't included in the "known_script_tags" function
323 $scripts = get_option("complianz_options_custom-scripts");
324 if ( is_array($scripts) && isset($scripts['add_script']) && is_array($scripts['add_script'] ) ) {
325 $added_scripts = array_filter( $scripts['add_script'], static function ( $script ) {
326 return $script['enable'] == 1;
327 } );
328 if (!empty($added_scripts)) $blocked_scripts = array_merge($blocked_scripts, $added_scripts);
329 }
330
331 //filter out non-iframe and disabled placeholders.
332 //'add_script' items do not have an iframe
333 return array_filter( $blocked_scripts, static function($script) {
334 return isset($script['enable_placeholder']) && $script['enable_placeholder'] == 1 && (!isset($script['iframe']) || $script['iframe'] == 0) && !empty($script['placeholder_class']);
335 });
336 }
337
338 /**
339 * Get dependencies and merge with dependencies from the script center
340 * @param array $blocked_scripts
341 * @return array
342 */
343
344 function dependencies( $blocked_scripts ) {
345 //array['wait-for-this-script'] = 'script-that-should-wait';
346 $dependencies = apply_filters( 'cmplz_dependencies', array() );
347 $scripts = get_option( "complianz_options_custom-scripts" );
348 if ( is_array( $scripts ) && isset( $scripts['block_script'] ) && is_array( $scripts['block_script'] ) ) {
349 $added_scripts = array_filter( $scripts['block_script'], function ( $script ) {
350 return $script['enable'] == 1;
351 } );
352 $blocked_scripts = array_merge($blocked_scripts, $added_scripts);
353 }
354 $blocked_scripts = array_filter( $blocked_scripts, function ( $script ) {
355 return isset($script['enable_dependency']) && $script['enable_dependency'] == 1 && !empty($script['dependency']);
356 } );
357
358 $flat = array();
359 foreach ( $blocked_scripts as $data ) {
360 $flat = array_merge($flat, $data['dependency']);
361 }
362 return array_merge($dependencies, $flat);
363 }
364
365 /**
366 * Get array of whitelisted scripts, and add flattened scriptcenter whitelist
367 *
368 * @return array
369 */
370
371 public function whitelisted_scripts( ) {
372 //whitelist our localized inline scripts
373 $whitelisted_script_tags = apply_filters( 'cmplz_whitelisted_script_tags', array('user_banner_id') );
374 $scripts = get_option("complianz_options_custom-scripts");
375 if ( is_array($scripts) && isset($scripts['whitelist_script']) && is_array($scripts['whitelist_script']) ) {
376 $custom_whitelisted_script_tags = array_filter( $scripts['whitelist_script'], function($script) {
377 return $script['enable'] == 1;
378 });
379
380 //flatten array
381 $flat = array();
382 foreach ( $custom_whitelisted_script_tags as $data ) {
383 $flat = array_merge($flat, $data['urls']);
384 }
385
386 $whitelisted_script_tags = array_merge($flat, $whitelisted_script_tags );
387 }
388 return $whitelisted_script_tags;
389 }
390
391 /**
392 * Apply the mixed content fixer.
393 *
394 * @since 1.0
395 *
396 * @access public
397 *
398 */
399
400 public function filter_buffer( $buffer ) {
401 if ( cmplz_is_amp() ) {
402 $buffer = apply_filters( 'cmplz_cookieblocker_amp', $buffer );
403 } else {
404 $buffer = $this->replace_tags( $buffer );
405 }
406
407 return $buffer;
408 }
409
410 /**
411 * Start buffering the output
412 *
413 * @since 1.0
414 *
415 * @access public
416 *
417 */
418
419 public function start_buffer() {
420 /**
421 * Don't activate the cookie blocker is AMP is active, but the AMP integration is not enabled
422 * This problem only occurs for manually included iframes, not for WP generated embeds
423 */
424
425 if ( cmplz_is_amp_endpoint() && !cmplz_amp_integration_active() ) {
426 return;
427 }
428
429 ob_start( array( $this, "filter_buffer" ) );
430 }
431
432 /**
433 * Flush the output buffer
434 *
435 * @since 1.0
436 *
437 * @access public
438 *
439 */
440
441 public function end_buffer() {
442
443 /**
444 * Don't activate the cookie blocker is AMP is active, but the AMP integration is not enabled
445 */
446
447 if ( cmplz_is_amp_endpoint() && !cmplz_amp_integration_active() ) {
448 return;
449 }
450
451 if ( ob_get_length() ) {
452 ob_end_flush();
453 }
454 }
455
456 /**
457 * Just before the page is sent to the visitor's browser, remove all tracked third party scripts
458 *
459 * @since 1.0
460 *
461 * @access public
462 *
463 */
464 public function replace_tags( $output ) {
465 /**
466 * Get style tags
467 *
468 * */
469 $known_style_tags = $this->blocked_styles();
470
471 /**
472 * Get script tags, including custom user scripts
473 *
474 * */
475 $blocked_scripts = cmplz_get_transient('cmplz_blocked_scripts');
476 if ( isset($_GET['cmplz_nocache']) ) {
477 $blocked_scripts = false;
478 }
479
480 if ( !$blocked_scripts ) {
481 $blocked_scripts = $this->blocked_scripts();
482 cmplz_set_transient('cmplz_blocked_scripts', $blocked_scripts, 30 * MINUTE_IN_SECONDS );
483 }
484
485 /**
486 * Get placeholder markers for non iframe blocked content
487 */
488
489 $placeholder_markers = $this->placeholder_markers( $blocked_scripts );
490
491 /**
492 * Get whitelisted script tags
493 *
494 * */
495 $whitelisted_script_tags = $this->whitelisted_scripts();
496
497 /**
498 * Get dependencies between scripts
499 *
500 * */
501 $dependencies = $this->dependencies( $blocked_scripts );
502
503 /**
504 * Get list of tags that require post scribe to be enabled on the page. Currently only for instawidget.js
505 *
506 * */
507
508 $post_scribe_list = apply_filters( 'cmplz_post_scribe_tags', array() );
509
510
511 //not meant as a "real" URL pattern, just a loose match for URL type strings.
512 //edit: instagram uses ;width, so we need to allow ; as well.
513 $url_pattern = '([\w.,;ß@?^=%&:()\/~+#!\-*]*?)';
514
515 /**
516 * Handle images from third party services, e.g. google maps
517 *
518 * */
519 $image_tags = apply_filters( 'cmplz_image_tags', array() );
520 $image_pattern = '/<img.*?src=[\'|"](\X*?)[\'|"].*?>/s'; //matches multiline with s operator, for FB pixel
521 if ( preg_match_all( $image_pattern, $output, $matches, PREG_PATTERN_ORDER )
522 ) {
523 foreach ( $matches[1] as $key => $image_url ) {
524 $total_match = $matches[0][ $key ];
525 $found = cmplz_strpos_arr( $image_url, $image_tags );
526 if ( $found !== false ) {
527 $placeholder = cmplz_placeholder( false, $image_url );
528 $service_name = cmplz_get_service_by_src( $image_url );
529 $service_name = !$service_name ? 'general' :$service_name;
530 $new = $total_match;
531 $new = $this->add_data( $new, 'img', 'src-cmplz', $image_url );
532 $new = $this->add_data( $new, 'img', 'service', $service_name );
533 $new = $this->add_data( $new, 'img', 'category', 'marketing' );
534 //remove lazy loading for images, as it is breaking on activation
535 $new = str_replace('loading="lazy"', 'data-deferlazy="1"', $new );
536 $new = $this->add_class( $new, 'img', apply_filters( 'cmplz_image_class', 'cmplz-image', $total_match, $found ) );
537 $new = $this->replace_src( $new, apply_filters( 'cmplz_source_placeholder', $placeholder ) );
538 $new = apply_filters('cmplz_image_html', $new, $image_url);
539
540 if ( cmplz_use_placeholder( $image_url ) ) {
541 $new = $this->add_class( $new, 'img', " cmplz-placeholder-element " );
542 $new = '<div class="cmplz-placeholder-parent">' . $new . '</div>';
543 }
544 $output = str_replace( $total_match, $new, $output );
545 }
546 }
547 }
548
549 /**
550 * Handle styles (e.g. google fonts)
551 *
552 * */
553 $style_pattern = '/<link.*?rel=[\'|"]stylesheet[\'|"][^>].*?href=[\'|"](\X*?)[\'|"][^>]*?>/i';
554 if ( preg_match_all( $style_pattern, $output, $matches, PREG_PATTERN_ORDER ) ) {
555 foreach ( $matches[1] as $key => $style_url ) {
556 $total_match = $matches[0][ $key ];
557 //we don't block scripts with the functional data attribute
558 if ( strpos( $total_match, 'data-category="functional"' ) !== false ) {
559 continue;
560 }
561
562 //check if we can skip blocking this array if a specific string is included
563 if ( cmplz_strpos_arr($total_match, $whitelisted_script_tags) ) {
564 continue;
565 }
566 $found = cmplz_strpos_arr( $style_url, array_keys($known_style_tags) );
567 if ( $found !== false ) {
568 $match = $known_style_tags[$found];
569 $new = $total_match;
570 $service_name = $this->sanitize_service_name($match['name']);
571 $new = $this->add_data( $new, 'link', 'category', apply_filters('cmplz_service_category', $match['category'], $total_match, $found) );
572 $new = $this->add_data( $new, 'link', 'service', $service_name );
573 $new = $this->replace_href( $new );
574 $output = str_replace( $total_match, $new, $output );
575 }
576 }
577 }
578
579 /**
580 * Handle iframes from third parties
581 *
582 * */
583 //the iframes URL pattern allows for a space, which may be included in a Google Maps embed.
584 $iframe_pattern = '/<(iframe)[^>].*?src=[\'"](.*?)[\'"].*?>.*?<\/iframe>/is';
585 if ( preg_match_all( $iframe_pattern, $output, $matches, PREG_PATTERN_ORDER ) ) {
586 foreach ( $matches[0] as $key => $total_match ) {
587 $iframe_src = $matches[2][ $key ];
588 if ( ( $tag_key = cmplz_strpos_arr($iframe_src, array_keys($blocked_scripts)) ) !== false ) {
589 $tag = $blocked_scripts[$tag_key];
590 if ($tag['category']==='functional') {
591 continue;
592 }
593
594 $is_video = $this->is_video( $iframe_src );
595 $service_name = $this->sanitize_service_name($tag['name']);
596 $new = $total_match;
597 $new = preg_replace( '~<iframe\\s~i', '<iframe data-cmplz-target="'.apply_filters('cmplz_data_target', 'src', $total_match).'" data-src-cmplz="' . $iframe_src . '" ', $new , 1 ); // make sure we replace it only once
598
599 //remove lazy loading for iframes, as it is breaking on activation
600 $new = str_replace('loading="lazy"', 'data-deferlazy="1"', $new );
601 //check if we can skip blocking this array if a specific string is included
602 if ( cmplz_strpos_arr($total_match, $whitelisted_script_tags) ) continue;
603 //we insert video/no-video class for specific video styling
604 $video_class = $is_video ? 'cmplz-video' : 'cmplz-no-video';
605 $video_class = apply_filters( 'cmplz_video_class', $video_class );
606
607 $new = $this->replace_src( $new, apply_filters( 'cmplz_source_placeholder', 'about:blank' ) );
608 $new = $this->add_class( $new, 'iframe', "cmplz-iframe cmplz-iframe-styles $video_class " );
609 $new = $this->add_data( $new, 'iframe', 'service', $service_name );
610 $new = $this->add_data( $new, 'iframe', 'category', $tag['category'] );
611
612 if ( cmplz_use_placeholder( $iframe_src ) ) {
613 $placeholder = cmplz_placeholder($tag['placeholder'], $iframe_src );
614 $new = $this->add_class( $new, 'iframe', "cmplz-placeholder-element" );
615 $new = $this->add_data( $new, 'iframe', 'placeholder-image', $placeholder );
616 //allow for integrations to override html
617 $new = apply_filters( 'cmplz_iframe_html', $new );
618
619 //make sure there is a parent element which contains this iframe only, to attach the placeholder to
620 if ( ! $is_video
621 && ! $this->no_div( $iframe_src )
622 ) {
623 $new = '<div class="cmplz-placeholder-parent">' . $new . '</div>';
624 }
625 }
626 $output = str_replace( $total_match, $new, $output );
627 }
628 }
629 }
630
631 /**
632 * specific classic wp video shortcode integration
633 */
634 if ( cmplz_uses_thirdparty('youtube') || cmplz_uses_thirdparty('vimeo') ) {
635 $iframe_pattern = '/<video class="wp-video-shortcode".*?<(source) type="video.*?src="(.*?)".*?>.*?<\/video>/is';
636 if ( preg_match_all( $iframe_pattern, $output, $matches, PREG_PATTERN_ORDER ) ) {
637 foreach ( $matches[0] as $key => $total_match ) {
638 $iframe_src = $matches[2][ $key ];
639 if ( ( $tag_key = cmplz_strpos_arr( $iframe_src, array_keys( $blocked_scripts ) ) ) !== false ) {
640 $tag = $blocked_scripts[ $tag_key ];
641 if ( $tag['category'] === 'functional' ) {
642 continue;
643 }
644 $service_name = sanitize_title( $tag['name'] );
645 $new = $total_match;
646 //check if we can skip blocking this array if a specific string is included
647 if ( cmplz_strpos_arr( $total_match, $whitelisted_script_tags ) ) {
648 continue;
649 }
650 //we add an additional class to make it possible to link some css to the blocked html.
651 $video_class_pattern = '/(["| ])(wp-video)(["| ])/is';
652 $output = preg_replace( $video_class_pattern, '$1wp-video cmplz-wp-video$3', $output );
653
654 $video_class = apply_filters( 'cmplz_video_class', 'cmplz-video' );
655 $new = $this->add_class( $new, 'video', " $video_class " );
656 $new = $this->add_data( $new, 'video', 'service', $service_name );
657 $new = $this->add_data( $new, 'video', 'category', $tag['category'] );
658 $new = str_replace( array( 'wp-video-shortcode', 'controls="controls"' ), array( 'cmplz-wp-video-shortcode', '' ), $new );
659 if ( cmplz_use_placeholder( $iframe_src ) ) {
660 $placeholder = cmplz_placeholder( $tag['placeholder'], $iframe_src );
661 $new = $this->add_class( $new, 'video', "cmplz-placeholder-element" );
662 $new = $this->add_data( $new, 'video', 'placeholder-image', $placeholder );
663 //allow for integrations to override html
664 $new = apply_filters( 'cmplz_source_html', $new );
665 }
666
667 $output = str_replace( $total_match, $new, $output );
668 }
669 }
670 }
671 }
672
673 /**
674 * set non iframe placeholders
675 *
676 * */
677 if ( cmplz_use_placeholder() ) {
678 foreach ( $placeholder_markers as $placeholder ) {
679 //placeholder class can be comma separated list e.g. facebook service integration
680 $classes = array_map('trim', explode(',',$placeholder['placeholder_class']) );
681 foreach ( $classes as $placeholder_class ) {
682 $placeholder_pattern = '/<(a|section|div|blockquote|twitter-widget)*[^>]*(id|class)=[\'" ]*[^>]*(' . $placeholder_class . ')[\'" ].*?>/is';
683 if ( preg_match_all( $placeholder_pattern, $output, $matches, PREG_PATTERN_ORDER ) ) {
684 foreach ( $matches[0] as $key => $html_match ) {
685 $el = $matches[1][ $key ];
686 if ( ! empty( $el ) ) {
687 $type = $placeholder['placeholder'];
688 $new_html = $this->add_data( $html_match, $el, 'placeholder-image', cmplz_placeholder( $type, $placeholder_class ) );
689 $new_html = $this->add_data( $new_html, $el, 'category', $placeholder['category'] );
690 $new_html = $this->add_data( $new_html, $el, 'service', $placeholder['name'] );
691 $new_html = $this->add_class( $new_html, $el, "cmplz-placeholder-element" );
692 $output = str_replace( $html_match, $new_html, $output );
693 }
694 }
695 }
696 }
697
698 }
699 }
700
701 /**
702 * Handle scripts from third parties
703 *
704 * */
705 $script_pattern = '/(<script.*?>)(\X*?)<\/script>/is';
706 $index = 0;
707 if ( preg_match_all( $script_pattern, $output, $matches, PREG_PATTERN_ORDER ) ) {
708 foreach ( $matches[1] as $key => $script_open ) {
709 //we don't block scripts with the functional data attribute
710 if ( strpos( $script_open, 'data-category="functional"' ) !== false ) {
711 continue;
712 }
713
714 //exclude ld+json
715 if ( strpos( $script_open, 'application/ld+json' ) !== false ) {
716 continue;
717 }
718
719 //check if we can skip blocking this array if a specific string is included
720 $total_match = $matches[0][ $key ];
721 $content = $matches[2][ $key ];
722 if ( cmplz_strpos_arr($total_match, $whitelisted_script_tags) ) {
723 continue;
724 }
725
726 //if there is inline script here, it has some content
727 if ( ! empty( $content ) )
728 {
729 if ( strpos( $content, 'avia_preview' ) !== false ) {
730 continue;
731 }
732
733 $found = cmplz_strpos_arr( $content, array_keys($blocked_scripts) );
734 if ( $found !== false ) {
735
736 $match = $blocked_scripts[$found];
737 $service_name = $this->sanitize_service_name($match['name']);
738 $new = $total_match;
739 $category = apply_filters_deprecated( 'cmplz_script_class', array($match['category'], $total_match, $found), '6.0.0', 'cmplz_service_category', 'The cmplz_script_class filter has been deprecated since 6.0');
740 $category = apply_filters('cmplz_service_category', $category, $total_match, $found);
741
742 //skip if functional
743 if ( $category === 'functional' ) {
744 continue;
745 }
746
747 $new = $this->add_data( $new, 'script', 'category', $category );
748 $new = $this->add_data( $new, 'script', 'service', $service_name );
749 $new = $this->set_javascript_to_plain( $new );
750 $waitfor = cmplz_strpos_arr( $content, $dependencies );
751 if ( $waitfor !== false ) {
752 $new = $this->add_data( $new, 'script', 'waitfor', $waitfor );
753 }
754
755 $output = str_replace( $total_match, $new, $output );
756 }
757 }
758
759 //when script contains src
760 $script_src_pattern = '/<script[^>]*?src=[\'"]' . $url_pattern . '[\'"].*?>/is';
761 if ( preg_match_all( $script_src_pattern, $total_match, $src_matches, PREG_PATTERN_ORDER ) ) {
762 foreach ( $src_matches[1] as $src_key => $script_src ) {
763 $script_src = $src_matches[1][ $src_key ];
764 $found = cmplz_strpos_arr( $script_src, array_keys($blocked_scripts) );
765 if ( $found !== false ) {
766 $match = $blocked_scripts[$found];
767 $new = $total_match;
768 $service_name = $this->sanitize_service_name($match['name']);
769 $category = apply_filters_deprecated( 'cmplz_script_class', array($match['category'], $total_match, $found), '6.0.0', 'cmplz_service_category', 'The cmplz_script_class filter has been deprecated since 6.0');
770 $new = $this->add_data( $new, 'script', 'category', apply_filters('cmplz_service_category', $category, $total_match, $found) );
771 $new = $this->add_data( $new, 'script', 'service', $service_name );
772 //native scripts don't have to be blocked
773 if ( strpos( $new, 'data-category="functional"' ) === false
774 ) {
775 $new = $this->set_javascript_to_plain( $new );
776 $new = str_replace( 'src=', 'data-cmplz-src=', $new );
777
778 if ( cmplz_strpos_arr( $found, $post_scribe_list )
779 ) {
780 //will be to late for the first page load, but will enable post scribe on next page load
781 if (!get_option('cmplz_post_scribe_required')) {
782 update_option('cmplz_post_scribe_required', true);
783 }
784 $index ++;
785 $new = $this->add_data( $new, 'script', 'post_scribe_id', 'cmplz-ps-' . $index );
786 $new .= '<div class="cmplz-blocked-content-container">'
787 . COMPLIANZ::$cookie_blocker->blocked_content_text()
788 . '<div id="cmplz-ps-' . $index . '"><img src="' . cmplz_placeholder( 'div' ) . '"></div></div>';
789
790 }
791
792 //maybe add dependency
793 $waitfor = cmplz_strpos_arr( $script_src, $dependencies );
794 if ( $waitfor !== false ) {
795 $new = $this->add_data( $new, 'script', 'waitfor', $waitfor );
796 }
797 }
798
799 $output = str_replace( $total_match, $new, $output );
800 }
801 }
802 }
803 }
804 }
805
806 //add a marker so we can recognize if this function is active on the front-end
807 $id = 1;
808 if ( cmplz_get_option( 'consent_per_service' ) === 'yes' ) {
809 $id = 2;
810 }
811 $output = str_replace( "<body ", "<body data-cmplz=$id ", $output );
812
813
814 return apply_filters('cmplz_cookie_blocker_output', $output);
815 }
816
817 /**
818 * Set the javascript attribute of a script element to plain
819 *
820 * @param string $script
821 *
822 * @return string
823 */
824
825 private function set_javascript_to_plain( string $script ): string {
826 //check if it's already set to plain
827 if ( strpos( $script, 'text/plain')!== false ) {
828 return $script;
829 }
830
831 // Check text/javascript
832 $pattern = '/<script[^>].*?\K(type=[\'|\"]text\/javascript[\'|\"])(?=.*>)/i';
833 preg_match( $pattern, $script, $matches );
834 if ( $matches ) {
835 return preg_replace( $pattern, 'type="text/plain"', $script, 1 );
836 }
837
838 // Check type="module"
839 $pattern_module = '/(<script[^>]*?)(type=[\'|\"]module[\'|\"])([^>]*?>)/i';
840 preg_match($pattern_module, $script, $matches_module);
841 if ($matches_module) {
842 return preg_replace($pattern_module, '$1 type="text/plain" data-script-type="module" $3', $script, 1);
843 }
844
845 $pos = strpos( $script, "<script" );
846 if ( $pos !== false ) {
847 return substr_replace( $script, '<script type="text/plain"', $pos, strlen( "<script" ) );
848 }
849
850 return $script;
851 }
852
853 /**
854 * replace the src attribute with a placeholder of choice
855 *
856 * @param string $script
857 * @param string $new_src
858 *
859 * @return string
860 */
861
862 private function replace_src( $script, $new_src ) {
863
864 $pattern = '/src=[\'"](http:\/\/|https:\/\/|\/\/)([\s\wêëèéēėęàáâæãåāäöôòóœøüÄÖÜß.,@!?^=%&:\/~+#-;]*[\w@!?^=%&\/~+#-;]?)[\'"]/i';
865 $new_src = ' src="' . $new_src . '" ';
866 preg_match( $pattern, $script, $matches );
867 $script = preg_replace( $pattern, $new_src, $script );
868
869 return $script;
870 }
871
872 /**
873 * replace the href attribute with a data-href attribute
874 *
875 * @param string $link
876 *
877 * @return string
878 */
879
880 private function replace_href( $link ) {
881 return str_replace( 'href=', 'data-href=', $link );
882 }
883
884 /**
885 * Add a class to an HTML element
886 *
887 * @param string $html
888 * @param string $el
889 * @param string $class
890 *
891 * @return string
892 */
893
894 public function add_class( $html, $el, $class ) {
895 $classes = array_filter( explode(' ', $class) );
896 preg_match( '/<' . $el . '[^\>]*[^\>\S]+\K(class=")(.*)"/i', $html, $matches );
897 if ( $matches ) {
898 foreach ($classes as $class){
899 //check if class is already added
900 if (strpos($matches[2], $class) === false && strlen(trim($class))>0) {
901 $html = preg_replace( '/<' . $el . '[^\>]*[^\>\S]+\K(class=")/i', 'class="' . esc_attr($class) . ' ', $html, 1 );
902 }
903 }
904
905 } else {
906 $pos = strpos( $html, "<$el" );
907 if ( $pos !== false ) {
908 $html = substr_replace( $html,
909 '<' . $el . ' class="' . esc_attr($class) . '"', $pos,
910 strlen( "<$el" ) );
911 }
912 }
913
914 return $html;
915 }
916
917 /**
918 * Add a data attribute to an html element
919 *
920 * @param string $html
921 * @param string $el
922 * @param string $id
923 * @param string $content
924 *
925 * @return string $html
926 */
927
928 public function add_data( $html, $el, $id, $content ) {
929 $content = esc_attr( $content );
930 $id = esc_attr( $id );
931 $pattern = '/<'.$el.'[^>].*?\K(data-'.preg_quote($id, '/').'=[\'|\"]'.preg_quote($content, '/').'[\'|\"])(?=.*>)/i';
932 preg_match( $pattern, $html, $matches );
933 if ( !$matches ) {
934 $pos = strpos( $html, "<$el" );
935 if ( $pos !== false ) {
936 $html = substr_replace( $html, '<' . $el . ' data-' . $id . '="' . $content . '"', $pos, strlen( "<$el" ) );
937 }
938 }
939
940 return $html;
941 }
942
943 /**
944 * Check if this iframe source is a video
945 *
946 * @param $iframe_src
947 *
948 * @return bool
949 */
950
951 private function is_video( $iframe_src ) {
952 if ( strpos( $iframe_src, 'dailymotion' ) !== false
953 || strpos( $iframe_src, 'youtube' ) !== false
954 || strpos( $iframe_src, 'vimeo' ) !== false
955 ) {
956 return true;
957 }
958
959 return false;
960 }
961
962 /**
963 * Check if this iframe source is soundcloud
964 *
965 * @param $iframe_src
966 *
967 * @return bool
968 */
969
970 private function no_div( $iframe_src ) {
971 if ( strpos( $iframe_src, 'soundcloud' ) !== false ) {
972 return true;
973 }
974
975 return false;
976 }
977
978 }
979 }
980
981
982
983