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 '
Not detected yet. Run the test below.
stored encryptedStats are read from Cloudflare and refreshed in the background . Only the encrypted token and the IDs above are saved in your database.
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.
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.
Removes the saved token and zone from this site. Your Cloudflare account is not changed; you can also revoke the token in Cloudflare.