comment_approved)) { $status = (string) $comment_obj->comment_approved; } } $status_label = $this->status_label($status); $source = (string) get_comment_meta($comment_id, 'aksrt_moderation_source', true); $result_value = (string) get_comment_meta($comment_id, 'aksrt_moderation_result', true); $reason = (string) get_comment_meta($comment_id, 'aksrt_moderation_reason', true); $akismet = (string) get_comment_meta($comment_id, 'aksrt_moderation_akismet', true); $ai_score = (string) get_comment_meta($comment_id, 'aksrt_moderation_ai_score', true); $ai_flagged = (string) get_comment_meta($comment_id, 'aksrt_moderation_ai_flagged', true); $ai_top = (string) get_comment_meta($comment_id, 'aksrt_moderation_ai_top_category', true); $keyword = (string) get_comment_meta($comment_id, 'aksrt_moderation_keyword', true); $match_type = (string) get_comment_meta($comment_id, 'aksrt_moderation_keyword_match_type', true); $moderation_user_id = (string) get_comment_meta($comment_id, 'aksrt_moderation_user_id', true); $moderation_user_ip = (string) get_comment_meta($comment_id, 'aksrt_moderation_user_ip', true); if ($source === '') { $source = 'wp_default'; } if ($result_value === '') { $result_value = $this->result_by_approved($status); } if ($reason === '') { $reason = 'wp_default_policy'; } if ($akismet === '') { $akismet_meta = get_comment_meta($comment_id, 'akismet_result', true); if ((string) $akismet_meta === 'true') { $akismet = 'spam'; } elseif ((string) $akismet_meta === 'false') { $akismet = 'ham'; } else { $akismet = 'unknown'; } } if ($source === 'wp_default' && $akismet === 'ham') { $source = 'akismet'; if ($result_value === 'wp_default_policy') { $result_value = 'approved'; } if ($reason === 'wp_default_policy') { $reason = 'ham'; } } elseif ($source === 'wp_default' && $akismet === 'spam') { $source = 'akismet'; $result_value = 'spam'; if ($reason === 'wp_default_policy') { $reason = 'spam'; } } echo '' . esc_html($status_label) . '
'; echo 'Source: ' . esc_html($source) . ' | '; echo 'Result: ' . esc_html($result_value) . ' | '; echo 'Reason: ' . esc_html($reason) . ' | '; echo 'Akismet: ' . esc_html($akismet); $extras = []; if ($ai_score !== '') { $extras[] = 'AI score: ' . $ai_score; } if ($ai_flagged !== '') { $extras[] = 'AI flagged: ' . $ai_flagged; } if ($ai_top !== '') { $extras[] = 'AI top: ' . $ai_top; } if ($keyword !== '') { $extras[] = 'Keyword: ' . $keyword; } if ($match_type !== '') { $extras[] = 'Match: ' . $match_type; } if ($moderation_user_id !== '') { $extras[] = 'UID: ' . $moderation_user_id; } if ($moderation_user_ip !== '') { $extras[] = 'IP: ' . $moderation_user_ip; } if (!empty($extras)) { echo '
' . esc_html(implode(' | ', $extras)) . ''; } } private function new_default_result(): array { return [ 'result' => 'approved', 'source' => 'wp_default', 'reason' => 'wp_default_policy', 'akismet' => 'unknown', 'ai_score' => '', 'ai_flagged' => '', 'ai_top_category' => '', 'keyword' => '', 'keyword_match_type' => '', 'ip' => '', ]; } private function remember_result(array $commentdata, array $decision): void { $fp = $this->comment_fingerprint($commentdata); if ($fp === '') { return; } $this->moderation_result_map[$fp] = $decision; if (array_key_exists('comment_approved', $commentdata)) { $this->forced_approved_map[$fp] = $commentdata['comment_approved']; } } private function resolve_result_from_runtime_or_default(array $commentdata, \WP_Comment $comment): array { $fp_data = $this->comment_fingerprint($commentdata); if ($fp_data !== '' && isset($this->moderation_result_map[$fp_data])) { return $this->moderation_result_map[$fp_data]; } $fp_comment = $this->comment_fingerprint((array) $comment); if ($fp_comment !== '' && isset($this->moderation_result_map[$fp_comment])) { return $this->moderation_result_map[$fp_comment]; } $fallback = $this->new_default_result(); $fallback['result'] = $this->result_by_approved((string) $comment->comment_approved); $fallback['reason'] = 'wp_default_policy'; $akismet_meta = get_comment_meta($comment->comment_ID, 'akismet_result', true); if ((string) $akismet_meta === 'true') { $fallback['source'] = 'akismet'; $fallback['reason'] = 'spam'; $fallback['akismet'] = 'spam'; $fallback['result'] = 'spam'; } elseif ((string) $akismet_meta === 'false') { $fallback['source'] = 'akismet'; $fallback['reason'] = 'ham'; $fallback['akismet'] = 'ham'; } return $fallback; } private function enrich_moderation_result(array $result, array $commentdata): array { if (!isset($result['result']) || $result['result'] === '') { $approved = isset($commentdata['comment_approved']) ? (string) $commentdata['comment_approved'] : '1'; $result['result'] = $this->result_by_approved($approved); } if (!isset($result['source']) || $result['source'] === '') { $result['source'] = 'wp_default'; } if (!isset($result['reason']) || $result['reason'] === '') { $result['reason'] = 'wp_default_policy'; } if (!isset($result['akismet']) || $result['akismet'] === '') { $result['akismet'] = 'unknown'; } if (!isset($result['ai_score'])) { $result['ai_score'] = ''; } if (!isset($result['ai_flagged'])) { $result['ai_flagged'] = ''; } if (!isset($result['ai_top_category'])) { $result['ai_top_category'] = ''; } if (!isset($result['keyword'])) { $result['keyword'] = ''; } if (!isset($result['keyword_match_type'])) { $result['keyword_match_type'] = ''; } return $result; } private function result_by_approved(string $approved): string { if ($approved === 'spam') { return 'spam'; } if ($approved === '0') { return 'pending'; } if ($approved === 'trash') { return 'trash'; } return 'approved'; } private function status_label(string $approved): string { if ($approved === '1') { return 'Approved'; } if ($approved === '0') { return 'Pending'; } if ($approved === 'spam') { return 'Spam'; } if ($approved === 'trash') { return 'Trash'; } return ucfirst($approved); } private function comment_fingerprint(array $data): string { $post_id = (string) ($data['comment_post_ID'] ?? ''); $author = strtolower(trim((string) ($data['comment_author'] ?? ''))); $email = strtolower(trim((string) ($data['comment_author_email'] ?? ''))); $content_md5 = md5(trim((string) ($data['comment_content'] ?? ''))); $ip = trim((string) ($data['comment_author_IP'] ?? ($data['comment_author_ip'] ?? ''))); if ($post_id === '' && $author === '' && $email === '' && $content_md5 === md5('')) { return ''; } return md5(implode('|', [$post_id, $author, $email, $content_md5, $ip])); } // ============================================================ // 1. Akismet // ============================================================ private function check_akismet(array $data, array $config, string $ip) { if (empty($config['akismet_enabled'])) { return true; } $api_key = trim((string) ($config['akismet_api_key'] ?? '')); if ($api_key === '') { return 'error'; } $site_url = !empty($config['akismet_site_url']) ? (string) $config['akismet_site_url'] : home_url('/'); $payload = [ 'blog' => $site_url, 'blog_lang' => $config['akismet_blog_lang'] ?? 'zh-CN', 'blog_charset' => get_bloginfo('charset') ?: 'UTF-8', 'user_ip' => $ip, 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'referrer' => $_SERVER['HTTP_REFERER'] ?? '', 'comment_type' => 'comment', 'comment_author' => $data['comment_author'] ?? '', 'comment_author_email' => $data['comment_author_email'] ?? '', 'comment_author_url' => $data['comment_author_URL'] ?? ($data['comment_author_url'] ?? ''), 'comment_content' => $data['comment_content'] ?? '', 'is_test' => defined('WP_DEBUG') && WP_DEBUG ? '1' : '0', ]; $endpoint = sprintf('https://%s.rest.akismet.com/1.1/comment-check', $api_key); $response = wp_remote_post($endpoint, [ 'body' => http_build_query($payload), 'timeout' => 10, 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'], ]); if (is_wp_error($response)) { return 'error'; } $body = trim((string) wp_remote_retrieve_body($response)); if ($body === 'true') { return 'spam'; } if ($body === 'false') { return 'ham'; } return 'error'; } // ============================================================ // 2. AI Moderation // ============================================================ private function check_ai_moderation(array $data, array $config) { if (empty($config['ai_enabled']) || empty($config['ai_moderate_comments'])) { return true; } $content = trim((string) ($data['comment_content'] ?? '')); if ($content === '') { return true; } // 委托给统一内容审核服务 if (!class_exists('\AKSRT\Extensions\ContentAudit')) { return true; } $result = \AKSRT\Extensions\ContentAudit::get_instance()->audit_text($content); if ($result['passed']) { return true; } // 映射到审核风控的 score 模型 $score = 70; if (in_array('non_compliant', $result['labels'] ?? [], true)) { $score = 95; } return [ 'flagged' => 1, 'score' => $score, 'top_category' => $result['reason'] ?? 'content_audit_flagged', ]; } /** * 图片审核 — 委托给统一内容审核服务。 * * @param string $image_path Absolute path to the local image file. * @param array $config Optional config override (merged with get_config()). * @return array ['passed' => bool, 'reason' => string, ...] */ public function baidu_moderate_image(string $image_path, array $config = []): array { $config = array_merge($this->get_config(), $config); if (empty($config['ai_enabled']) || empty($config['ai_moderate_background'])) { return ['passed' => true, 'reason' => 'disabled']; } if (!class_exists('\AKSRT\Extensions\ContentAudit')) { return ['passed' => true, 'reason' => 'audit_service_missing']; } return \AKSRT\Extensions\ContentAudit::get_instance()->audit_image($image_path); } // ============================================================ // 3. Blocked keywords // ============================================================ private function check_blocked_keywords(array $data, array $config): array { $keywords_raw = (string) ($config['blocked_keywords'] ?? ''); if (trim($keywords_raw) === '') { return ['hit' => false, 'keyword' => '', 'match_type' => '']; } $parts = preg_split('/[\r\n,;、\x{FF0C}\x{FF1B}]+/u', $keywords_raw); $keywords = array_values(array_filter(array_map('trim', (array) $parts), static function ($v) { return $v !== ''; })); if (empty($keywords)) { return ['hit' => false, 'keyword' => '', 'match_type' => '']; } $text = implode(' ', [ (string) ($data['comment_content'] ?? ''), (string) ($data['comment_author'] ?? ''), (string) ($data['comment_author_email'] ?? ''), (string) ($data['comment_author_URL'] ?? ($data['comment_author_url'] ?? '')), ]); $text_norm = $this->normalize_text($text); foreach ($keywords as $keyword) { if ($keyword === '') { continue; } // User-defined regex keyword like /pattern/flags. if (preg_match('#^/.+/[imsxuADSUXJ]*$#', $keyword)) { $matched = @preg_match($keyword, $text); if ($matched === 1) { return ['hit' => true, 'keyword' => $keyword, 'match_type' => 'regex']; } continue; } if ($this->contains_text($text, $keyword)) { return ['hit' => true, 'keyword' => $keyword, 'match_type' => 'plain']; } $keyword_norm = $this->normalize_text($keyword); if ($keyword_norm !== '' && $this->contains_text($text_norm, $keyword_norm)) { return ['hit' => true, 'keyword' => $keyword, 'match_type' => 'normalized']; } } return ['hit' => false, 'keyword' => '', 'match_type' => '']; } private function contains_text(string $haystack, string $needle): bool { if ($needle === '') { return false; } if (function_exists('mb_stripos')) { return mb_stripos($haystack, $needle, 0, 'UTF-8') !== false; } return stripos($haystack, $needle) !== false; } }