Site Health. */ class DebugTools { /** Setting names whose values are never shown in reports. */ private const SECRET_PATTERN = '/token|secret|password|license|_key$|^zaraz_events_path$/i'; public function register(): void { add_action('admin_post_zbridge_zaraz_debug', [$this, 'handleZarazDebug']); add_action('admin_init', [$this, 'handleClearLog']); add_action('wp_ajax_zbridge_support_info', [$this, 'handleSupportInfo']); add_filter('debug_information', [$this, 'addSiteHealthInfo']); add_filter('site_status_tests', [$this, 'addSiteHealthTest']); } /** * Debug key Zaraz uses on this site: the live configuration's (Pro, with an API token), * otherwise the one in the container WP Zbridge exports. * * @return array ['key' => string, 'source' => 'live'|'export'] */ public static function zarazDebugKey(): array { $isPro = class_exists('\WPZaraz\Licensing\LicenseManager') && \WPZaraz\Licensing\LicenseManager::isPro(); if ($isPro && class_exists('\WPZaraz\Cloudflare\ApiClient') && Options::get('cf_api_token') && Options::get('cf_zone_id')) { $live = get_transient('wp_zaraz_live_config'); if (!is_array($live)) { $response = (new \WPZaraz\Cloudflare\ApiClient())->getConfig(); $live = !empty($response['success']) ? (array) ($response['data']['result'] ?? []) : null; if ($live !== null) { set_transient('wp_zaraz_live_config', $live, 600); } } if (!empty($live['debugKey'])) { return ['key' => (string) $live['debugKey'], 'source' => 'live']; } } return ['key' => Options::debugKey(), 'source' => 'export']; } /** * Sets or clears the zarazDebug cookie, then opens the site (or returns to Diagnostics). */ public function handleZarazDebug(): void { if (!current_user_can('manage_options')) { wp_die(esc_html__('You are not allowed to do this.', 'zbridge')); } check_admin_referer('zbridge_zaraz_debug'); $on = isset($_GET['state']) && sanitize_key(wp_unslash($_GET['state'])) === 'on'; $path = defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/'; $domain = defined('COOKIE_DOMAIN') && COOKIE_DOMAIN ? COOKIE_DOMAIN : ''; if ($on) { // Zaraz turns on its debugger for visitors with this cookie (Zaraz debug mode) setcookie('zarazDebug', self::zarazDebugKey()['key'], 0, $path, $domain, is_ssl(), false); wp_safe_redirect(home_url('/')); } else { setcookie('zarazDebug', '', time() - 3600, $path, $domain, is_ssl(), false); wp_safe_redirect(admin_url('admin.php?page=wp-zbridge&tab=diagnostics')); } exit; } public function handleClearLog(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified below if (empty($_GET['zbridge_clear_log']) || !current_user_can('manage_options')) { return; } check_admin_referer('zbridge_clear_log'); Logger::clear(); wp_safe_redirect(admin_url('admin.php?page=wp-zbridge&tab=diagnostics')); exit; } public function handleSupportInfo(): void { check_ajax_referer('wp_zaraz_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Unauthorized: Insufficient user permissions.', 'zbridge')], 403); return; } wp_send_json_success(['report' => $this->supportReport()]); } /** * Plain-text report for support requests. Tokens, keys and other secrets are never included. * * @return string */ public function supportReport(): string { $lines = ['### WP Zbridge support info', '']; foreach ($this->environment() as $label => $value) { $lines[] = $label . ': ' . $value; } $lines[] = ''; $lines[] = '## Setup Health'; foreach ((new HealthCheck())->run() as $result) { $lines[] = '[' . strtoupper($result['level']) . '] ' . $result['title'] . ' - ' . $result['detail']; } $lines[] = ''; $lines[] = '## Settings (secrets removed)'; foreach (self::redactedSettings() as $key => $value) { $lines[] = $key . ': ' . $value; } $lines[] = ''; $lines[] = '## Active plugins'; foreach ($this->activePlugins() as $plugin) { $lines[] = $plugin; } $lines[] = ''; $lines[] = '## Last 50 log lines'; // The Events API path accepts events from anyone who knows it, so it stays out of the report $eventsPath = (string) Options::get('zaraz_events_path', ''); foreach (array_slice(Logger::getLogs(), -50) as $log) { $message = (string) ($log['message'] ?? ''); if ($eventsPath !== '') { $message = str_replace($eventsPath, '[events API path]', $message); } $lines[] = '[' . ($log['timestamp'] ?? '') . '] [' . strtoupper((string) ($log['level'] ?? '')) . '] ' . $message; } return implode("\n", $lines); } /** * Settings with every secret value replaced by "(set)" or "(empty)". * * @return array */ public static function redactedSettings(): array { $out = []; foreach (Options::getAll() as $key => $value) { if (preg_match(self::SECRET_PATTERN, (string) $key)) { $out[$key] = ($value === '' || $value === null || $value === []) ? '(empty)' : '(set)'; continue; } if (is_bool($value)) { $out[$key] = $value ? 'yes' : 'no'; } elseif (is_array($value)) { $out[$key] = (string) wp_json_encode($value); } else { $out[$key] = (string) $value; } } ksort($out); return $out; } /** * @param array $info * @return array */ public function addSiteHealthInfo(array $info): array { $fields = []; foreach ($this->environment() as $label => $value) { $fields[sanitize_key($label)] = ['label' => $label, 'value' => $value]; } foreach ((new HealthCheck())->run() as $i => $result) { $fields['health_' . $i] = [ /* translators: %s: health check level, such as WARNING */ 'label' => sprintf(__('Setup Health (%s)', 'zbridge'), strtoupper($result['level'])), 'value' => $result['title'], ]; } $info['zbridge'] = [ 'label' => __('Zbridge (Cloudflare Zaraz)', 'zbridge'), 'fields' => $fields, ]; return $info; } /** * @param array $tests * @return array */ public function addSiteHealthTest(array $tests): array { $tests['direct']['zbridge_setup'] = [ 'label' => __('Zbridge setup', 'zbridge'), 'test' => [$this, 'siteHealthTest'], ]; return $tests; } /** * Site Health test: critical if any Setup Health check is an error, recommended if a warning. * * @return array */ public function siteHealthTest(): array { $results = (new HealthCheck())->run(); $levels = array_column($results, 'level'); $problems = array_filter($results, static function ($r) { return in_array($r['level'], ['error', 'warning'], true); }); $status = in_array('error', $levels, true) ? 'critical' : (in_array('warning', $levels, true) ? 'recommended' : 'good'); $description = '
' . esc_html__('Zbridge checks that Cloudflare Zaraz has your latest container, your tools have their access tokens and consent is collected.', 'zbridge') . '
'; if (!empty($problems)) { $description .= '