= $max ) { continue; } } if ( ! $best || $rule['priority'] > $best['priority'] ) { $best = $rule; } } return $best; } /** * Cookie_Notice_Dashboard class. * * @class Cookie_Notice_Dashboard */ class Cookie_Notice_Dashboard { /** * Class constructor. * * @return void */ public function __construct() { // actions add_action( 'wp_dashboard_setup', [ $this, 'wp_dashboard_setup' ], 11 ); add_action( 'wp_network_dashboard_setup', [ $this, 'wp_dashboard_setup' ], 11 ); add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts_styles' ] ); // site status add_filter( 'site_status_tests', [ $this, 'add_tests' ] ); } /** * Initialize widget. * * @global array $wp_meta_boxes * * @return void */ public function wp_dashboard_setup() { // filter user_can_see_stats if ( ! current_user_can( apply_filters( 'cn_manage_cookie_notice_cap', 'manage_options' ) ) ) return; // get main instance $cn = Cookie_Notice(); // check when to hide widget if ( is_multisite() ) { // site dashboard if ( current_action() === 'wp_dashboard_setup' && $cn->is_plugin_network_active() && $cn->network_options['general']['global_override'] ) return; // network dashboard if ( current_action() === 'wp_network_dashboard_setup' ) { if ( $cn->is_plugin_network_active() ) { if ( ! $cn->network_options['general']['global_override'] ) return; } else return; } } // check is it network admin if ( $cn->is_network_admin() ) $dashboard_key = 'dashboard-network'; else $dashboard_key = 'dashboard'; global $wp_meta_boxes; // set widget key $widget_key = 'cn_dashboard_stats'; // add dashboard chart widget wp_add_dashboard_widget( $widget_key, __( 'Compliance by Hu-manity.co', 'cookie-notice' ), [ $this, 'dashboard_widget' ] ); // get widgets $normal_dashboard = $wp_meta_boxes[$dashboard_key]['normal']['core']; // attempt to place the widget at the top $widget_instance = [ $widget_key => $normal_dashboard[ $widget_key ] ]; // remove new widget unset( $normal_dashboard[ $widget_key ] ); // merge widgets $sorted_dashboard = array_merge( $widget_instance, $normal_dashboard ); // update widgets $wp_meta_boxes[$dashboard_key]['normal']['core'] = $sorted_dashboard; } /** * Enqueue admin scripts and styles. * * @param string $pagenow * @return void */ public function admin_scripts_styles( $pagenow ) { if ( $pagenow !== 'index.php' ) return; // filter user_can_see_stats if ( ! current_user_can( apply_filters( 'cn_manage_cookie_notice_cap', 'manage_options' ) ) ) return; // get main instance $cn = Cookie_Notice(); $date_format = get_option( 'date_format' ); if ( is_multisite() && $cn->is_network_admin() && $cn->is_plugin_network_active() && $cn->network_options['general']['global_override'] ) $analytics = get_site_option( 'cookie_notice_app_analytics', [] ); else $analytics = get_option( 'cookie_notice_app_analytics', [] ); // styles wp_enqueue_style( 'cookie-notice-admin-dashboard', COOKIE_NOTICE_URL . '/css/admin-dashboard.css', [], $cn->defaults['version'] ); wp_enqueue_style( 'cookie-notice-microtip', COOKIE_NOTICE_URL . '/assets/microtip/microtip.min.css', [], $cn->defaults['version'] ); // bail if compliance is not active if ( $cn->get_status() !== 'active' ) return; // scripts wp_register_script( 'cookie-notice-admin-chartjs', COOKIE_NOTICE_URL . '/assets/chartjs/chart.min.js', [ 'jquery' ], '4.5.1', true ); wp_enqueue_script( 'cookie-notice-admin-dashboard', COOKIE_NOTICE_URL . '/js/admin-dashboard.js', [ 'jquery', 'cookie-notice-admin-chartjs' ], $cn->defaults['version'], true ); // cycle usage data $cycle_usage = [ 'threshold' => ! empty( $analytics['cycleUsage']->threshold ) ? (int) $analytics['cycleUsage']->threshold : 0, 'visits' => ! empty( $analytics['cycleUsage']->visits ) ? (int) $analytics['cycleUsage']->visits : 0 ]; // no more than threshold available $cycle_usage['visits'] = $cycle_usage['visits'] > $cycle_usage['threshold'] ? $cycle_usage['threshold'] : $cycle_usage['visits']; // available visits, -1 for no pro plans $cycle_usage['visits_available'] = $cycle_usage['threshold'] ? $cycle_usage['threshold'] - $cycle_usage['visits'] : -1; // get used threshold info if ( $cycle_usage['threshold'] > 0 ) { $threshold_used = ( $cycle_usage['visits'] / $cycle_usage['threshold'] ) * 100; if ( $threshold_used > 100 ) $threshold_used = 100; } else $threshold_used = 0; // CN_DEV_MODE: override usage % for testing. Param: ?cn_usage=0-100 if ( defined( 'CN_DEV_MODE' ) && CN_DEV_MODE && current_user_can( 'manage_options' ) && isset( $_GET['cn_usage'] ) ) { $cn_usage_override = (int) $_GET['cn_usage']; if ( $cn_usage_override >= 0 && $cn_usage_override <= 100 ) { $threshold_used = $cn_usage_override; if ( $cycle_usage['threshold'] <= 0 ) $cycle_usage['threshold'] = 10000; $cycle_usage['visits'] = (int) round( $cycle_usage['threshold'] * ( $threshold_used / 100 ) ); $cycle_usage['visits_available'] = $cycle_usage['threshold'] - $cycle_usage['visits']; } } $chartdata = [ 'usage' => [ 'type' => 'doughnut', 'options' => [ 'responsive' => true, 'plugins' => [ 'legend' => [ 'position' => 'top' ] ], 'hover' => [ 'mode' => 'label' ], 'layout' => [ 'padding' => 0 ] ], 'data' => [ 'labels' => [ _x( 'Used', 'threshold limit', 'cookie-notice' ), _x( 'Free', 'threshold limit', 'cookie-notice' ) ], 'datasets' => [ [ 'data' => [ $cycle_usage['visits'], $cycle_usage['visits_available'] ], 'backgroundColor' => [ 'rgb(32, 193, 158)', 'rgb(235, 233, 235)' ] ] ] ] ], 'consent-activity' => [ 'type' => 'line', 'options' => [ 'maintainAspectRatio' => false, 'responsive' => true, 'scales' => [ 'x' => [ 'display' => true, 'title' => [ 'display' => false ] ], 'y' => [ 'display' => true, 'grace' => 0, 'beginAtZero' => true, 'title' => [ 'display' => false ], 'ticks' => [ 'precision' => 0, 'maxTicksLimit' => 12 ] ] ] ] ], 'privacy-consent-logs-activity' => [ 'type' => 'line', 'options' => [ 'maintainAspectRatio' => false, 'responsive' => true, 'scales' => [ 'x' => [ 'display' => true, 'title' => [ 'display' => false ] ], 'y' => [ 'display' => true, 'grace' => 0, 'beginAtZero' => true, 'title' => [ 'display' => false ], 'ticks' => [ 'precision' => 0, 'maxTicksLimit' => 12 ] ] ], 'plugins' => [ 'legend' => [ 'display' => false ] ] ] ] ]; // warning usage color if ( $threshold_used > 80 && $threshold_used < 100 ) $chartdata['usage']['data']['datasets'][0]['backgroundColor'][0] = 'rgb(255, 193, 7)'; // danger usage color elseif ( $threshold_used == 100 ) $chartdata['usage']['data']['datasets'][0]['backgroundColor'][0] = 'rgb(220, 53, 69)'; $consent_activity_data = [ 'labels' => [], 'datasets' => [ 0 => [ 'label' => sprintf( __( 'Level %s', 'cookie-notice' ), 1 ), 'data' => [], 'fill' => true, 'backgroundColor' => 'rgba(196, 196, 196, 0.3)', 'borderColor' => 'rgba(196, 196, 196, 1)', 'borderWidth' => 1.2, 'borderDash' => [], 'pointBorderColor' => 'rgba(196, 196, 196, 1)', 'pointBackgroundColor' => 'rgba(255, 255, 255, 1)', 'pointBorderWidth' => 1.2 ], 1 => [ 'label' => sprintf( __( 'Level %s', 'cookie-notice' ), 2 ), 'data' => [], 'fill' => true, 'backgroundColor' => 'rgba(213, 181, 101, 0.3)', 'borderColor' => 'rgba(213, 181, 101, 1)', 'borderWidth' => 1.2, 'borderDash' => [], 'pointBorderColor' => 'rgba(213, 181, 101, 1)', 'pointBackgroundColor' => 'rgba(255, 255, 255, 1)', 'pointBorderWidth' => 1.2 ], 2 => [ 'label' => sprintf( __( 'Level %s', 'cookie-notice' ), 3 ), 'data' => [], 'fill' => true, 'backgroundColor' => 'rgba(152, 145, 177, 0.3)', 'borderColor' => 'rgba(152, 145, 177, 1)', 'borderWidth' => 1.2, 'borderDash' => [], 'pointBorderColor' => 'rgba(152, 145, 177, 1)', 'pointBackgroundColor' => 'rgba(255, 255, 255, 1)', 'pointBorderWidth' => 1.2 ] ] ]; // generate chart days $chart_date_format = 'j/m'; for ( $i = 29; $i >= 0; $i-- ) { // set label $consent_activity_data['labels'][] = date( $chart_date_format, strtotime( '-'. ( $i + 1 ) .' days' ) ); // reset datasets $consent_activity_data['datasets'][0]['data'][] = 0; $consent_activity_data['datasets'][1]['data'][] = 0; $consent_activity_data['datasets'][2]['data'][] = 0; } if ( ! empty( $analytics['consentActivities'] ) && is_array( $analytics['consentActivities'] ) ) { // set consent records in charts days foreach ( $analytics['consentActivities'] as $index => $entry ) { $time = date_i18n( $chart_date_format, strtotime( $entry->eventdt ) ); $i = array_search( $time, $consent_activity_data['labels'] ); if ( $i !== false ) $consent_activity_data['datasets'][(int) $entry->consentlevel - 1]['data'][$i] = (int) $entry->totalrecd; } } $chartdata['consent-activity']['data'] = $consent_activity_data; $privacy_consent_logs_activity_data = [ 'labels' => [], 'datasets' => [ 0 => [ 'label' => __( 'Privacy Content Logs', 'cookie-notice' ), 'data' => [], 'fill' => true, 'backgroundColor' => 'rgba(32, 193, 158, 0.3)', 'borderColor' => 'rgba(32, 193, 158, 1)', 'borderWidth' => 1.2, 'borderDash' => [], 'pointBorderColor' => 'rgba(32, 193, 158, 1)', 'pointBackgroundColor' => 'rgba(255, 255, 255, 1)', 'pointBorderWidth' => 1.2 ] ] ]; for ( $i = 29; $i >= 0; $i-- ) { // set label $privacy_consent_logs_activity_data['labels'][] = date( $chart_date_format, strtotime( '-'. ( $i + 1 ) .' days' ) ); // reset dataset $privacy_consent_logs_activity_data['datasets'][0]['data'][] = 0; } if ( ! empty( $analytics['privacyActivities'] ) && is_array( $analytics['privacyActivities'] ) ) { // set consent records in charts days foreach ( $analytics['privacyActivities'] as $index => $entry ) { $time = date_i18n( $chart_date_format, strtotime( $entry->date ) ); $i = array_search( $time, $privacy_consent_logs_activity_data['labels'] ); if ( $i !== false ) $privacy_consent_logs_activity_data['datasets'][0]['data'][$i] = (int) $entry->count; } } $chartdata['privacy-consent-logs-activity']['data'] = $privacy_consent_logs_activity_data; // prepare script data $script_data = [ 'ajaxURL' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'cn-dashboard-widget' ), 'nonceUser' => wp_create_nonce( 'cn-dashboard-user-options' ), 'charts' => $chartdata ]; wp_add_inline_script( 'cookie-notice-admin-dashboard', 'var cnDashboardArgs = ' . wp_json_encode( $script_data ) . ";\n", 'before' ); } /** * Render dashboard widget. * * @return void */ public function dashboard_widget() { // get main instance $cn = Cookie_Notice(); if ( $cn->is_network_admin() ) $upgrade_url = network_admin_url( 'admin.php?page=cookie-notice&welcome=1' ); else $upgrade_url = admin_url( 'admin.php?page=cookie-notice&welcome=1' ); $html = ''; // compliance active, display chart if ( $cn->get_status() === 'active' ) { // get user options $user_options = get_user_meta( get_current_user_id(), 'pvc_dashboard', true ); // empty options? if ( empty( $user_options ) || ! is_array( $user_options ) ) $user_options = []; // sanitize options $user_options = map_deep( $user_options, 'sanitize_text_field' ); // get menu items $menu_items = ! empty( $user_options['menu_items'] ) ? $user_options['menu_items'] : []; $items = [ [ 'id' => 'visits', 'title' => esc_html__( 'Traffic Overview', 'cookie-notice' ), 'description' => esc_html__( 'Displays the general visits information for your domain.', 'cookie-notice' ) ], [ 'id' => 'consent-activity', 'title' => esc_html__( 'Cookie Consent Activity', 'cookie-notice' ), 'description' => esc_html__( 'Displays the chart of the domain cookie consent activity in the last 30 days.', 'cookie-notice' ) ], [ 'id' => 'privacy-consent-logs-activity', 'title' => esc_html__( 'Privacy Consent Activity', 'cookie-notice' ), 'description' => esc_html__( 'Displays the chart of the domain privacy consent activity in the last 30 days.', 'cookie-notice' ) ] ]; $html .= '
'; foreach ( $items as $item ) { $html .= $this->widget_item( $item, $menu_items ); } $html .= '
'; // compliance inactive, display image } else { $html .= '
Compliance by Hu-manity.co widget

' . esc_html__( 'View consent activity inside WordPress Dashboard', 'cookie-notice' ) . '

' . esc_html__( 'Display information about the visits.', 'cookie-notice' ) . '

' . esc_html__( 'Get Consent logs data for the last 30 days.', 'cookie-notice' ) . '

' . esc_html__( 'Enable consent purpose categories, automatic cookie blocking and more.', 'cookie-notice' ) . '

' . esc_html__( 'Try Compliance by Hu-manity.co free', 'cookie-notice' ) . '

'; } // allows a list of html entities such as $allowed_html = wp_kses_allowed_html( 'post' ); $allowed_html['canvas'] = [ 'id' => true, 'height' => true ]; echo wp_kses( $html, $allowed_html ); } /** * Generate dashboard widget item HTML. * * @param array $item * @param array $menu_items * @return string */ public function widget_item( $item, $menu_items ) { return '
' . esc_html( $item['title'] ) . '
' . $this->widget_item_content( $item['id'] ) . '
'; } /** * Generate dashboard widget item content HTML. * * @param array $item * @return void */ public function widget_item_content( $item ) { $html = ''; switch ( $item ) { case 'visits': // get main instance $cn = Cookie_Notice(); $date_format = get_option( 'date_format' ); // get analytics data options if ( is_multisite() && $cn->is_network_admin() && $cn->is_plugin_network_active() && $cn->network_options['general']['global_override'] ) $analytics = get_site_option( 'cookie_notice_app_analytics', [] ); else $analytics = get_option( 'cookie_notice_app_analytics', [] ); // thirty days data $thirty_days_usage = [ 'visits' => ! empty( $analytics['thirtyDaysUsage']->visits ) ? (int) $analytics['thirtyDaysUsage']->visits : 0, 'consents' => 0, 'consents_updated' => ! empty( $analytics['lastUpdated'] ) ? date_create_from_format( 'Y-m-d H:i:s', $analytics['lastUpdated'] ) : date_create_from_format( 'Y-m-d H:i:s', current_time( 'mysql', true ) ) ]; // set current timezone $current_timezone = new DateTimeZone( $this->timezone_string() ); // update date $thirty_days_usage['consents_updated']->setTimezone( $current_timezone ); if ( ! empty( $analytics['consentActivities'] ) ) { foreach ( $analytics['consentActivities'] as $index => $entry ) { $thirty_days_usage['consents'] += (int) $entry->totalrecd; } } // cycle usage data $cycle_usage = [ 'threshold' => ! empty( $analytics['cycleUsage']->threshold ) ? (int) $analytics['cycleUsage']->threshold : 0, 'visits' => ! empty( $analytics['cycleUsage']->visits ) ? (int) $analytics['cycleUsage']->visits : 0, 'days_to_go' => ! empty( $analytics['cycleUsage']->daysToGo ) ? (int) $analytics['cycleUsage']->daysToGo : 0, 'start_date' => ! empty( $analytics['cycleUsage']->startDate ) ? date_create_from_format( '!Y-m-d', $analytics['cycleUsage']->startDate ) : '' ]; // get used threshold info if ( $cycle_usage['threshold'] > 0 ) { $threshold_used = ( $cycle_usage['visits'] / $cycle_usage['threshold'] ) * 100; if ( $threshold_used > 100 ) $threshold_used = 100; } else $threshold_used = 0; // CN_DEV_MODE: override usage % for testing. Param: ?cn_usage=0-100 if ( defined( 'CN_DEV_MODE' ) && CN_DEV_MODE && current_user_can( 'manage_options' ) && isset( $_GET['cn_usage'] ) ) { $cn_usage_override = (int) $_GET['cn_usage']; if ( $cn_usage_override >= 0 && $cn_usage_override <= 100 ) { $threshold_used = $cn_usage_override; if ( $cycle_usage['threshold'] <= 0 ) $cycle_usage['threshold'] = 10000; $cycle_usage['visits'] = (int) round( $cycle_usage['threshold'] * ( $threshold_used / 100 ) ); $cycle_usage['visits_available'] = $cycle_usage['threshold'] - $cycle_usage['visits']; } } $html .= '
' . esc_html__( 'Total Visits', 'cookie-notice' ) . '
' . esc_html( number_format_i18n( $thirty_days_usage['visits'], 0 ) ) . '
' . esc_html__( 'Last 30 days', 'cookie-notice' ) . '
' . esc_html__( 'Consent Logs', 'cookie-notice' ) . '
' . esc_html( number_format_i18n( $thirty_days_usage['consents'], 0 ) ) . '
' . esc_html( sprintf( __( 'Updated %s', 'cookie-notice' ), date_i18n( $date_format, $thirty_days_usage['consents_updated']->getTimestamp() ) ) ) . '
'; if ( $cycle_usage['threshold'] ) { $usage_class = 'success'; // warning usage color if ( $threshold_used > 80 && $threshold_used < 100 ) $usage_class = 'warning'; // danger usage color elseif ( $threshold_used === 100 ) $usage_class = 'danger'; $html .= '
' . esc_html__( 'Traffic Usage', 'cookie-notice' ) . '
' . esc_html( number_format_i18n( $threshold_used, 1 ) ) . ' %

' . esc_html( sprintf( __( 'Visits usage: %1$s / %2$s', 'cookie-notice' ), $cycle_usage['visits'], $cycle_usage['threshold'] ) ) . '

' . esc_html( sprintf( __( 'Cycle started: %s', 'cookie-notice' ), date_i18n( $date_format, $cycle_usage['start_date']->getTimestamp() ) ) ) . '

' . esc_html( sprintf( __( 'Days to go: %s', 'cookie-notice' ), $cycle_usage['days_to_go'] ) ) . '

'; // Near-limit nudge — reads copy from shared notifications.json. $cn_dash_tier = 'basic'; if ( ! empty( $cn->options['general']['app_id'] ) ) { $cn_dash_tier = ( $cn->get_subscription() === 'pro' ) ? 'pro' : 'free'; } $cn_dash_notification = cn_get_dashboard_notification( $threshold_used, $cn_dash_tier ); if ( $cn_dash_notification ) { $react_welcome_url = $cn->is_network_admin() ? network_admin_url( 'admin.php?page=cookie-notice&cn_react_welcome=1' ) : admin_url( 'admin.php?page=cookie-notice&cn_react_welcome=1' ); $cn_dash_desc = str_replace( [ '{usagePercent}', '{sessionTotal}', '{sessionUsed}' ], [ number_format_i18n( $threshold_used, 0 ), number_format_i18n( $cycle_usage['threshold'] ), number_format_i18n( $cycle_usage['visits'] ) ], $cn_dash_notification['description'] ?? '' ); $cn_dash_cta_label = $cn_dash_notification['cta']['label'] ?? 'Upgrade →'; $html .= '

' . esc_html( $cn_dash_desc ) . '

' . esc_html( $cn_dash_cta_label ) . '

'; } $html .= '
'; } $html .= '
'; break; case 'consent-activity': $html .= '
'; break; case 'privacy-consent-logs-activity': $html .= '
'; break; } return $html; } /** * Add site test. * * @param array $tests * @return array */ public function add_tests( $tests ) { $tests['direct']['cookie_compliance_status'] = [ 'label' => esc_html__( 'Compliance by Hu-manity.co Status', 'cookie-notice' ), 'test' => [ $this, 'test_cookie_compliance' ] ]; return $tests; } /** * Test for Cookie Compliance. * * @return array|void */ public function test_cookie_compliance() { if ( Cookie_Notice()->get_status() !== 'active' ) { return [ 'label' => esc_html__( 'Your site does not have Compliance by Hu-manity.co', 'cookie-notice' ), 'status' => 'recommended', 'description' => esc_html__( "Run Compliance Check to determine your site's compliance with updated data processing and consent rules under GDPR, CCPA and other international data privacy laws.", 'cookie-notice' ), 'actions' => sprintf( '

%s

', admin_url( 'admin.php?page=cookie-notice&welcome=1' ), esc_html__( 'Run Compliance Check', 'cookie-notice' ) ), 'test' => 'cookie_compliance_status', 'badge' => [ 'label' => esc_html__( 'Compliance', 'cookie-notice' ), 'color' => 'blue' ] ]; } else { return [ 'label' => esc_html__( 'Compliance by Hu-manity.co is active', 'cookie-notice' ), 'status' => 'good', 'description' => esc_html__( 'Compliance by Hu-manity.co is configured with active Compliance by Hu-manity.co protection. Your site is collecting consent in accordance with GDPR, CCPA, and other applicable privacy laws.', 'cookie-notice' ), 'actions' => sprintf( '

%s

', admin_url( 'admin.php?page=cookie-notice' ), esc_html__( 'View compliance dashboard', 'cookie-notice' ) ), 'test' => 'cookie_compliance_status', 'badge' => [ 'label' => esc_html__( 'Compliance', 'cookie-notice' ), 'color' => 'green' ] ]; } } /** * Retrieve the timezone of the site as a string. * * @return string */ public function timezone_string() { if ( function_exists( 'wp_timezone_string' ) ) return wp_timezone_string(); $timezone_string = get_option( 'timezone_string' ); if ( $timezone_string ) return $timezone_string; $offset = (float) get_option( 'gmt_offset' ); $hours = (int) $offset; $minutes = ( $offset - $hours ); $sign = ( $offset < 0 ) ? '-' : '+'; $abs_hour = abs( $hours ); $abs_mins = abs( $minutes * 60 ); $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); return $tz_offset; } }