PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 2.0.25
Advanced Ads – Ad Manager & AdSense v2.0.25
2.0.25 2.0.24 2.0.23 2.0.22 2.0.21 1.38.0 1.39.0 1.39.1 1.39.2 1.39.3 1.39.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.40.0 1.40.1 1.40.2 1.41.0 1.42.0 1.42.1 1.42.2 1.43.0 1.44.0 1.44.1 1.45.0 1.45.1 1.46.0 1.47.0 1.47.1 1.47.2 1.47.3 1.47.4 1.47.5 1.48.0 1.48.1 1.49.0 1.5.0 1.5.0.1 1.5.1 1.5.2 1.5.2.1 1.5.4 1.5.4.1 1.5.5 1.50.0 1.51.0 1.51.1 1.51.2 1.51.3 1.52.0 1.52.1 1.52.2 1.52.3 1.52.4 1.53.0 1.53.1 1.53.2 1.54.0 1.54.1 1.55.0 1.56.0 1.56.1 1.56.2 1.56.3 1.56.4 1.6 1.6.1 1.6.10 1.6.10.1 1.6.10.2 1.6.11 1.6.11.1 1.6.12 1.6.13 1.6.14 1.6.15 1.6.16 1.6.17 1.6.17.1 1.6.17.2 1.6.2 1.6.2.1 1.6.3 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.6.1 1.6.7 1.6.7.1 1.6.8 1.6.8.1 1.6.8.2 1.6.8.3 1.6.9 1.6.9.1 1.6.9.2 1.6.9.3 1.6.9.4 1.7 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.1.1 1.7.1.2 1.7.1.3 1.7.1.4 1.7.1.5 1.7.10 trunk 1.7.11 1.0.1 1.7.12 1.0.2 1.7.13 1.0.3 1.7.14 1.1.0 1.7.15 1.1.1 1.7.16 1.1.2 1.7.17 1.1.3 1.7.18 1.10 1.7.19 1.10.1 1.7.2 1.10.10 1.7.2.1 1.10.11 1.7.20 1.10.12 1.7.21 1.10.2 1.7.22 1.10.3 1.7.23 1.10.4 1.7.24 1.10.5 1.7.25 1.10.6 1.7.3 1.10.7 1.7.4 1.10.8 1.7.4.1 1.10.9 1.7.4.2 1.11 1.7.4.3 1.11.1 1.7.4.4 1.11.2 1.7.4.5 1.12 1.7.5 1.13 1.7.5.1 1.13.1 1.7.6 1.13.2 1.7.7 1.13.3 1.7.8 1.13.4 1.7.9 1.13.5 1.7.9.1 1.13.6 1.7.9.2 1.13.7 1.7.9.3 1.13.8 1.8 1.14 1.8.1 1.14.1 1.8.10 1.14.10 1.8.11 1.14.11 1.8.12 1.14.2 1.8.13 1.14.3 1.8.14 1.14.4 1.8.15 1.14.5 1.8.16 1.14.6 1.8.17 1.14.7 1.8.18 1.14.8 1.8.19 1.14.9 1.8.2 1.15 1.8.20 1.16 1.8.21 1.16.1 1.8.22 1.17 1.8.23 1.17.1 1.8.24 1.17.10 1.8.25 1.17.10-rc.1 1.8.26 1.17.11 1.8.27 1.17.12 1.8.28 1.17.12-rc.1 1.8.29 1.17.2 1.8.3 1.17.3 1.8.30 1.17.4 1.8.4 1.17.5 1.8.5 1.17.6 1.8.6 1.17.7 1.8.7 1.17.8 1.8.8 1.17.9 1.8.9 1.17.9-beta.1 1.9 1.18.0 2.0.0 1.19.0 2.0.1 1.19.1 2.0.10 1.2 2.0.11 1.2.1 2.0.12 1.2.2 2.0.13 1.2.3 2.0.14 1.2.4 2.0.15 1.2.5 2.0.16 1.2.6 2.0.17 1.2.7 2.0.18 1.20.0 2.0.19 1.20.0-rc.1 2.0.2 1.20.0-rc.2 2.0.20 1.20.1 2.0.3 1.20.2 2.0.4 1.20.3 2.0.5 1.21.0 2.0.6 1.21.1 2.0.7 1.22.0 2.0.8 1.22.1 2.0.9 1.22.2 1.23.0 1.23.1 1.23.2 1.24.0 1.24.1 1.24.2 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.29.0 1.29.1 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.30.0 1.30.1 1.30.2 1.30.2-rc.1 1.30.3 1.30.4 1.30.4-rc.1 1.30.5 1.31.0 1.31.1 1.32.0 1.32.0-rc.1 1.33.0 1.33.1 1.33.2 1.34.0 1.35.0 1.35.1 1.36.0 1.36.1 1.36.2 1.36.3 1.37.0 1.37.1 1.37.2
advanced-ads / classes / frontend_checks.php
advanced-ads / classes Last commit date
ad-health-notices.php 1 month ago checks.php 3 months ago display-conditions.php 1 day ago filesystem.php 1 month ago frontend_checks.php 1 week ago inline-css.php 1 year ago utils.php 1 week ago visitor-conditions.php 1 day ago
frontend_checks.php
1282 lines
1 <?php
2 // phpcs:ignoreFile
3
4 use AdvancedAds\Abstracts\Ad;
5 use AdvancedAds\Utilities\Data;
6 use AdvancedAds\Utilities\Validation;
7 use AdvancedAds\Utilities\Conditional;
8
9 /**
10 * Class Advanced_Ads_Frontend_Checks
11 *
12 * Handle Ad Health and other notifications and checks in the frontend.
13 */
14 class Advanced_Ads_Frontend_Checks {
15 /**
16 * True if 'the_content' was invoked, false otherwise.
17 *
18 * @var bool
19 */
20 private $did_the_content = false;
21 private $has_many_the_content = false;
22
23 /**
24 * Plugin options.
25 *
26 * @var array
27 */
28 private $options = [];
29
30 /**
31 * Published placements loaded once per request for frontend checks.
32 *
33 * @var array|null
34 */
35 private $frontend_placements = null;
36
37 /**
38 * Constructor.
39 */
40 public function __construct() {
41 // Wait until other plugins (for example Elementor) have disabled admin bar using `show_admin_bar` filter.
42 add_action( 'template_redirect', [ $this, 'init' ], 11 );
43
44 if ( wp_doing_ajax() ) {
45 add_filter( 'advanced-ads-ad-output', [ $this, 'after_ad_output' ], 10, 2 );
46 }
47
48 // get plugin options.
49 $this->options = Advanced_Ads::get_instance()->options();
50 }
51
52 /**
53 * Ad Health init.
54 */
55 public function init() {
56 if ( ! is_admin()
57 && is_admin_bar_showing()
58 && Conditional::user_can( 'advanced_ads_edit_ads' )
59 && Advanced_Ads_Ad_Health_Notices::notices_enabled()
60 ) {
61 add_action( 'admin_bar_menu', [ $this, 'add_admin_bar_menu' ], 1000 );
62 add_filter( 'the_content', [ $this, 'set_did_the_content' ] );
63 add_action( 'wp_footer', [ $this, 'footer_checks' ], -101 );
64 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
65 add_filter( 'advanced-ads-ad-select-args', [ $this, 'ad_select_args_callback' ] );
66 add_filter( 'advanced-ads-ad-output', [ $this, 'after_ad_output' ], 10, 2 );
67 }
68
69 if ( Advanced_Ads_Ad_Health_Notices::notices_enabled() ) {
70 add_action( 'body_class', [ $this, 'body_class' ] );
71 }
72
73 if ( ( $this->has_adblocker_placements() || $this->has_adblocker_visitor_condition() )
74 && ! wp_advads()->registry->is_script( 'find-adblocker', 'enqueued' ) ) {
75 wp_advads()->registry->enqueue_script( 'find-adblocker' );
76 }
77 }
78
79 /**
80 * Notify ads loaded with AJAX.
81 *
82 * @param array $args ad arguments.
83 * @return array $args
84 */
85 public function ad_select_args_callback( $args ) {
86 $args['frontend-check'] = true;
87 return $args;
88 }
89
90 /**
91 * Enqueue scripts
92 * needs to add ajaxurl in case no other plugin is doing that
93 */
94 public function enqueue_scripts() {
95 if ( Conditional::is_amp() ) {
96 return;
97 }
98
99 // we don’t have our own script, so we attach this information to jquery.
100 wp_localize_script( 'jquery', 'advads_frontend_checks', [ 'ajax_url' => admin_url( 'admin-ajax.php' ) ] );
101 }
102
103 /**
104 * List current ad situation on the page in the admin-bar.
105 *
106 * @param object $wp_admin_bar WP_Admin_Bar.
107 */
108 public function add_admin_bar_menu( $wp_admin_bar ) {
109 global $wp_the_query, $post, $wp_scripts;
110
111 $options = Advanced_Ads::get_instance()->options();
112
113 // load AdSense related options.
114 $adsense_options = Advanced_Ads_AdSense_Data::get_instance()->get_options();
115
116 // common data used in nodes[].
117 $health_parent_class = 'advanced_ads_ad_health';
118 $health_node_common_data = [
119 'parent' => $health_parent_class,
120 'meta' => [
121 'class' => 'advanced_ads_ad_health_warning',
122 'target' => '_blank',
123 ],
124 ];
125
126 // check if AdSense loads Auto Ads ads
127 // Hidden, will be shown using js.
128 if ( ! isset( $adsense_options['violation-warnings-disable'] ) ) {
129 $nodes[] = [
130 'type' => 2,
131 'data' => [
132 'parent' => $health_parent_class,
133 'id' => 'advanced_ads_autoads_displayed',
134 'title' => __( 'Random AdSense ads', 'advanced-ads' ),
135 'href' => 'https://wpadvancedads.com/adsense-in-random-positions-auto-ads/?utm_source=advanced-ads&utm_medium=link&utm_campaign=frontend-autoads-ads',
136 'meta' => [
137 'class' => 'hidden',
138 'target' => '_blank',
139 ],
140 ],
141 ];
142 }
143
144 // check if current user was identified as a bot.
145 if ( Conditional::is_ua_bot() ) {
146 $nodes[] = [
147 'type' => 1,
148 'data' => array_merge(
149 $health_node_common_data,
150 [
151 'id' => 'advanced_ads_user_is_bot',
152 'title' => __( 'You look like a bot', 'advanced-ads' ),
153 'href' => 'https://wpadvancedads.com/manual/ad-health/#look-like-bot',
154 ]
155 ),
156 ];
157 }
158
159 // check if an ad blocker is enabled
160 // Hidden, will be shown using js.
161 $nodes[] = [
162 'type' => 2,
163 'data' => [
164 'parent' => $health_parent_class,
165 'id' => 'advanced_ads_ad_health_adblocker_enabled',
166 'title' => __( 'Ad blocker enabled', 'advanced-ads' ),
167 'meta' => [
168 'class' => 'hidden advanced_ads_ad_health_warning',
169 'target' => '_blank',
170 ],
171 ],
172 ];
173
174 if ( $wp_the_query->is_singular() ) {
175 if ( $this->has_the_content_placements() ) {
176 $nodes[] = [
177 'type' => 2,
178 'data' => [
179 'parent' => $health_parent_class,
180 'id' => 'advanced_ads_ad_health_the_content_not_invoked',
181 'title' => __( '<em>the_content</em> filter does not exist', 'advanced-ads' ),
182 'href' => 'https://wpadvancedads.com/manual/ads-not-showing-up/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-content-filter-missing#the_content-filter-missing',
183 'meta' => [
184 'class' => 'hidden advanced_ads_ad_health_warning',
185 'target' => '_blank',
186 ],
187 ],
188 ];
189 }
190
191 if ( ! empty( $post->ID ) ) {
192 $ad_settings = get_post_meta( $post->ID, '_advads_ad_settings', true );
193
194 if ( ! empty( $ad_settings['disable_the_content'] ) ) {
195 $nodes[] = [
196 'type' => 1,
197 'data' => array_merge(
198 $health_node_common_data,
199 [
200 'id' => 'advanced_ads_ad_health_disabled_in_content',
201 'title' => __( 'Ads are disabled in the content of this page', 'advanced-ads' ),
202 'href' => get_edit_post_link( $post->ID ) . '#advads-ad-settings',
203 ]
204 ),
205 ];
206 }
207 } else {
208 $nodes[] = [
209 'type' => 1,
210 'data' => array_merge(
211 $health_node_common_data,
212 [
213 'id' => 'advanced_ads_ad_health_post_zero',
214 'title' => __( 'the current post ID is 0 ', 'advanced-ads' ),
215 'href' => 'https://wpadvancedads.com/manual/ad-health/#post-id-0',
216 ]
217 ),
218 ];
219
220 }
221 }
222
223 $disabled_reason = wp_advads()->frontend->get_disabled_reason();
224 $disabled_id = wp_advads()->frontend->get_disabled_id();
225 $settings_page = admin_url( 'admin.php?page=advanced-ads-settings' );
226
227 if ( 'page' === $disabled_reason && $disabled_id ) {
228 $nodes[] = [
229 'type' => 1,
230 'data' => array_merge(
231 $health_node_common_data,
232 [
233 'id' => 'advanced_ads_ad_health_disabled_on_page',
234 'title' => __( 'Ads are disabled on this page', 'advanced-ads' ),
235 'href' => get_edit_post_link( $disabled_id ) . '#advads-ad-settings',
236 ]
237 ),
238 ];
239 } elseif ( 'all' === $disabled_reason ) {
240 $nodes[] = [
241 'type' => 1,
242 'data' => array_merge(
243 $health_node_common_data,
244 [
245 'id' => 'advanced_ads_ad_health_no_all',
246 'title' => __( 'Ads are disabled on all pages', 'advanced-ads' ),
247 'href' => $settings_page,
248 ]
249 ),
250 ];
251 } elseif ( '404' === $disabled_reason ) {
252 $nodes[] = [
253 'type' => 1,
254 'data' => array_merge(
255 $health_node_common_data,
256 [
257 'id' => 'advanced_ads_ad_health_no_404',
258 'title' => __( 'Ads are disabled on 404 pages', 'advanced-ads' ),
259 'href' => $settings_page,
260 ]
261 ),
262 ];
263 } elseif ( 'archive' === $disabled_reason ) {
264 $nodes[] = [
265 'type' => 1,
266 'data' => array_merge(
267 $health_node_common_data,
268 [
269 'id' => 'advanced_ads_ad_health_no_archive',
270 'title' => __( 'Ads are disabled on non singular pages', 'advanced-ads' ),
271 'href' => $settings_page,
272 ]
273 ),
274 ];
275 } elseif ( 'user-role' === $disabled_reason ) {
276 global $wp_roles;
277 $role_names = [];
278
279 if ( isset( $this->options['hide-for-user-role'] ) ) {
280 $option_roles = array_flip( $this->options['hide-for-user-role'] );
281 $role_names = array_intersect_key( $wp_roles->get_names(), $option_roles );
282 }
283
284 $title = __( 'Ads are disabled for the user role(s)', 'advanced-ads' ) . ': ' . implode( ', ', $role_names );
285
286 $nodes[] = [
287 'type' => 1,
288 'data' => array_merge(
289 $health_node_common_data,
290 [
291 'id' => 'advanced_ads_ad_health_no_user_role',
292 'title' => $title,
293 'href' => $settings_page,
294 ]
295 ),
296 ];
297 } elseif ( 'ip-address' === $disabled_reason ) {
298 $user_ip = get_user_ip_address();
299 $nodes[] = [
300 'type' => 1,
301 'data' => array_merge(
302 $health_node_common_data,
303 [
304 'id' => 'advanced_ads_ad_health_disabled_ip_address',
305 'title' => __( 'Ads are disabled for this IP address', 'advanced-ads' ) . ': ' . $user_ip,
306 'href' => $settings_page,
307 ]
308 ),
309 ];
310 }
311
312 $nodes[] = [
313 'type' => 2,
314 'data' => [
315 'parent' => $health_parent_class,
316 'id' => 'advanced_ads_ad_health_has_http',
317 'title' => sprintf(
318 '%s %s',
319 __( 'Your website is using HTTPS, but the ad code contains HTTP and might not work.', 'advanced-ads' ),
320 /* translators: em tags */
321 sprintf( __( 'Ad IDs: %s', 'advanced-ads' ), '<em></em>' )
322 ),
323 'href' => 'https://wpadvancedads.com/manual/ad-health/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-https-ads#https-ads',
324 'meta' => [
325 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_has_http',
326 'target' => '_blank',
327 ],
328 ],
329 ];
330
331 $nodes[] = [
332 'type' => 2,
333 'data' => [
334 'parent' => $health_parent_class,
335 'id' => 'advanced_ads_ad_health_incorrect_head',
336 'title' => sprintf( __( 'Visible ads should not use the Header placement: %s', 'advanced-ads' ), '<i></i>' ),
337 'href' => 'https://wpadvancedads.com/manual/ad-health/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-visible-ad-in-header#header-ads',
338 'meta' => [
339 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_incorrect_head',
340 'target' => '_blank',
341 ],
342 ],
343 ];
344
345 // warn if an AdSense ad seems to be hidden.
346 if ( ! isset( $adsense_options['violation-warnings-disable'] ) ) {
347 $nodes[] = [
348 'type' => 2,
349 'data' => [
350 'parent' => $health_parent_class,
351 'id' => 'advanced_ads_ad_health_hidden_adsense',
352 'title' => sprintf(
353 '%s: %s. %s',
354 __( 'AdSense violation', 'advanced-ads' ),
355 __( 'Ad is hidden', 'advanced-ads' ),
356 /* translators: em tags */
357 sprintf( __( 'IDs: %s', 'advanced-ads' ), '<em></em>' )
358 ),
359 'href' => 'https://wpadvancedads.com/manual/ad-health/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-frontend-adsense-hidden#adsense-hidden',
360 'meta' => [
361 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_hidden_adsense',
362 'target' => '_blank',
363 ],
364 ],
365 ];
366 }
367
368 $nodes[] = [
369 'type' => 2,
370 'data' => [
371 'parent' => $health_parent_class,
372 'id' => 'advanced_ads_ad_health_floated_responsive_adsense',
373 /* translators: em tags */
374 'title' => sprintf( __( 'The following responsive AdSense ads are not showing up: %s', 'advanced-ads' ), '<em></em>' ),
375 'href' => 'https://wpadvancedads.com/manual/ad-health/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-adsense-responsive-not-showing#The_following_responsive_AdSense_ads_arenot_showing_up',
376 'meta' => [
377 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_floated_responsive_adsense',
378 'target' => '_blank',
379 ],
380 ],
381 ];
382
383 // warn if consent was not given.
384 $privacy = Advanced_Ads_Privacy::get_instance();
385 if ( 'not_needed' !== $privacy->get_state() ) {
386 $nodes[] = [
387 'type' => 2,
388 'data' => [
389 'parent' => $health_parent_class,
390 'id' => 'advanced_ads_ad_health_consent_missing',
391 'title' => __( 'Consent not given', 'advanced-ads' ),
392 'href' => admin_url( 'admin.php?page=advanced-ads-settings#top#privacy' ),
393 'meta' => [
394 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_consent_missing',
395 'target' => '_blank',
396 ],
397 ],
398 ];
399 }
400
401 $privacy_options = $privacy->options();
402 if ( empty( $privacy_options['enabled'] ) || 'iab_tcf_20' !== $privacy_options['consent-method'] ) {
403 $nodes[] = [
404 'type' => 2,
405 'data' => [
406 'parent' => $health_parent_class,
407 'id' => 'advanced_ads_ad_health_privacy_disabled',
408 'title' => __( 'Enable TCF integration', 'advanced-ads' ),
409 'href' => admin_url( 'admin.php?page=advanced-ads-settings#top#privacy' ),
410 'meta' => [
411 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_privacy_disabled',
412 'target' => '_blank',
413 ],
414 ],
415 ];
416 }
417
418 $nodes[] = [
419 'type' => 3,
420 'data' => [
421 'parent' => $health_parent_class,
422 'id' => 'advanced_ads_ad_health_gam_debug',
423 'title' => __( 'Debug Google Ad Manager', 'advanced-ads' ),
424 'href' => esc_url( add_query_arg( 'google_force_console', '1' ) ),
425 'meta' => [
426 'class' => 'hidden advanced_ads_ad_health_gam_debug_link',
427 ],
428 ],
429 ];
430
431 // link to highlight ads and jump from one ad to the next.
432 $nodes[] = [
433 'type' => 3,
434 'amp' => false,
435 'data' => [
436 'parent' => $health_parent_class,
437 'id' => 'advanced_ads_ad_health_highlight_ads',
438 'title' => sprintf(
439 '<span class="link">%s</span> %s',
440 __( 'highlight ads', 'advanced-ads' ),
441 '<span class="arrows">
442 <i class = "dashicons dashicons-arrow-up-alt previous"></i>
443 <i class = "dashicons dashicons-arrow-down-alt next"></i>
444 </span>'
445 ),
446 'meta' => [
447 'class' => 'advanced_ads_ad_health_highlight_ads',
448 ],
449 ],
450 ];
451
452 /**
453 * Add new node.
454 *
455 * @param array $node An array that contains:
456 * 'type' => 1 - warning, 2 - hidden warning that will be shown using JS, 3 - info message
457 * 'data': @see WP_Admin_Bar->add_node
458 * @param object $wp_admin_bar
459 */
460 $nodes = apply_filters( 'advanced-ads-ad-health-nodes', $nodes );
461
462 usort( $nodes, [ $this, 'sort_nodes' ] );
463
464 // load number of already detected notices.
465 $notices = Advanced_Ads_Ad_Health_Notices::get_number_of_notices();
466
467 if ( ! Conditional::is_amp() ) {
468 $warnings = 0; // Will be updated using JS.
469 } else {
470 $warnings = $this->count_visible_warnings( $nodes, [ 1 ] );
471 }
472
473 $issues = $warnings;
474
475 $this->add_header_nodes( $wp_admin_bar, $issues, $notices );
476
477 foreach ( $nodes as $node ) {
478 if ( isset( $node['data'] ) ) {
479 $wp_admin_bar->add_node( $node['data'] );
480 }
481 }
482
483 $this->add_footer_nodes( $wp_admin_bar, $issues );
484 }
485
486
487 /**
488 * Add classes to the `body` tag.
489 *
490 * @param string[] $classes Array of existing class names.
491 * @return string[] $classes Array of existing and new class names.
492 */
493 public function body_class( $classes ) {
494 global $post;
495
496 $aa_classes = [
497 'aa-prefix-' . wp_advads()->get_frontend_prefix(),
498 ];
499
500 $disabled_reason = wp_advads()->frontend->get_disabled_reason();
501 if ( $disabled_reason ) {
502 $aa_classes[] = 'aa-disabled-' . esc_attr( $disabled_reason );
503 }
504
505 if ( ! empty( $post->ID ) ) {
506 $ad_settings = get_post_meta( $post->ID, '_advads_ad_settings', true );
507 if ( ! empty( $ad_settings['disable_the_content'] ) ) {
508 $aa_classes[] = 'aa-disabled-content';
509 }
510 }
511
512 // hide-ads-from-bots option is enabled.
513 if ( ! empty( $this->options['block-bots'] ) ) {
514 $aa_classes[] = 'aa-disabled-bots';
515 }
516
517 $aa_classes = apply_filters( 'advanced-ads-body-classes', $aa_classes );
518
519 if ( ! is_array( $classes ) ) {
520 $classes = [];
521 }
522 if ( ! is_array( $aa_classes ) ) {
523 $aa_classes = [];
524 }
525
526 return array_merge( $classes, $aa_classes );
527 }
528
529
530
531
532 /**
533 * Count visible notices and warnings.
534 *
535 * @param array $nodes Nodes to add.
536 * @param array $types Warning types.
537 */
538 private function count_visible_warnings( $nodes, $types = [] ) {
539 $warnings = 0;
540 foreach ( $nodes as $node ) {
541 if ( ! isset( $node['type'] ) || ! isset( $node['data'] ) ) {
542 continue;
543 }
544 if ( in_array( $node['type'], $types ) ) {
545 ++$warnings;
546 }
547 }
548 return $warnings;
549 }
550
551 /**
552 * Add header nodes.
553 *
554 * @param object $wp_admin_bar WP_Admin_Bar object.
555 * @param int $issues Number of all issues.
556 * @param int $notices Number of notices.
557 */
558 private function add_header_nodes( $wp_admin_bar, $issues, $notices ) {
559 $wp_admin_bar->add_node(
560 [
561 'id' => 'advanced_ads_ad_health',
562 'title' => __( 'Ad Health', 'advanced-ads' ) . '&nbsp;<span class="advanced-ads-issue-counter">' . $issues . '</span>',
563 'parent' => false,
564 'href' => admin_url( 'admin.php?page=advanced-ads' ),
565 'meta' => [
566 'class' => $issues ? 'advads-adminbar-is-warnings' : '',
567 ],
568 ]
569 );
570
571 // show that there are backend notices.
572 if ( $notices ) {
573 $wp_admin_bar->add_node(
574 [
575 'parent' => 'advanced_ads_ad_health',
576 'id' => 'advanced_ads_ad_health_more',
577 /* translators: number of notices */
578 'title' => sprintf( __( 'Show %d more notifications', 'advanced-ads' ), absint( $notices ) ),
579 'href' => admin_url( 'admin.php?page=advanced-ads' ),
580 ]
581 );
582 }
583 }
584
585 /**
586 * Add footer nodes.
587 *
588 * @param obj $wp_admin_bar WP_Admin_Bar object.
589 * @param int $issues Number of all issues.
590 */
591 private function add_footer_nodes( $wp_admin_bar, $issues ) {
592 if ( ! $issues ) {
593 $wp_admin_bar->add_node(
594 [
595 'parent' => 'advanced_ads_ad_health',
596 'id' => 'advanced_ads_ad_health_fine',
597 'title' => __( 'Everything is fine', 'advanced-ads' ),
598 'href' => false,
599 'meta' => [
600 'target' => '_blank',
601 ],
602 ]
603 );
604 }
605
606 $wp_admin_bar->add_node(
607 [
608 'parent' => 'advanced_ads_ad_health',
609 'id' => 'advanced_ads_ad_health_support',
610 'title' => __( 'Get help', 'advanced-ads' ),
611 'href' => Data::support_url( '?utm_source=advanced-ads&utm_medium=link&utm_campaign=health-support' ),
612 'meta' => [
613 'target' => '_blank',
614 ],
615 ]
616 );
617 }
618
619 /**
620 * Filter out nodes intended to AMP pages only.
621 *
622 * @param array $nodes Nodes to add.
623 * @return array $nodes Nodes to add.
624 */
625 private function filter_nodes( $nodes ) {
626 return $nodes;
627 }
628
629 /**
630 * Sort nodes.
631 *
632 * @param array $a The first node to compare.
633 * @param array $b The second node to compare.
634 * @return int Returns -1 if $a is less than $b, 1 if $a is greater than $b, and 0 if they are equal.
635 */
636 public function sort_nodes( $a, $b ) {
637 if ( ! isset( $a['type'] ) || ! isset( $b['type'] ) ) {
638 return 0;
639 }
640 if ( $a['type'] === $b['type'] ) {
641 return 0;
642 }
643 return ( $a['type'] < $b['type'] ) ? -1 : 1;
644 }
645
646 /**
647 * Set variable to 'true' when 'the_content' filter is invoked.
648 *
649 * @param string $content The content to be filtered.
650 * @return string $content The filtered content.
651 */
652 public function set_did_the_content( $content ) {
653 if ( ! $this->did_the_content ) {
654 $this->did_the_content = true;
655 }
656
657 if ( Advanced_Ads::get_instance()->has_many_the_content() ) {
658 $this->has_many_the_content = true;
659 }
660 return $content;
661 }
662
663 /**
664 * Check conditions and display warning.
665 * Conditions:
666 * AdBlocker enabled,
667 * jQuery is included in header
668 * AdSense Quick Start ads are running
669 */
670 public function footer_checks() {
671 ob_start();
672 ?><!-- Advanced Ads: <?php esc_html_e( 'the following code is used for automatic error detection and only visible to admins', 'advanced-ads' ); ?>-->
673 <style>#wp-admin-bar-advanced_ads_ad_health .hidden { display: none; }
674 #wp-admin-bar-advanced_ads_ad_health-default a:after { content: "\25BA"; margin-left: .5em; font-size: smaller; }
675 #wp-admin-bar-advanced_ads_ad_health-default .advanced_ads_ad_health_highlight_ads div:before { content: "\f177"; margin-right: .2em; line-height: 1em; padding: 0.2em 0 0; color: inherit; }
676 #wp-admin-bar-advanced_ads_ad_health-default .advanced_ads_ad_health_highlight_ads div:hover { color: #00b9eb; cursor: pointer; }
677 #wpadminbar .advanced-ads-issue-counter { background-color: #d54e21; display: none; padding: 1px 7px 1px 6px!important; border-radius: 50%; color: #fff; }
678 #wpadminbar .advads-adminbar-is-warnings .advanced-ads-issue-counter { display: inline; }
679 .advanced-ads-highlight-ads { outline:4px solid #0474A2 !important; }
680 #wp-admin-bar-advanced_ads_ad_health .advanced_ads_ad_health_highlight_ads .arrows {display: none;}
681 #wp-admin-bar-advanced_ads_ad_health .arrows .dashicons {font-family: 'dashicons';}
682 #wp-admin-bar-advanced_ads_ad_health.hover .advanced_ads_ad_health_highlight_ads.active .arrows {display: inline-block;}
683 </style>
684 <?php
685 // phpcs:ignore
686 echo ob_get_clean();
687
688 if ( Conditional::is_amp() ) {
689 return;
690 }
691
692 $adsense_options = Advanced_Ads_AdSense_Data::get_instance()->get_options();
693 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.EnqueuedResources.NonEnqueuedScript
694 ob_start();
695 ?>
696 <script type="text/javascript" src="<?php echo ADVADS_BASE_URL . 'admin/assets/js/advertisement.js'; ?>"></script>
697 <script>
698 var advanced_ads_frontend_checks = {
699 showCount: function() {
700 try {
701 // Count only warnings that have the 'advanced_ads_ad_health_warning' class.
702 var warning_count = document.querySelectorAll( '.advanced_ads_ad_health_warning:not(.hidden)' ).length;
703 var fine_item = document.getElementById( 'wp-admin-bar-advanced_ads_ad_health_fine' );
704 } catch ( e ) { return; }
705
706 var header = document.querySelector( '#wp-admin-bar-advanced_ads_ad_health > a' );
707 if ( warning_count ) {
708 if ( fine_item ) {
709 // Hide 'fine' item.
710 fine_item.className += ' hidden';
711 }
712
713 if ( header ) {
714 header.innerHTML = header.innerHTML.replace(/<span class="advanced-ads-issue-counter">\d*<\/span>/, '') + '<span class="advanced-ads-issue-counter">' + warning_count + '</span>';
715 // add class
716 header.className += ' advads-adminbar-is-warnings';
717 }
718 } else {
719 // Show 'fine' item.
720 if ( fine_item ) {
721 fine_item.classList.remove('hidden');
722 }
723
724 // Remove counter.
725 if ( header ) {
726 header.innerHTML = header.innerHTML.replace(/<span class="advanced-ads-issue-counter">\d*<\/span>/, '');
727 header.classList.remove('advads-adminbar-is-warnings');
728 }
729 }
730 },
731
732 array_unique: function( array ) {
733 var r= [];
734 for ( var i = 0; i < array.length; i++ ) {
735 if ( r.indexOf( array[ i ] ) === -1 ) {
736 r.push( array[ i ] );
737 }
738 }
739 return r;
740 },
741
742 /**
743 * Add item to Ad Health node.
744 *
745 * @param string selector Selector of the node.
746 * @param string/array item item(s) to add.
747 */
748 add_item_to_node: function( selector, item ) {
749 if ( typeof item === 'string' ) {
750 item = item.split();
751 }
752 var selector = document.querySelector( selector );
753 if ( selector ) {
754 selector.className = selector.className.replace( 'hidden', '' );
755 selector.innerHTML = selector.innerHTML.replace( /(<i>)(.*?)(<\/i>)/, function( match, p1, p2, p3 ) {
756 p2 = ( p2 ) ? p2.split( ', ' ) : [];
757 p2 = p2.concat( item );
758 p2 = advanced_ads_frontend_checks.array_unique( p2 );
759 return p1 + p2.join( ', ' ) + p3;
760 } );
761 advanced_ads_frontend_checks.showCount();
762 }
763 },
764
765 /**
766 * Add item to Ad Health notices in the backend
767 *
768 * @param key of the notice
769 * @param attr
770 * @returns {undefined}
771 */
772 add_item_to_notices: function( key, attr = '' ) {
773 var cookie = advads.get_cookie( 'advanced_ads_ad_health_notices' );
774 if ( cookie ){
775 advads_cookie_notices = JSON.parse( cookie );
776 } else {
777 advads_cookie_notices = new Array();
778 }
779 // stop if notice was added less than 1 hour ago
780 if ( 0 <= advads_cookie_notices.indexOf( key ) ){
781 return;
782 }
783 var query = {
784 action: 'advads-ad-health-notice-push',
785 key: key,
786 attr: attr,
787 nonce: '<?php echo wp_create_nonce('advanced-ads-ad-health-ajax-nonce'); ?>'
788 };
789 // send query
790 // update notices and cookie
791 jQuery.post( advads_frontend_checks.ajax_url, query, function (r) {
792 advads_cookie_notices.push( key );
793 var notices_str = JSON.stringify( advads_cookie_notices );
794 advads.set_cookie_sec( 'advanced_ads_ad_health_notices', notices_str, 3600 ); // 1 hour
795 });
796 },
797
798 /**
799 * Search for hidden AdSense.
800 *
801 * @param string context Context for search.
802 */
803 advads_highlight_hidden_adsense: function( context ) {
804 if ( ! context ) {
805 context = 'html'
806 }
807 if ( window.jQuery ) {
808 var responsive_zero_width = [];
809 jQuery( 'ins.adsbygoogle', context ).each( function() {
810 // Zero width, perhaps because a parent container is floated
811 if ( jQuery( this ).attr( 'data-ad-format' ) && 0 === jQuery( this ).width() ) {
812 responsive_zero_width.push( this.dataset.adSlot );
813 }
814 });
815 if ( responsive_zero_width.length ) {
816 advanced_ads_frontend_checks.add_item_to_node( '.advanced_ads_ad_health_floated_responsive_adsense', responsive_zero_width );
817 }
818 }
819 }
820 };
821
822 (function(d, w) {
823 // highlight link as global
824 var highlightLink = d.getElementById( 'wp-admin-bar-advanced_ads_ad_health_highlight_ads' );
825 if ( ! highlightLink ) return;
826 var adWrappers;
827 // update ad count in health tool admin bar
828 updateAdsCount(d);
829 var addEvent = function( obj, type, fn ) {
830 if ( obj.addEventListener )
831 obj.addEventListener( type, fn, false );
832 else if ( obj.attachEvent )
833 obj.attachEvent( 'on' + type, function() { return fn.call( obj, window.event ); } );
834 };
835
836 function getAdWrappers() {
837 return document.querySelectorAll(".<?php echo wp_advads()->get_frontend_prefix(); ?>highlight-wrapper, .google-auto-placed");
838 }
839
840 // highlight ads that use Advanced Ads placements or AdSense Auto ads
841 function highlightAds() {
842 /**
843 * Selectors:
844 * Placement container: ".<?php echo wp_advads()->get_frontend_prefix(); ?>highlight-wrapper, .google-auto-placed"
845 * AdSense Auto ads: 'google-auto-placed'
846 */
847 try {
848 <?php //phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped ?>
849 adWrappers = getAdWrappers();
850 <?php //phpcs:enable ?>
851 } catch ( e ) { return; }
852 for ( i = 0; i < adWrappers.length; i++ ) {
853 // Check highlighted ads active
854 adWrappers[i].classList.toggle('advanced-ads-highlight-ads');
855 // show title only when highlight ads active.
856 if ( adWrappers[i].classList.contains('advanced-ads-highlight-ads') ) {
857 adWrappers[i].title = adWrappers[i].getAttribute('data-title');
858 } else {
859 adWrappers[i].title = '';
860 }
861 }
862
863 // add or remove active class from highlight link
864 highlightLink.classList.toggle('active');
865 }
866
867 function scrollToHighlightedAd() {
868 try {
869 // If no ad wrappers are found, exit the function
870 if (adWrappers.length === 0) return;
871
872 // Initialize or update the index of the currently highlighted ad
873 if (typeof window.current_highlighted_ad === "undefined") {
874 window.current_highlighted_ad = 0;
875 } else if (this.classList.contains('next') && adWrappers.length - 1 > window.current_highlighted_ad) {
876 window.current_highlighted_ad++;
877 } else if (this.classList.contains('previous') && window.current_highlighted_ad > 0) {
878 window.current_highlighted_ad--;
879 }
880
881 // Get the offsetTop of the currently highlighted ad's wrapper
882 const scrollDiv = document.getElementById(adWrappers[window.current_highlighted_ad]?.id)?.offsetTop;
883
884 // If scrollDiv is defined, scroll to the ad wrapper's position
885 if (scrollDiv !== undefined) {
886 window.scrollTo({ top: scrollDiv, behavior: 'smooth' });
887 }
888 } catch (e) {
889 // Handle any errors that might occur
890 }
891 }
892
893 advanced_ads_ready( function() {
894 var adblock_item = d.getElementById( 'wp-admin-bar-advanced_ads_ad_health_adblocker_enabled' );
895
896 // handle click on the highlightAds link
897 var link = highlightLink.querySelector('.link');
898 addEvent( link, 'click', highlightAds );
899
900 // arrows click handler
901 var arrows = highlightLink.querySelector('.arrows').querySelectorAll('.dashicons');
902 for ( let i = 0; i < arrows.length; i++ ) {
903 arrows[i].addEventListener("click", scrollToHighlightedAd);
904 }
905
906 if ( adblock_item && typeof advanced_ads_adblocker_test === 'undefined' ) {
907 // show hidden item
908 adblock_item.className = adblock_item.className.replace( /hidden/, '' );
909 }
910
911 <?php if ( ! $this->did_the_content ) : ?>
912 var the_content_item = d.getElementById( 'wp-admin-bar-advanced_ads_ad_health_the_content_not_invoked' );
913 if ( the_content_item ) {
914 the_content_item.className = the_content_item.className.replace( /hidden/, '' );
915 }
916 <?php endif; ?>
917
918 advanced_ads_frontend_checks.showCount();
919 });
920
921 <?php if ( ! isset( $adsense_options['violation-warnings-disable'] ) ) : ?>
922 // show warning if AdSense ad is hidden
923 // show hint if AdSense Auto ads are enabled
924 setTimeout( function(){
925 advanced_ads_ready( advanced_ads_frontend_checks.advads_highlight_hidden_adsense );
926 }, 2000 );
927
928 // highlight AdSense Auto Ads ads 3 seconds after site loaded
929 setTimeout( function(){
930 advanced_ads_ready( advads_highlight_adsense_autoads );
931 }, 3000 );
932 function advads_highlight_adsense_autoads(){
933 if ( ! window.jQuery ) {
934 window.console && window.console.log( 'Advanced Ads: jQuery not found. Some Ad Health warnings will not be displayed.' );
935 return;
936 }
937 var autoads_ads = document.querySelectorAll('.google-auto-placed');
938 // show Auto Ads warning in Ad Health bar if relevant
939 if ( autoads_ads.length ){
940 var advads_autoads_link = document.querySelector( '#wp-admin-bar-advanced_ads_autoads_displayed.hidden' );
941 if ( advads_autoads_link ) {
942 advads_autoads_link.className = advads_autoads_link.className.replace( 'hidden', '' );
943 }
944 advanced_ads_frontend_checks.showCount();
945 }
946 }
947 <?php
948 endif;
949 /**
950 * Code to check if current user gave consent to show ads
951 */
952 $privacy = Advanced_Ads_Privacy::get_instance();
953 if ( 'not_needed' !== $privacy->get_state() ) :
954 ?>
955 document.addEventListener('advanced_ads_privacy', function (event) {
956 var advads_consent_link = document.querySelector('#wp-admin-bar-advanced_ads_ad_health_consent_missing');
957
958 if (!advads_consent_link) {
959 return;
960 }
961
962 if (event.detail.state !== 'accepted' && event.detail.state !== 'not_needed') {
963 advads_consent_link.classList.remove('hidden');
964 } else {
965 advads_consent_link.classList.add('hidden');
966 }
967
968 advanced_ads_frontend_checks.showCount();
969 });
970 <?php
971 endif;
972 $privacy_options = $privacy->options();
973 if (
974 ( empty( $privacy_options['enabled'] ) || 'iab_tcf_20' !== $privacy_options['consent-method'] )
975 && (bool) apply_filters( 'advanced-ads-ad-health-show-tcf-notice', true )
976 ) :
977 ?>
978 var count = 0,
979 tcfapiInterval = setInterval(function () {
980 if (++count === 181) {
981 clearInterval(tcfapiInterval);
982 }
983 if (typeof window.__tcfapi === 'undefined') {
984 return;
985 }
986 clearInterval(tcfapiInterval);
987
988 var advadsPrivacyLink = document.querySelector('#wp-admin-bar-advanced_ads_ad_health_privacy_disabled');
989
990 if (!advadsPrivacyLink) {
991 return;
992 }
993
994 advadsPrivacyLink.classList.remove('hidden');
995
996 advanced_ads_frontend_checks.showCount();
997 }, 333);
998 <?php endif; ?>
999 /**
1000 * show Google Ad Manager debug link in Ad Health
1001 *
1002 * look for container with ID starting with `div-gpt-ad-`
1003 * or `gpt-ad-` as used by our own Google Ad Manager integration
1004 * we don’t look for the gpt header script because that is also used by other services that are based on Google Publisher Tags
1005 */
1006 function advadsGamShowDebugLink(){
1007 var advadsGamDebugLink = document.querySelector( '.advanced_ads_ad_health_gam_debug_link.hidden' );
1008
1009 if ( ! advadsGamDebugLink ){
1010 return;
1011 }
1012
1013 // Check for the `googletag` variable created in the page header or directly in the body alongside the ad slot definition.
1014 if ( typeof window.googletag !== 'undefined' ) {
1015 advadsGamDebugLink.className = advadsGamDebugLink.className.replace( 'hidden', '' );
1016 }
1017 }
1018 // look for Google Ad Manager tags with a delay of 2 seconds
1019 setTimeout( function(){
1020 advanced_ads_ready( advadsGamShowDebugLink );
1021 }, 2000 );
1022
1023 // Function to count visible ads with unique group IDs
1024 function getAdsCount(){
1025 // Get all elements with the specified class name
1026 const adWrappers = getAdWrappers();
1027 // Initialize a count for visible ads
1028 let ads_count = 0;
1029 // Loop through each ad wrapper element
1030 for ( let i = 0; i < adWrappers.length; i++ ) {
1031 // Check if the group ID is either null or not included in the array of seen group IDs.
1032 if ( adWrappers[i].offsetHeight > 0 ) {
1033 // Increment the ad count and add the group ID to the list.
1034 ads_count++;
1035 }
1036 }
1037 // Return the total count of eligible ads
1038 return ads_count;
1039 }
1040
1041 function updateAdsCount(d){
1042 var highlightLink = d.getElementById( 'wp-admin-bar-advanced_ads_ad_health_highlight_ads' );
1043 if ( ! highlightLink ) return;
1044 // update ad count in health tool admin bar
1045 highlightLink.querySelector('.link').innerHTML += ' (<span class="highlighted_ads_count">' + getAdsCount() + '</span>) ';
1046
1047 // If any ads load by ajax its update count after ajax load
1048 var origOpen = XMLHttpRequest.prototype.open;
1049 XMLHttpRequest.prototype.open = function() {
1050 this.addEventListener('load', function() {
1051 if ( this.status === 200 ) {
1052 highlightLink.querySelector('.highlighted_ads_count').innerHTML = getAdsCount();
1053 }
1054 });
1055 origOpen.apply(this, arguments);
1056 };
1057 }
1058 })(document, window);
1059 </script>
1060 <?php
1061 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.EnqueuedResources.NonEnqueuedScript
1062 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1063 echo Advanced_Ads_Utils::get_inline_asset( ob_get_clean() );
1064 }
1065
1066 /**
1067 * Inject JS after ad content.
1068 *
1069 * @param string $content Ad content.
1070 * @param Ad $ad Ad instance.
1071 *
1072 * @return string $content ad content
1073 */
1074 public function after_ad_output( $content, Ad $ad ) {
1075 if ( null === $ad->get_prop( 'frontend-check' ) ) {
1076 return $content;
1077 }
1078
1079 if ( Conditional::is_amp() ) {
1080 return $content;
1081 }
1082
1083 if ( Validation::is_ad_https( $ad ) ) {
1084 ob_start();
1085 ?>
1086 <script>advanced_ads_ready( function() {
1087 var ad_id = '<?php echo esc_html( $ad->get_id() ); ?>';
1088 advanced_ads_frontend_checks.add_item_to_node( '.advanced_ads_ad_health_has_http', ad_id );
1089 advanced_ads_frontend_checks.add_item_to_notices( 'ad_has_http', { append_key: ad_id, ad_id: ad_id } );
1090 });</script>
1091 <?php
1092 $content .= Advanced_Ads_Utils::get_inline_asset( ob_get_clean() );
1093 }
1094
1095 if ( ! Advanced_Ads_Frontend_Checks::can_use_head_placement( $content, $ad ) ) {
1096 ob_start();
1097 ?>
1098 <script>advanced_ads_ready( function() {
1099 var ad_id = '<?php echo esc_html( $ad->get_id() ); ?>';
1100 advanced_ads_frontend_checks.add_item_to_node( '.advanced_ads_ad_health_incorrect_head', ad_id );
1101 advanced_ads_frontend_checks.add_item_to_notices( 'ad_with_output_in_head', { append_key: ad_id, ad_id: ad_id } );
1102 });</script>
1103 <?php
1104 $content .= Advanced_Ads_Utils::get_inline_asset( ob_get_clean() );
1105 }
1106
1107 $adsense_options = Advanced_Ads_AdSense_Data::get_instance()->get_options();
1108 if (
1109 $ad->is_type( 'adsense' ) &&
1110 ! empty( $ad->get_prop( 'cache_busting_elementid' ) ) &&
1111 ! isset( $adsense_options['violation-warnings-disable'] )
1112 ) {
1113 ob_start();
1114 ?>
1115 <script>advanced_ads_ready( function() {
1116 var ad_id = '<?php echo esc_html( $ad->get_id() ); ?>';
1117 var wrapper = '#<?php echo esc_html( $ad->get_prop( 'cache_busting_elementid' ) ); ?>';
1118 advanced_ads_frontend_checks.advads_highlight_hidden_adsense( wrapper );
1119 });</script>
1120 <?php
1121 $content .= Advanced_Ads_Utils::get_inline_asset( ob_get_clean() );
1122 }
1123
1124 return $content;
1125 }
1126
1127
1128 /**
1129 * Check if the 'Header Code' placement can be used to delived the ad.
1130 *
1131 * @param string $content Ad content.
1132 * @param Ad $ad Ad.
1133 *
1134 * @return bool
1135 */
1136 public static function can_use_head_placement( $content, Ad $ad ) {
1137
1138 if ( ! $ad->is_head_placement() ) {
1139 return true;
1140 }
1141
1142 // strip linebreaks, because, a line break after a comment is identified as a text node.
1143 $content = preg_replace( "/\r|\n/", '', $content );
1144
1145 if ( ! $dom = self::get_ad_dom( $content ) ) {
1146 return true;
1147 }
1148
1149 $body = $dom->getElementsByTagName( 'body' )->item( 0 );
1150
1151 // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase, WordPress.PHP.StrictInArray.MissingTrueStrict
1152 $count = $body->childNodes->length;
1153 for ( $i = 0; $i < $count; $i++ ) {
1154 $node = $body->childNodes->item( $i );
1155
1156 if ( XML_TEXT_NODE === $node->nodeType ) {
1157 return false;
1158 }
1159
1160 if ( XML_ELEMENT_NODE === $node->nodeType
1161 && ! in_array( $node->nodeName, [ 'meta', 'link', 'title', 'style', 'script', 'noscript', 'base' ] ) ) {
1162 return false;
1163 }
1164 }
1165 // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase, WordPress.PHP.StrictInArray.MissingTrueStrict
1166 return true;
1167 }
1168
1169 /**
1170 * Convert ad content to a DOMDocument.
1171 *
1172 * @param string $content The ad content.
1173 * @return DOMDocument|false
1174 */
1175 private static function get_ad_dom( $content ) {
1176 if ( ! extension_loaded( 'dom' ) ) {
1177 return false;
1178 }
1179 $libxml_previous_state = libxml_use_internal_errors( true );
1180 $dom = new DOMDocument();
1181 $result = $dom->loadHTML( '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $content . '</body></html>' );
1182
1183 libxml_clear_errors();
1184 libxml_use_internal_errors( $libxml_previous_state );
1185
1186 if ( ! $result ) {
1187 return false;
1188 }
1189
1190 return $dom;
1191 }
1192
1193 /**
1194 * Get published placements once per request for frontend checks.
1195 *
1196 * @return array
1197 */
1198 private function get_frontend_placements() {
1199 if ( null === $this->frontend_placements ) {
1200 $this->frontend_placements = wp_advads_get_published_placements();
1201 }
1202
1203 return $this->frontend_placements;
1204 }
1205
1206 /**
1207 * Check if at least one placement uses `the_content`.
1208 *
1209 * @return bool True/False.
1210 */
1211 private function has_the_content_placements() {
1212 $placements = $this->get_frontend_placements();
1213
1214 // Find a placement that depends on 'the_content' filter.
1215 foreach ( $placements as $placement ) {
1216 if ( $placement->get_type_object()->get_options()['uses_the_content'] ) {
1217 return true;
1218 }
1219 }
1220
1221 return false;
1222 }
1223
1224 /**
1225 * Check if atleast one placement uses `adblocker item`.
1226 *
1227 * @return bool True/False.
1228 */
1229 private function has_adblocker_placements() {
1230 $placements = $this->get_frontend_placements();
1231 foreach ( $placements as $placement ) {
1232 if ( ! empty( $placement->get_prop( 'item_adblocker' ) ) ) {
1233 return true;
1234 }
1235 }
1236
1237 return false;
1238 }
1239
1240 /**
1241 * Check if atleast one ad uses adblocker visitor condition.
1242 *
1243 * @return bool True/False.
1244 */
1245 public function has_adblocker_visitor_condition() {
1246 $placements = $this->get_frontend_placements();
1247
1248 foreach ( $placements as $placement ) {
1249 $item = $placement->get_item();
1250 if ( empty( $item ) ) {
1251 continue;
1252 }
1253
1254 $ads = [];
1255
1256 if ( 'ad' === $placement->get_item_type() ) {
1257 $ads[] = $placement->get_item_object();
1258 } elseif ( 'group' === $placement->get_item_type() ) {
1259 $item_object = $placement->get_item_object();
1260 $ads = array_merge(
1261 $ads,
1262 $item_object ? $item_object->get_ads() : []
1263 );
1264 }
1265
1266 foreach ( $ads as $ad ) {
1267 if ( ! $ad ) {
1268 continue;
1269 }
1270 $options = $ad->get_visitor_conditions() ?? [];
1271 foreach ( $options as $option ) {
1272 if ( 'adblocker' === $option['type'] ) {
1273 return true;
1274 }
1275 }
1276 }
1277 }
1278
1279 return false;
1280 }
1281 }
1282