page_title = __( 'Dashboard', 'wpconsent-cookies-banner-privacy-suite' ); parent::__construct(); } /** * Page hooks. * * @return void */ public function page_hooks() { add_filter( 'wpconsent_admin_js_data', array( $this, 'banner_preview_scripts' ) ); } /** * Page content — prototype with placeholder data. * * @return void */ public function output_content() { $this->alert_bar(); // Count actionable pending items (not upsells) to decide docs widget placement. $all_items = $this->get_basic_items(); $advanced_items = $this->get_advanced_items(); $pending_count = 0; foreach ( array_merge( $all_items, $advanced_items ) as $item ) { if ( ! $item['earned'] && empty( $item['upsell'] ) ) { $pending_count++; } } // If 5+ actionable pending items, the score widget is tall — put docs on the right. $docs_on_right = $pending_count >= 5; ?>
get_alerts(); if ( empty( $alerts ) ) { return; } $visible = array_slice( $alerts, 0, 3 ); $overflow = count( $alerts ) - 3; ?> inspector->get_pending_cookies(); if ( ! empty( $pending ) ) { $alerts[] = array( 'key' => 'inspector_pending', /* translators: %d: number of cookies awaiting review. */ 'message' => sprintf( __( 'Inspector found %d cookies awaiting review.', 'wpconsent-cookies-banner-privacy-suite' ), count( $pending ) ), 'action' => __( 'Review', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-scanner&view=inspector' ), ); } // Scan overdue (>30 days). $scan_data = wpconsent()->scanner->get_scan_data(); if ( ! empty( $scan_data['date'] ) ) { $days_ago = (int) floor( ( time() - strtotime( $scan_data['date'] ) ) / DAY_IN_SECONDS ); if ( $days_ago > 30 ) { $alerts[] = array( 'key' => 'scan_overdue', /* translators: %d: number of days since last scan. */ 'message' => sprintf( __( 'Your last scan was %d days ago. Your site may have changed.', 'wpconsent-cookies-banner-privacy-suite' ), $days_ago ), 'action' => __( 'Scan Now', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-scanner' ), ); } } return $alerts; } /** * Get the basic compliance items with real data. * * @return array */ protected function get_basic_items() { if ( null !== $this->cached_basic_items ) { return $this->cached_basic_items; } // Consent banner enabled (20 pts). $banner_enabled = (bool) wpconsent()->settings->get_option( 'enable_consent_banner' ); // Script blocking enabled (20 pts). $script_blocking = (bool) wpconsent()->settings->get_option( 'enable_script_blocking' ); $all_scripts = wpconsent()->script_blocker->get_all_scripts(); $scripts_count = 0; foreach ( $all_scripts as $scripts ) { $scripts_count += count( $scripts ); } // Cookie policy configured (10 pts). $policy_page_id = (int) wpconsent()->settings->get_option( 'cookie_policy_page' ); $policy_exists = $policy_page_id > 0 && 'publish' === get_post_status( $policy_page_id ); // Cookie categories configured (10 pts). // Count user-configured cookies, excluding the auto-created wpconsent_preferences cookie. $cookie_query = new WP_Query( array( 'post_type' => 'wpconsent_cookie', 'post_status' => 'publish', 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => array( 'relation' => 'OR', array( 'key' => 'wpconsent_cookie_id', 'value' => 'wpconsent_preferences', 'compare' => '!=', ), array( 'key' => 'wpconsent_cookie_id', 'compare' => 'NOT EXISTS', ), ), ) ); $cookie_count = $cookie_query->found_posts; $has_categories = $cookie_count > 0; // Website scanned in last 30 days (5 pts). $scan_data = wpconsent()->scanner->get_scan_data(); $scan_recent = false; $scan_days = 0; $scan_time = 0; if ( ! empty( $scan_data['date'] ) ) { $scan_time = strtotime( $scan_data['date'] ); $scan_days = (int) floor( ( time() - $scan_time ) / DAY_IN_SECONDS ); $scan_recent = $scan_days <= 30; } // Inspector run at least once (5 pts). $inspector_run = (bool) wpconsent()->settings->get_option( 'inspector_completed', false ); // Scan findings reviewed (5 pts). $pending_cookies = wpconsent()->inspector->get_pending_cookies(); $all_reviewed = empty( $pending_cookies ); // Google Consent Mode (5 pts) — only shown when Google services are detected. $has_google_services = $this->has_google_services(); $gcm_enabled = (bool) wpconsent()->settings->get_option( 'google_consent_mode', true ); // Content blocking enabled (5 pts) — only shown when content-blockable services exist. $has_embeddable_services = $this->has_embeddable_services(); $content_blocking = (bool) wpconsent()->settings->get_option( 'enable_content_blocking' ); $items = array( array( 'label' => __( 'Consent banner enabled', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 20, 'earned' => $banner_enabled, 'detail' => $banner_enabled ? __( 'Your consent banner is active and showing to visitors.', 'wpconsent-cookies-banner-privacy-suite' ) : __( 'Enable a consent banner to inform visitors about cookies.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-banner' ), 'action_type' => 'toggle', 'action_key' => 'enable_consent_banner', ), array( 'label' => __( 'Script blocking enabled', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 20, 'earned' => $script_blocking, 'detail' => $script_blocking /* translators: %d: number of scripts being managed. */ ? sprintf( __( '%d scripts managed.', 'wpconsent-cookies-banner-privacy-suite' ), $scripts_count ) : __( 'Block tracking scripts until visitors give consent.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-cookies' ), 'action_type' => 'toggle', 'action_key' => 'enable_script_blocking', ), array( 'label' => __( 'Cookie policy configured', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 10, 'earned' => $policy_exists, 'detail' => $policy_exists /* translators: %s: the cookie policy page title. */ ? sprintf( __( 'Linked to "%s".', 'wpconsent-cookies-banner-privacy-suite' ), get_the_title( $policy_page_id ) ) : __( 'Set a cookie policy page so visitors can learn more.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-cookies' ) . '#cookie-policy-input', 'action_type' => 'generate_policy', 'action_key' => 'cookie_policy', ), array( 'label' => __( 'Cookie categories configured', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 10, 'earned' => $has_categories, 'detail' => $has_categories /* translators: %d: number of cookies documented. */ ? sprintf( __( '%d cookies documented.', 'wpconsent-cookies-banner-privacy-suite' ), $cookie_count ) : __( 'Document cookies so visitors know what data is collected.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-cookies&view=cookies' ), 'action_type' => 'configure_cookies', 'action_key' => '', 'has_scan' => ! empty( $scan_data['date'] ), ), array( 'label' => __( 'Website scanned recently', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 5, 'earned' => $scan_recent, 'detail' => $scan_recent /* translators: %s: formatted date of last scan. */ ? sprintf( __( 'Last scan: %s.', 'wpconsent-cookies-banner-privacy-suite' ), date_i18n( get_option( 'date_format' ), $scan_time ) ) : ( ! empty( $scan_data['date'] ) /* translators: %d: days since last scan. */ ? sprintf( __( 'Last scan was %d days ago. Run a new scan to check for changes.', 'wpconsent-cookies-banner-privacy-suite' ), $scan_days ) : __( 'Scan your website to detect cookies and tracking scripts.', 'wpconsent-cookies-banner-privacy-suite' ) ), 'url' => admin_url( 'admin.php?page=wpconsent-scanner' ), 'action_type' => 'link', 'action_key' => '', ), array( 'label' => __( 'Inspector completed', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 5, 'earned' => $inspector_run, 'detail' => $inspector_run ? __( 'Inspector has been run to verify cookie detection.', 'wpconsent-cookies-banner-privacy-suite' ) : __( 'Run the Cookie Inspector to detect cookies in real-time.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-scanner&view=inspector' ), 'action_type' => 'link', 'action_key' => '', ), array( 'label' => __( 'All findings reviewed', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 5, 'earned' => $all_reviewed && $inspector_run, 'detail' => $all_reviewed ? __( 'No pending findings to review.', 'wpconsent-cookies-banner-privacy-suite' ) /* translators: %d: number of cookies pending review. */ : sprintf( __( '%d cookies need categorization.', 'wpconsent-cookies-banner-privacy-suite' ), count( $pending_cookies ) ), 'url' => admin_url( 'admin.php?page=wpconsent-scanner&view=inspector' ), 'action_type' => 'link', 'action_key' => '', ), ); // Content blocking — only add when embeddable services are registered. if ( $has_embeddable_services ) { $items[] = array( 'label' => __( 'Content blocking enabled', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 5, 'earned' => $content_blocking, 'detail' => $content_blocking ? __( 'Embedded content is blocked until visitors give consent.', 'wpconsent-cookies-banner-privacy-suite' ) : __( 'Block iframes and embeds like YouTube and Google Maps until consent is given.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-cookies' ) . '#enable_content_blocking', 'action_type' => 'toggle', 'action_key' => 'enable_content_blocking', ); } // Google Consent Mode — only add when Google services are present. if ( $has_google_services ) { $items[] = array( 'label' => __( 'Google Consent Mode', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 5, 'earned' => $gcm_enabled, 'detail' => $gcm_enabled ? __( 'Google services respect visitor consent choices.', 'wpconsent-cookies-banner-privacy-suite' ) : __( 'Let Google Analytics and Ads respect consent before tracking.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => admin_url( 'admin.php?page=wpconsent-cookies' ) . '#google_consent_mode', 'action_type' => 'toggle', 'action_key' => 'google_consent_mode', ); } $this->cached_basic_items = $items; return $items; } /** * Check if the site has Google services registered. * * Uses the same logic as the frontend: checks service taxonomy terms * for slugs containing "google". Result is cached in a transient. * * @return bool */ protected function has_google_services() { $cached = get_transient( 'wpconsent_has_google_services' ); if ( false !== $cached ) { return 'yes' === $cached; } $services = get_terms( array( 'taxonomy' => 'wpconsent_category', 'hide_empty' => false, 'number' => 0, ) ); if ( is_wp_error( $services ) ) { return false; } foreach ( $services as $service ) { if ( false !== strpos( $service->slug, 'google' ) ) { set_transient( 'wpconsent_has_google_services', 'yes', DAY_IN_SECONDS ); return true; } } set_transient( 'wpconsent_has_google_services', 'no', DAY_IN_SECONDS ); return false; } /** * Check if the site has services with embeddable content (iframes, videos, maps). * * Cross-references content-blocking providers from the script library with * services actually registered in the taxonomy. Result is cached in a transient. * * @return bool */ protected function has_embeddable_services() { $cached = get_transient( 'wpconsent_has_embeddable_services' ); if ( false !== $cached ) { return 'yes' === $cached; } // Get provider keys that support content blocking (e.g., youtube, google-maps, vimeo). $providers = wpconsent()->script_blocker->get_content_blocking_providers(); if ( empty( $providers ) ) { set_transient( 'wpconsent_has_embeddable_services', 'no', DAY_IN_SECONDS ); return false; } // Check if any of these providers are registered as services on this site. $services = get_terms( array( 'taxonomy' => 'wpconsent_category', 'hide_empty' => false, 'number' => 0, ) ); if ( is_wp_error( $services ) ) { return false; } $provider_keys = array_keys( $providers ); foreach ( $services as $service ) { if ( in_array( $service->slug, $provider_keys, true ) ) { set_transient( 'wpconsent_has_embeddable_services', 'yes', DAY_IN_SECONDS ); return true; } } set_transient( 'wpconsent_has_embeddable_services', 'no', DAY_IN_SECONDS ); return false; } /** * Get value stats to show below the score label. * * @return array Array of stat strings. */ protected function get_value_stats() { // Cookies documented. $total_cookies = (int) wp_count_posts( 'wpconsent_cookie' )->publish; // Days since activation. $activated_data = get_option( 'wpconsent_activated', array() ); $activated = ! empty( $activated_data['wpconsent'] ) ? (int) $activated_data['wpconsent'] : time(); $days = (int) floor( ( time() - $activated ) / DAY_IN_SECONDS ); $stats = array( /* translators: %d: number of cookies documented. */ sprintf( __( '%d cookies documented', 'wpconsent-cookies-banner-privacy-suite' ), $total_cookies ), ); if ( $days > 0 ) { /* translators: %d: number of days since plugin activation. */ $stats[] = sprintf( __( 'Active for %d days', 'wpconsent-cookies-banner-privacy-suite' ), $days ); } return $stats; } /** * Compliance score widget. * * @return void */ public function compliance_score_widget() { $all_items = $this->get_basic_items(); $advanced_items = $this->get_advanced_items(); // Split into earned and pending for both sections. $pending_basics = array_filter( $all_items, function ( $item ) { return ! $item['earned']; } ); $earned_basics = array_filter( $all_items, function ( $item ) { return $item['earned']; } ); $pending_advanced = array_filter( $advanced_items, function ( $item ) { return ! $item['earned']; } ); $earned_advanced = array_filter( $advanced_items, function ( $item ) { return $item['earned']; } ); // Combine all earned for the toggle count. $all_earned = array_merge( $earned_basics, $earned_advanced ); $all_total = array_merge( $all_items, $advanced_items ); $earned_count = count( $all_earned ); $total_count = count( $all_total ); // Calculate score. // Lite: score is based on base items only, scaled to 75% max. // Pro: score is based on all items out of 100%. $has_upsells = false; foreach ( $advanced_items as $item ) { if ( ! empty( $item['upsell'] ) ) { $has_upsells = true; break; } } if ( $has_upsells ) { // Lite: only base items count. Upsell items appear in the UI but don't affect the score. // Scaling to 75 means a fully configured Lite user always reaches exactly 75%. $base_earned = 0; $base_total = 0; foreach ( $all_items as $item ) { $base_total += $item['points']; if ( $item['earned'] ) { $base_earned += $item['points']; } } $percentage = $base_total > 0 ? (int) round( ( $base_earned / $base_total ) * 75 ) : 0; } else { // Pro: all items count toward a 100% score. $earned_points = 0; $total_points = 0; foreach ( array_merge( $all_items, $advanced_items ) as $item ) { $total_points += $item['points']; if ( $item['earned'] ) { $earned_points += $item['points']; } } $percentage = $total_points > 0 ? (int) round( ( $earned_points / $total_points ) * 100 ) : 0; } // Status label and color. if ( $percentage >= 90 ) { $label = __( 'Excellent. All recommended features enabled.', 'wpconsent-cookies-banner-privacy-suite' ); $color = 'green'; } elseif ( $percentage >= 70 ) { $label = __( 'Good. Core setup done, more features available.', 'wpconsent-cookies-banner-privacy-suite' ); $color = 'blue'; } elseif ( $percentage >= 50 ) { $label = __( 'Getting there. A few steps to complete.', 'wpconsent-cookies-banner-privacy-suite' ); $color = 'yellow'; } else { $label = __( 'Just getting started. Let\'s set things up.', 'wpconsent-cookies-banner-privacy-suite' ); $color = 'orange'; } ?> cached_advanced_items ) { return $this->cached_advanced_items; } $items = array( array( 'earned' => false, 'label' => __( 'Geolocation rules', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 0, 'detail' => __( 'Show the right consent experience per region.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => wpconsent_utm_url( 'https://wpconsent.com/lite/', 'dashboard', 'geolocation' ), 'upsell' => true, ), array( 'earned' => false, 'label' => __( 'Consent logging enabled', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 15, 'detail' => __( 'Keep a log of visitor consent choices.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => wpconsent_utm_url( 'https://wpconsent.com/lite/', 'dashboard', 'consent-logging' ), 'upsell' => true, ), array( 'earned' => false, 'label' => __( 'Auto-scanning enabled', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 10, 'detail' => __( 'Automatically detect new cookies as your site changes.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => wpconsent_utm_url( 'https://wpconsent.com/lite/', 'dashboard', 'auto-scanning' ), 'upsell' => true, ), ); // Conditional: show multilanguage upsell if a translation plugin is active. if ( $this->has_translation_plugin() ) { $items[] = array( 'earned' => false, 'label' => __( 'Automatic translations', 'wpconsent-cookies-banner-privacy-suite' ), 'points' => 0, 'detail' => __( 'Automatically translate your consent banner for multilingual visitors.', 'wpconsent-cookies-banner-privacy-suite' ), 'url' => wpconsent_utm_url( 'https://wpconsent.com/lite/', 'dashboard', 'multilanguage' ), 'upsell' => true, ); } $this->cached_advanced_items = $items; return $items; } /** * Check if a supported translation plugin is active. * * @return bool */ protected function has_translation_plugin() { return function_exists( 'pll_current_language' ) || defined( 'ICL_SITEPRESS_VERSION' ) || class_exists( 'TRP_Translate_Press' ); } /** * Get suggestion items. Suggestions are dismissible and don't count toward score. * * Lite returns empty — suggestions are Pro features. * Pro overrides this to return IAB TCF, Do Not Sell, etc. * * @return array */ protected function get_suggestions() { return array(); } /** * Get dismissed suggestion items for display in the details toggle. * * Lite returns empty. Pro overrides this. * * @return array */ protected function get_dismissed_suggestions() { return array(); } /** * Render a single compliance score item. * * @param array $item The item data. * @param bool $is_advanced Whether this is in the advanced section. * * @return void */ protected function render_score_item( $item, $is_advanced = false ) { $is_upsell = ! empty( $item['upsell'] ); $classes = 'wpconsent-score-item'; $classes .= $item['earned'] ? ' wpconsent-score-item-earned' : ' wpconsent-score-item-pending'; if ( $is_upsell ) { $classes .= ' wpconsent-score-item-pro'; } ?>