client = new CGARD_AI_Client(); } /** * @param array $finding One row from CGARD_Db::get_findings(). * @return string|WP_Error */ public function explain_finding( array $finding ) { $cache_key = 'cgard_ai_explain_' . md5( $finding['check_key'] . $finding['description'] ); $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } $result = $this->client->complete( $this->build_finding_prompt( $finding ), 500 ); if ( is_wp_error( $result ) ) { return $result; } set_transient( $cache_key, $result, self::CACHE_TTL ); return $result; } /** * @param array $findings Open findings, as from CGARD_Db::get_findings( 'open' ). * @param string $site_name * @return string|WP_Error */ public function generate_incident_report( array $findings, $site_name ) { if ( empty( $findings ) ) { return sprintf( /* translators: %s: site name */ __( '%s currently has no open security findings — nothing to report.', 'clarityguard' ), $site_name ); } $cache_key = 'cgard_ai_report_' . md5( $site_name . wp_json_encode( wp_list_pluck( $findings, 'check_key' ) ) ); $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } $result = $this->client->complete( $this->build_report_prompt( $findings, $site_name ), 900 ); if ( is_wp_error( $result ) ) { return $result; } set_transient( $cache_key, $result, self::CACHE_TTL ); return $result; } private function build_finding_prompt( array $finding ) { return sprintf( "You are a security assistant helping a non-technical WordPress site owner understand one scan finding.\n\n" . "Finding: %s\nSeverity: %s\nTechnical description: %s\nStandard fix already shown to the user: %s\n\n" . "Write a short, friendly explanation (about 100-150 words) covering: (1) what this actually means in plain " . "language, (2) a realistic worst-case scenario if it's left unfixed, and (3) how urgent it really is relative " . "to the stated severity. Do not repeat the fix steps verbatim — the user already has those above. " . "Write 2-3 short plain-text paragraphs, no markdown headers or bullet points.", $finding['title'], strtoupper( $finding['severity'] ), $finding['description'], $finding['fix_steps'] ); } private function build_report_prompt( array $findings, $site_name ) { $lines = array(); foreach ( $findings as $finding ) { $lines[] = sprintf( '- [%s] %s: %s', strtoupper( $finding['severity'] ), $finding['title'], $finding['description'] ); } return sprintf( "Write a short, professional security incident report that an agency could hand directly to a client, based " . "on the following WordPress security scan findings for the site \"%s\".\n\n" . "Structure it as: a one-paragraph executive summary; a prioritized list of issues (most urgent first) each " . "with a one-sentence business-risk explanation; and a short closing recommendation paragraph. Keep the whole " . "thing under 400 words, plain text (no markdown headers), professional but not alarmist tone.\n\nFindings:\n%s", $site_name, implode( "\n", $lines ) ); } }