collect(); $settings_url = admin_url('admin.php?page=abdal-security-headers'); $report_url = $settings_url . '#ash-csp-assistant'; $logo_url = ASH_PLUGIN_URL . 'assets/images/logo-200x200.png'; $logo_path = ASH_PLUGIN_DIR . 'assets/images/logo-200x200.png'; ?>
header_keys); $headers_active = $this->count_enabled($options, $this->header_keys); $features_active = $this->count_enabled($options, $this->feature_keys); $features_total = count($this->feature_keys); $state = class_exists('ASH_CSP_Assistant') ? ASH_CSP_Assistant::get_state() : array(); $disk_job = class_exists('ASH_CSP_Disk_Scanner') ? ASH_CSP_Disk_Scanner::get_job() : array(); $source_total = class_exists('ASH_CSP_Repository') ? ASH_CSP_Repository::count_sources() : 0; $violation_total = class_exists('ASH_CSP_Repository') ? ASH_CSP_Repository::count_violations() : 0; return array( 'security' => $this->security_status($headers_active, $headers_total, $features_active, $features_total), 'headers_active' => $headers_active, 'headers_total' => $headers_total, 'csp' => $this->csp_status($options, $state), 'activities' => $this->build_activities($state, $disk_job), 'stats' => $this->build_stats($headers_active, $headers_total, $source_total, $violation_total), ); } /** * @param array $options Plugin options. * @param array $keys Option keys. * @return int */ private function count_enabled($options, $keys) { $count = 0; foreach ($keys as $key) { if (isset($options[$key]) && (string) $options[$key] === '1') { $count++; } } return $count; } /** * Match the settings dashboard scoring, using Warning as the middle label. * * @param int $header_active Enabled header count. * @param int $header_total Total header count. * @param int $feature_active Enabled feature count. * @param int $feature_total Total feature count. * @return array */ private function security_status($header_active, $header_total, $feature_active, $feature_total) { $total = $header_total + $feature_total; $active = $header_active + $feature_active; $ratio = $total > 0 ? ($active / $total) : 0; if ($ratio >= 0.75) { return array( 'tone' => 'good', 'label' => _x('Good', 'security status', 'abdal-security-headers'), ); } if ($ratio >= 0.4) { return array( 'tone' => 'warning', 'label' => __('Warning', 'abdal-security-headers'), ); } return array( 'tone' => 'attention', 'label' => _x('Needs attention', 'security status', 'abdal-security-headers'), ); } /** * @param array $options Plugin options. * @param array $state Assistant state. * @return array */ private function csp_status($options, $state) { $status = isset($state['status']) ? (string) $state['status'] : ''; if ($status === 'learning' || (class_exists('ASH_CSP_Assistant') && ASH_CSP_Assistant::is_learning())) { return array( 'tone' => 'purple', 'label' => __('Learning', 'abdal-security-headers'), ); } $enabled = isset($options['content_security_policy']) && (string) $options['content_security_policy'] === '1'; if ($enabled) { return array( 'tone' => 'good', 'label' => _x('Enabled', 'CSP status', 'abdal-security-headers'), ); } return array( 'tone' => 'muted', 'label' => _x('Disabled', 'CSP status', 'abdal-security-headers'), ); } /** * Derive recent events from stored timestamps. Does not write a log. * * @param array $state Assistant state. * @param array $disk_job Disk scanner job. * @return array */ private function build_activities($state, $disk_job) { $items = array(); $started = $this->unix_ts(isset($state['started_at']) ? $state['started_at'] : ''); if ($started > 0) { $items[] = array( 'title' => __('Learning Started', 'abdal-security-headers'), 'time' => $started, 'icon' => 'dashicons-info', 'tone' => 'blue', ); } $reviewed = $this->unix_ts(isset($state['last_review_at']) ? $state['last_review_at'] : ''); if ($reviewed > 0) { $status = isset($state['status']) ? (string) $state['status'] : ''; $title = in_array($status, array('analysis_complete', 'monitoring'), true) ? __('CSP Scan Completed', 'abdal-security-headers') : __('Learning Completed', 'abdal-security-headers'); $items[] = array( 'title' => $title, 'time' => $reviewed, 'icon' => 'dashicons-yes-alt', 'tone' => 'green', ); } $disk_status = isset($disk_job['status']) ? (string) $disk_job['status'] : ''; $disk_updated = $this->unix_ts(isset($disk_job['updated_at']) ? $disk_job['updated_at'] : 0); if ($disk_status === 'complete' && $disk_updated > 0) { $items[] = array( 'title' => __('Deep File Scan Completed', 'abdal-security-headers'), 'time' => $disk_updated, 'icon' => 'dashicons-portfolio', 'tone' => 'purple', ); } $latest = class_exists('ASH_CSP_Repository') ? ASH_CSP_Repository::latest_source() : null; $violation = class_exists('ASH_CSP_Repository') ? ASH_CSP_Repository::latest_violation() : null; $violation_id = ($violation && isset($violation['id'])) ? (int) $violation['id'] : 0; if (is_array($latest)) { $source_ts = $this->unix_ts(isset($latest['last_seen']) ? $latest['last_seen'] : ''); $source_id = isset($latest['id']) ? (int) $latest['id'] : 0; if ($source_ts > 0 && $source_id !== $violation_id) { $items[] = array( 'title' => __('CSP Source Detected', 'abdal-security-headers'), 'time' => $source_ts, 'icon' => 'dashicons-info', 'tone' => 'blue', ); } } if (is_array($violation)) { $violation_ts = $this->unix_ts(isset($violation['last_seen']) ? $violation['last_seen'] : ''); if ($violation_ts > 0) { $items[] = array( 'title' => __('CSP Violation Detected', 'abdal-security-headers'), 'time' => $violation_ts, 'icon' => 'dashicons-warning', 'tone' => 'warning', ); } } usort($items, function ($a, $b) { return (int) $b['time'] - (int) $a['time']; }); return array_slice($items, 0, 3); } /** * @param int $headers_active Enabled headers. * @param int $headers_total Total headers. * @param int $source_total Detected sources. * @param int $violation_total Report-Only / warning sources. * @return array */ private function build_stats($headers_active, $headers_total, $source_total, $violation_total) { return array( array( 'label' => __('Detected CSP Sources', 'abdal-security-headers'), 'value' => number_format_i18n($source_total), 'icon' => 'dashicons-shield', 'tone' => 'neutral', ), array( 'label' => __('Active Security Headers', 'abdal-security-headers'), 'value' => $headers_active . ' / ' . $headers_total, 'icon' => 'dashicons-lock', 'tone' => ($headers_active === $headers_total && $headers_total > 0) ? 'neutral' : 'danger', ), array( 'label' => __('CSP Violations', 'abdal-security-headers'), 'value' => number_format_i18n($violation_total), 'icon' => 'dashicons-chart-line', 'tone' => $violation_total > 0 ? 'purple' : 'neutral', ), ); } /** * @param mixed $value Unix timestamp or MySQL datetime. * @return int */ private function unix_ts($value) { if ($value === '' || $value === null) { return 0; } if (is_numeric($value)) { $ts = (int) $value; return $ts > 0 ? $ts : 0; } $dt = date_create((string) $value, wp_timezone()); if (!$dt) { $parsed = strtotime((string) $value); return $parsed ? (int) $parsed : 0; } return (int) $dt->getTimestamp(); } /** * @param int $timestamp Unix timestamp. * @return string */ private function format_ago($timestamp) { $timestamp = (int) $timestamp; if ($timestamp <= 0) { return ''; } $now = time(); if ($timestamp > $now) { $timestamp = $now; } return sprintf( /* translators: %s: human-readable time difference */ __('%s ago', 'abdal-security-headers'), human_time_diff($timestamp, $now) ); } }