options = Advanced_Ads::get_instance()->options(); } /** * Ad Health init. */ public function init() { if ( ! is_admin() && is_admin_bar_showing() && Conditional::user_can( 'advanced_ads_edit_ads' ) && Advanced_Ads_Ad_Health_Notices::notices_enabled() ) { add_action( 'admin_bar_menu', [ $this, 'add_admin_bar_menu' ], 1000 ); add_filter( 'the_content', [ $this, 'set_did_the_content' ] ); add_action( 'wp_footer', [ $this, 'footer_checks' ], -101 ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_filter( 'advanced-ads-ad-select-args', [ $this, 'ad_select_args_callback' ] ); add_filter( 'advanced-ads-ad-output', [ $this, 'after_ad_output' ], 10, 2 ); } if ( Advanced_Ads_Ad_Health_Notices::notices_enabled() ) { add_action( 'body_class', [ $this, 'body_class' ] ); } if ( ( $this->has_adblocker_placements() || $this->has_adblocker_visitor_condition() ) && ! wp_advads()->registry->is_script( 'find-adblocker', 'enqueued' ) ) { wp_advads()->registry->enqueue_script( 'find-adblocker' ); } } /** * Notify ads loaded with AJAX. * * @param array $args ad arguments. * @return array $args */ public function ad_select_args_callback( $args ) { $args['frontend-check'] = true; return $args; } /** * Enqueue scripts * needs to add ajaxurl in case no other plugin is doing that */ public function enqueue_scripts() { if ( Conditional::is_amp() ) { return; } // we don’t have our own script, so we attach this information to jquery. wp_localize_script( 'jquery', 'advads_frontend_checks', [ 'ajax_url' => admin_url( 'admin-ajax.php' ) ] ); } /** * List current ad situation on the page in the admin-bar. * * @param object $wp_admin_bar WP_Admin_Bar. */ public function add_admin_bar_menu( $wp_admin_bar ) { global $wp_the_query, $post, $wp_scripts; $options = Advanced_Ads::get_instance()->options(); // load AdSense related options. $adsense_options = Advanced_Ads_AdSense_Data::get_instance()->get_options(); // common data used in nodes[]. $health_parent_class = 'advanced_ads_ad_health'; $health_node_common_data = [ 'parent' => $health_parent_class, 'meta' => [ 'class' => 'advanced_ads_ad_health_warning', 'target' => '_blank', ], ]; // check if AdSense loads Auto Ads ads // Hidden, will be shown using js. if ( ! isset( $adsense_options['violation-warnings-disable'] ) ) { $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_autoads_displayed', 'title' => __( 'Random AdSense ads', 'advanced-ads' ), 'href' => 'https://wpadvancedads.com/adsense-in-random-positions-auto-ads/?utm_source=advanced-ads&utm_medium=link&utm_campaign=frontend-autoads-ads', 'meta' => [ 'class' => 'hidden', 'target' => '_blank', ], ], ]; } // check if current user was identified as a bot. if ( Conditional::is_ua_bot() ) { $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_user_is_bot', 'title' => __( 'You look like a bot', 'advanced-ads' ), 'href' => 'https://wpadvancedads.com/manual/ad-health/#look-like-bot', ] ), ]; } // check if an ad blocker is enabled // Hidden, will be shown using js. $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_adblocker_enabled', 'title' => __( 'Ad blocker enabled', 'advanced-ads' ), 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning', 'target' => '_blank', ], ], ]; if ( $wp_the_query->is_singular() ) { if ( $this->has_the_content_placements() ) { $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_the_content_not_invoked', 'title' => __( 'the_content filter does not exist', 'advanced-ads' ), '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', 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning', 'target' => '_blank', ], ], ]; } if ( ! empty( $post->ID ) ) { $ad_settings = get_post_meta( $post->ID, '_advads_ad_settings', true ); if ( ! empty( $ad_settings['disable_the_content'] ) ) { $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_disabled_in_content', 'title' => __( 'Ads are disabled in the content of this page', 'advanced-ads' ), 'href' => get_edit_post_link( $post->ID ) . '#advads-ad-settings', ] ), ]; } } else { $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_post_zero', 'title' => __( 'the current post ID is 0 ', 'advanced-ads' ), 'href' => 'https://wpadvancedads.com/manual/ad-health/#post-id-0', ] ), ]; } } $disabled_reason = wp_advads()->frontend->get_disabled_reason(); $disabled_id = wp_advads()->frontend->get_disabled_id(); $settings_page = admin_url( 'admin.php?page=advanced-ads-settings' ); if ( 'page' === $disabled_reason && $disabled_id ) { $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_disabled_on_page', 'title' => __( 'Ads are disabled on this page', 'advanced-ads' ), 'href' => get_edit_post_link( $disabled_id ) . '#advads-ad-settings', ] ), ]; } elseif ( 'all' === $disabled_reason ) { $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_no_all', 'title' => __( 'Ads are disabled on all pages', 'advanced-ads' ), 'href' => $settings_page, ] ), ]; } elseif ( '404' === $disabled_reason ) { $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_no_404', 'title' => __( 'Ads are disabled on 404 pages', 'advanced-ads' ), 'href' => $settings_page, ] ), ]; } elseif ( 'archive' === $disabled_reason ) { $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_no_archive', 'title' => __( 'Ads are disabled on non singular pages', 'advanced-ads' ), 'href' => $settings_page, ] ), ]; } elseif ( 'user-role' === $disabled_reason ) { global $wp_roles; $role_names = []; if ( isset( $this->options['hide-for-user-role'] ) ) { $option_roles = array_flip( $this->options['hide-for-user-role'] ); $role_names = array_intersect_key( $wp_roles->get_names(), $option_roles ); } $title = __( 'Ads are disabled for the user role(s)', 'advanced-ads' ) . ': ' . implode( ', ', $role_names ); $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_no_user_role', 'title' => $title, 'href' => $settings_page, ] ), ]; } elseif ( 'ip-address' === $disabled_reason ) { $user_ip = get_user_ip_address(); $nodes[] = [ 'type' => 1, 'data' => array_merge( $health_node_common_data, [ 'id' => 'advanced_ads_ad_health_disabled_ip_address', 'title' => __( 'Ads are disabled for this IP address', 'advanced-ads' ) . ': ' . $user_ip, 'href' => $settings_page, ] ), ]; } $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_has_http', 'title' => sprintf( '%s %s', __( 'Your website is using HTTPS, but the ad code contains HTTP and might not work.', 'advanced-ads' ), /* translators: em tags */ sprintf( __( 'Ad IDs: %s', 'advanced-ads' ), '' ) ), 'href' => 'https://wpadvancedads.com/manual/ad-health/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-https-ads#https-ads', 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_has_http', 'target' => '_blank', ], ], ]; $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_incorrect_head', 'title' => sprintf( __( 'Visible ads should not use the Header placement: %s', 'advanced-ads' ), '' ), 'href' => 'https://wpadvancedads.com/manual/ad-health/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-visible-ad-in-header#header-ads', 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_incorrect_head', 'target' => '_blank', ], ], ]; // warn if an AdSense ad seems to be hidden. if ( ! isset( $adsense_options['violation-warnings-disable'] ) ) { $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_hidden_adsense', 'title' => sprintf( '%s: %s. %s', __( 'AdSense violation', 'advanced-ads' ), __( 'Ad is hidden', 'advanced-ads' ), /* translators: em tags */ sprintf( __( 'IDs: %s', 'advanced-ads' ), '' ) ), 'href' => 'https://wpadvancedads.com/manual/ad-health/?utm_source=advanced-ads&utm_medium=link&utm_campaign=adhealth-frontend-adsense-hidden#adsense-hidden', 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_hidden_adsense', 'target' => '_blank', ], ], ]; } $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_floated_responsive_adsense', /* translators: em tags */ 'title' => sprintf( __( 'The following responsive AdSense ads are not showing up: %s', 'advanced-ads' ), '' ), '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', 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_floated_responsive_adsense', 'target' => '_blank', ], ], ]; // warn if consent was not given. $privacy = Advanced_Ads_Privacy::get_instance(); if ( 'not_needed' !== $privacy->get_state() ) { $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_consent_missing', 'title' => __( 'Consent not given', 'advanced-ads' ), 'href' => admin_url( 'admin.php?page=advanced-ads-settings#top#privacy' ), 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_consent_missing', 'target' => '_blank', ], ], ]; } $privacy_options = $privacy->options(); if ( empty( $privacy_options['enabled'] ) || 'iab_tcf_20' !== $privacy_options['consent-method'] ) { $nodes[] = [ 'type' => 2, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_privacy_disabled', 'title' => __( 'Enable TCF integration', 'advanced-ads' ), 'href' => admin_url( 'admin.php?page=advanced-ads-settings#top#privacy' ), 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_warning advanced_ads_ad_health_privacy_disabled', 'target' => '_blank', ], ], ]; } $nodes[] = [ 'type' => 3, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_gam_debug', 'title' => __( 'Debug Google Ad Manager', 'advanced-ads' ), 'href' => esc_url( add_query_arg( 'google_force_console', '1' ) ), 'meta' => [ 'class' => 'hidden advanced_ads_ad_health_gam_debug_link', ], ], ]; // link to highlight ads and jump from one ad to the next. $nodes[] = [ 'type' => 3, 'amp' => false, 'data' => [ 'parent' => $health_parent_class, 'id' => 'advanced_ads_ad_health_highlight_ads', 'title' => sprintf( '%s %s', __( 'highlight ads', 'advanced-ads' ), ' ' ), 'meta' => [ 'class' => 'advanced_ads_ad_health_highlight_ads', ], ], ]; /** * Add new node. * * @param array $node An array that contains: * 'type' => 1 - warning, 2 - hidden warning that will be shown using JS, 3 - info message * 'data': @see WP_Admin_Bar->add_node * @param object $wp_admin_bar */ $nodes = apply_filters( 'advanced-ads-ad-health-nodes', $nodes ); usort( $nodes, [ $this, 'sort_nodes' ] ); // load number of already detected notices. $notices = Advanced_Ads_Ad_Health_Notices::get_number_of_notices(); if ( ! Conditional::is_amp() ) { $warnings = 0; // Will be updated using JS. } else { $warnings = $this->count_visible_warnings( $nodes, [ 1 ] ); } $issues = $warnings; $this->add_header_nodes( $wp_admin_bar, $issues, $notices ); foreach ( $nodes as $node ) { if ( isset( $node['data'] ) ) { $wp_admin_bar->add_node( $node['data'] ); } } $this->add_footer_nodes( $wp_admin_bar, $issues ); } /** * Add classes to the `body` tag. * * @param string[] $classes Array of existing class names. * @return string[] $classes Array of existing and new class names. */ public function body_class( $classes ) { global $post; $aa_classes = [ 'aa-prefix-' . wp_advads()->get_frontend_prefix(), ]; $disabled_reason = wp_advads()->frontend->get_disabled_reason(); if ( $disabled_reason ) { $aa_classes[] = 'aa-disabled-' . esc_attr( $disabled_reason ); } if ( ! empty( $post->ID ) ) { $ad_settings = get_post_meta( $post->ID, '_advads_ad_settings', true ); if ( ! empty( $ad_settings['disable_the_content'] ) ) { $aa_classes[] = 'aa-disabled-content'; } } // hide-ads-from-bots option is enabled. if ( ! empty( $this->options['block-bots'] ) ) { $aa_classes[] = 'aa-disabled-bots'; } $aa_classes = apply_filters( 'advanced-ads-body-classes', $aa_classes ); if ( ! is_array( $classes ) ) { $classes = []; } if ( ! is_array( $aa_classes ) ) { $aa_classes = []; } return array_merge( $classes, $aa_classes ); } /** * Count visible notices and warnings. * * @param array $nodes Nodes to add. * @param array $types Warning types. */ private function count_visible_warnings( $nodes, $types = [] ) { $warnings = 0; foreach ( $nodes as $node ) { if ( ! isset( $node['type'] ) || ! isset( $node['data'] ) ) { continue; } if ( in_array( $node['type'], $types ) ) { ++$warnings; } } return $warnings; } /** * Add header nodes. * * @param object $wp_admin_bar WP_Admin_Bar object. * @param int $issues Number of all issues. * @param int $notices Number of notices. */ private function add_header_nodes( $wp_admin_bar, $issues, $notices ) { $wp_admin_bar->add_node( [ 'id' => 'advanced_ads_ad_health', 'title' => __( 'Ad Health', 'advanced-ads' ) . ' ' . $issues . '', 'parent' => false, 'href' => admin_url( 'admin.php?page=advanced-ads' ), 'meta' => [ 'class' => $issues ? 'advads-adminbar-is-warnings' : '', ], ] ); // show that there are backend notices. if ( $notices ) { $wp_admin_bar->add_node( [ 'parent' => 'advanced_ads_ad_health', 'id' => 'advanced_ads_ad_health_more', /* translators: number of notices */ 'title' => sprintf( __( 'Show %d more notifications', 'advanced-ads' ), absint( $notices ) ), 'href' => admin_url( 'admin.php?page=advanced-ads' ), ] ); } } /** * Add footer nodes. * * @param obj $wp_admin_bar WP_Admin_Bar object. * @param int $issues Number of all issues. */ private function add_footer_nodes( $wp_admin_bar, $issues ) { if ( ! $issues ) { $wp_admin_bar->add_node( [ 'parent' => 'advanced_ads_ad_health', 'id' => 'advanced_ads_ad_health_fine', 'title' => __( 'Everything is fine', 'advanced-ads' ), 'href' => false, 'meta' => [ 'target' => '_blank', ], ] ); } $wp_admin_bar->add_node( [ 'parent' => 'advanced_ads_ad_health', 'id' => 'advanced_ads_ad_health_support', 'title' => __( 'Get help', 'advanced-ads' ), 'href' => Data::support_url( '?utm_source=advanced-ads&utm_medium=link&utm_campaign=health-support' ), 'meta' => [ 'target' => '_blank', ], ] ); } /** * Filter out nodes intended to AMP pages only. * * @param array $nodes Nodes to add. * @return array $nodes Nodes to add. */ private function filter_nodes( $nodes ) { return $nodes; } /** * Sort nodes. * * @param array $a The first node to compare. * @param array $b The second node to compare. * @return int Returns -1 if $a is less than $b, 1 if $a is greater than $b, and 0 if they are equal. */ public function sort_nodes( $a, $b ) { if ( ! isset( $a['type'] ) || ! isset( $b['type'] ) ) { return 0; } if ( $a['type'] === $b['type'] ) { return 0; } return ( $a['type'] < $b['type'] ) ? -1 : 1; } /** * Set variable to 'true' when 'the_content' filter is invoked. * * @param string $content The content to be filtered. * @return string $content The filtered content. */ public function set_did_the_content( $content ) { if ( ! $this->did_the_content ) { $this->did_the_content = true; } if ( Advanced_Ads::get_instance()->has_many_the_content() ) { $this->has_many_the_content = true; } return $content; } /** * Check conditions and display warning. * Conditions: * AdBlocker enabled, * jQuery is included in header * AdSense Quick Start ads are running */ public function footer_checks() { ob_start(); ?> get_options(); // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.EnqueuedResources.NonEnqueuedScript ob_start(); ?> get_prop( 'frontend-check' ) ) { return $content; } if ( Conditional::is_amp() ) { return $content; } if ( Validation::is_ad_https( $ad ) ) { ob_start(); ?> get_options(); if ( $ad->is_type( 'adsense' ) && ! empty( $ad->get_prop( 'cache_busting_elementid' ) ) && ! isset( $adsense_options['violation-warnings-disable'] ) ) { ob_start(); ?> is_head_placement() ) { return true; } // strip linebreaks, because, a line break after a comment is identified as a text node. $content = preg_replace( "/\r|\n/", '', $content ); if ( ! $dom = self::get_ad_dom( $content ) ) { return true; } $body = $dom->getElementsByTagName( 'body' )->item( 0 ); // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase, WordPress.PHP.StrictInArray.MissingTrueStrict $count = $body->childNodes->length; for ( $i = 0; $i < $count; $i++ ) { $node = $body->childNodes->item( $i ); if ( XML_TEXT_NODE === $node->nodeType ) { return false; } if ( XML_ELEMENT_NODE === $node->nodeType && ! in_array( $node->nodeName, [ 'meta', 'link', 'title', 'style', 'script', 'noscript', 'base' ] ) ) { return false; } } // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase, WordPress.PHP.StrictInArray.MissingTrueStrict return true; } /** * Convert ad content to a DOMDocument. * * @param string $content The ad content. * @return DOMDocument|false */ private static function get_ad_dom( $content ) { if ( ! extension_loaded( 'dom' ) ) { return false; } $libxml_previous_state = libxml_use_internal_errors( true ); $dom = new DOMDocument(); $result = $dom->loadHTML( '
' . $content . '' ); libxml_clear_errors(); libxml_use_internal_errors( $libxml_previous_state ); if ( ! $result ) { return false; } return $dom; } /** * Check if at least one placement uses `the_content`. * * @return bool True/False. */ private function has_the_content_placements() { $placements = wp_advads_get_placements(); // Find a placement that depends on 'the_content' filter. foreach ( $placements as $placement ) { if ( $placement->get_type_object()->get_options()['uses_the_content'] ) { return true; } } return false; } /** * Check if atleast one placement uses `adblocker item`. * * @return bool True/False. */ private function has_adblocker_placements() { $placements = wp_advads_get_placements(); foreach ( $placements as $placement ) { if ( ! empty( $placement->get_prop( 'item_adblocker' ) ) ) { return true; } } return false; } /** * Check if atleast one ad uses adblocker visitor condition. * * @return bool True/False. */ public function has_adblocker_visitor_condition() { $placements = wp_advads_get_placements(); foreach ( $placements as $placement ) { $item = $placement->get_item(); if ( empty( $item ) ) { continue; } $ads = []; if ( 'ad' === $placement->get_item_type() ) { $ads[] = $placement->get_item_object(); } elseif ( 'group' === $placement->get_item_type() ) { $item_object = $placement->get_item_object(); $ads = array_merge( $ads, $item_object ? $item_object->get_ads() : [] ); } foreach ( $ads as $ad ) { if ( ! $ad ) { continue; } $options = $ad->get_visitor_conditions() ?? []; foreach ( $options as $option ) { if ( 'adblocker' === $option['type'] ) { return true; } } } } return false; } }