Web Analytics > gear icon). */ if (!defined('ABSPATH')) { exit; } /** * Saved token shown as asterisks plus its last four characters */ function eacfa_masked_token() { $credentials = EACFA_API::resolve_credentials(); if (empty($credentials['token'])) { return ''; } return str_repeat('*', 24) . substr($credentials['token'], -4); } function eacfa_is_settings_request() { return eacfa_param('page') === 'cf-analytics' && eacfa_param('section') === 'settings' && current_user_can('manage_options'); } /** * Handle form submissions (runs before any output) */ add_action('admin_init', function() { if (!eacfa_is_settings_request()) { return; } // Connect / change token or zone if (isset($_POST['cf_analytics_save_settings'])) { check_admin_referer('cf_analytics_settings'); $existing = get_option('cf_analytics_settings', array()); $zone_id = sanitize_text_field(wp_unslash(isset($_POST['cf_zone_id']) ? $_POST['cf_zone_id'] : '')); $token = EACFA_API::clean_token(isset($_POST['cf_api_token']) ? sanitize_text_field(wp_unslash($_POST['cf_api_token'])) : ''); $manual_tag = strtolower(trim(sanitize_text_field(wp_unslash(isset($_POST['cf_site_tag']) ? $_POST['cf_site_tag'] : '')))); // Leaving the token empty keeps the saved one (handy when only changing the zone). if ($token === '' && !empty($existing['api_token'])) { $token = (string) EACFA_Encryption::decrypt($existing['api_token']); } if ($token === '') { add_settings_error('cf_analytics', 'token_missing', 'Please paste your Cloudflare API token.'); return; } if (!EACFA_API::is_tag($zone_id)) { add_settings_error('cf_analytics', 'zone_invalid', 'The Zone ID should be 32 characters (letters a-f and digits). Use "Load my zones" to pick it automatically.'); return; } if ($manual_tag !== '' && !EACFA_API::is_tag($manual_tag)) { add_settings_error('cf_analytics', 'tag_invalid', 'The Web Analytics site tag should be 32 characters (letters a-f and digits), or leave it empty to detect it automatically.'); return; } $api = new EACFA_API(array('type' => 'token', 'token' => $token)); $d = $api->diagnose($zone_id, $manual_tag); // Token and zone are required; the Web Analytics steps may need follow-up permissions. if (empty($d['zone_name'])) { $failed = array(); foreach ($d['checks'] as $c) { if (!$c['ok']) { $failed[] = $c['label'] . ': ' . $c['detail']; } } add_settings_error('cf_analytics', 'credentials_invalid', 'Could not connect to Cloudflare. ' . implode(' ', $failed)); return; } $saved = EACFA_Live::save_option('cf_analytics_settings', array( 'auth_type' => 'token', 'api_token' => EACFA_Encryption::encrypt($token), 'zone_id' => $zone_id, 'zone_name' => $d['zone_name'], 'account_id' => $d['account_id'], 'site_tag' => $d['site_tag'], 'site_host' => $d['site_host'], 'site_manual' => ($manual_tag !== '') )); if (!$saved['ok']) { add_settings_error('cf_analytics', 'save_failed', 'Connected to Cloudflare, but the settings could not be saved: ' . $saved['error']); return; } EACFA_Live::refresh_cache(); if ($d['ok']) { add_settings_error('cf_analytics', 'settings_saved', 'Connected. Web Analytics data is readable.', 'success'); } else { $failed = array(); foreach ($d['checks'] as $c) { if (!$c['ok']) { $failed[] = $c['label'] . ': ' . $c['detail']; } } add_settings_error('cf_analytics', 'needs_followup', 'Saved, but one step needs attention. ' . implode(' ', $failed), 'warning'); } return; } // Disconnect if (isset($_POST['cf_analytics_disconnect'])) { check_admin_referer('cf_analytics_disconnect'); if (EACFA_Live::remove_option('cf_analytics_settings')) { EACFA_Live::refresh_cache(); add_settings_error('cf_analytics', 'disconnected', 'Disconnected. Your token has been removed from this site.', 'success'); } else { add_settings_error('cf_analytics', 'disconnect_failed', 'The token could not be removed from the database.'); } return; } // Clear cache if (isset($_POST['cf_analytics_refresh_cache'])) { check_admin_referer('cf_analytics_refresh_cache'); EACFA_Live::refresh_cache(); add_settings_error('cf_analytics', 'cache_cleared', 'Cache cleared. The next view fetches fresh data from Cloudflare.', 'success'); return; } // Refresh interval if (isset($_POST['cf_analytics_save_refresh'])) { check_admin_referer('cf_analytics_refresh_interval'); $value = isset($_POST['cf_analytics_interval']) ? (int) $_POST['cf_analytics_interval'] : EACFA_Live::TTL_LIVE; if (!array_key_exists($value, EACFA_Live::intervals())) { $value = EACFA_Live::TTL_LIVE; } $saved = EACFA_Live::save_option('cf_analytics_refresh_interval', $value); if ($saved['ok']) { wp_safe_redirect(EACFA_Live::dashboard_url(array('section' => 'settings', 'eacfa_saved' => 'interval'))); exit; } add_settings_error('cf_analytics', 'interval_failed', 'The refresh setting could not be saved: ' . $saved['error']); return; } // Primary colour (site-wide) if (isset($_POST['cf_analytics_save_primary']) || isset($_POST['cf_analytics_reset_primary'])) { check_admin_referer('cf_analytics_primary_color'); $color = isset($_POST['cf_analytics_reset_primary']) ? eacfa_default_primary_color() : sanitize_hex_color(isset($_POST['cf_analytics_primary_color']) ? wp_unslash($_POST['cf_analytics_primary_color']) : ''); if (!$color) { add_settings_error('cf_analytics', 'color_invalid', 'Please pick a valid color.'); return; } $saved = EACFA_Live::save_option('cf_analytics_primary_color', $color); if ($saved['ok']) { wp_safe_redirect(EACFA_Live::dashboard_url(array('section' => 'settings', 'eacfa_saved' => 'color'))); exit; } add_settings_error('cf_analytics', 'color_failed', 'The color could not be saved. Please try again.'); return; } // Appearance (per user) if (isset($_POST['cf_analytics_save_appearance'])) { check_admin_referer('cf_analytics_appearance'); if (eacfa_set_theme(isset($_POST['cf_analytics_theme']) ? sanitize_key($_POST['cf_analytics_theme']) : 'auto')) { wp_safe_redirect(EACFA_Live::dashboard_url(array('section' => 'settings', 'eacfa_saved' => 'appearance'))); exit; } add_settings_error('cf_analytics', 'appearance_failed', 'Your appearance setting could not be saved.'); return; } }); /** * AJAX: list zones for the token typed into the form (or the saved one) */ add_action('wp_ajax_cf_analytics_get_zones', function() { check_ajax_referer('cf_analytics_get_zones', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); } $token = EACFA_API::clean_token(isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : ''); $credentials = null; if ($token !== '') { $credentials = array('type' => 'token', 'token' => $token); } elseif (!EACFA_API::resolve_credentials()) { wp_send_json_error('Paste your API token first.'); } $api = new EACFA_API($credentials); $result = $api->get_zones(); if (empty($result['success'])) { wp_send_json_error($result['message']); } $host = preg_replace('/^www\./', '', (string) wp_parse_url(home_url(), PHP_URL_HOST)); $zones = array(); $match = ''; foreach ($result['data'] as $zone) { $zones[] = array('id' => $zone['id'], 'name' => $zone['name']); if ($match === '' && $host && ($host === $zone['name'] || substr($host, -strlen('.' . $zone['name'])) === '.' . $zone['name'])) { $match = $zone['id']; } } wp_send_json_success(array('zones' => $zones, 'match' => $match)); }); /** * AJAX: run the connection checks with the saved settings */ add_action('wp_ajax_cf_analytics_test_connection', function() { check_ajax_referer('cf_analytics_test_connection', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); } $settings = get_option('cf_analytics_settings', array()); $api = new EACFA_API(); $manual = (!empty($settings['site_manual']) && !empty($settings['site_tag'])) ? $settings['site_tag'] : ''; $d = $api->diagnose(isset($settings['zone_id']) ? $settings['zone_id'] : '', $manual); // Remember a newly discovered site so the dashboard can use it. if (!empty($d['site_tag']) && !empty($d['account_id'])) { $settings['account_id'] = $d['account_id']; $settings['site_tag'] = $d['site_tag']; if ($d['site_host'] !== '') { $settings['site_host'] = $d['site_host']; } EACFA_Live::save_option('cf_analytics_settings', $settings); delete_transient('eacfa_ctx_fail'); } if ($d['ok']) { delete_transient('cf_analytics_last_error'); } wp_send_json_success(array('ok' => $d['ok'], 'checks' => $d['checks'])); }); /** * The settings section (rendered inside the dashboard shell) */ function eacfa_render_settings() { $settings = get_option('cf_analytics_settings', array()); $is_configured = EACFA_API::is_configured(); $last_error = get_transient('cf_analytics_last_error'); $theme = eacfa_current_theme(); $masked = eacfa_masked_token(); // A link the user clicks to open their Cloudflare dashboard. Nothing is loaded from it. // phpcs:ignore PluginCheck.CodeAnalysis.Offloading.OffloadedContent $tokens_url = 'https://dash.cloudflare.com/profile/api-tokens'; settings_errors('cf_analytics'); $saved_notice = eacfa_param('eacfa_saved'); if ($saved_notice === 'appearance') { echo '
Appearance saved.
'; } elseif ($saved_notice === 'interval') { echo '
Refresh setting saved.
'; } elseif ($saved_notice === 'color') { echo '
Primary color saved.
'; } ?>

Connection

Connected
Zone
Web Analytics site
Not detected yet. Run the test below.
API token
stored encrypted
Last API error
· ago

Stats are read from Cloudflare and refreshed in the background . Only the encrypted token and the IDs above are saved in your database.

  1. Turn on Web Analytics

    Enable it for your site in Cloudflare (your site → Speed → Real User Monitoring). This plugin only reads the data; it doesn’t add the tracking snippet.

  2. Create an API token

    In Cloudflare → My Profile → API Tokens choose Create Token → Create Custom Token and add these permissions. You sign in with your passkey as usual; the plugin only needs the token.

    Zone › Zone › Read Account › Account Settings › Read Account › Account Analytics › Read
  3. Paste the token and pick your zone

Saved token:

Advanced

Only needed if the token can’t read Account Settings. Find it in the Web Analytics snippet or the site list in Cloudflare.

Data refresh

'5 min', 600 => '10 min', 1800 => '30 min', 3600 => '1 hour', 0 => 'Manual'); $current = EACFA_Live::refresh_interval(); ?>
$label): ?>

Pages always open instantly with the data already saved. This sets how old that data may get before the next visit refreshes it in the background, after the page has loaded. Nothing runs on a timer. “Manual” keeps what you see until you click Refresh in the dashboard header (a new day still starts with a new lookup). The default is 30 min.

Appearance

'Light', 'dark' => 'Dark', 'auto' => 'System') as $value => $label): ?>

Saves as soon as you pick an option. Applies to the dashboard, the dashboard widget and this page, for your user only. “System” follows your device setting.

Primary color

Used for buttons, highlights and the chart lines. Every other tone is derived from this color. Good, needs improvement and poor keep their own colors so the Core Web Vitals ratings stay readable. Applies to everyone who uses this site's dashboard.

Disconnect

Removes the saved token and zone from this site. Your Cloudflare account is not changed; you can also revoke the token in Cloudflare.

wp_create_nonce('cf_analytics_get_zones'), 'testNonce' => wp_create_nonce('cf_analytics_test_connection'), )); ?> 0); }