PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.6
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
← All changes | includes/Core/ArticleSummary.php +22 -53 4.9.24.2.6 View file →
@@ -1,16 +1,11 @@
1 1 <?php
2 +
2 3 namespace WPDeveloper\BetterDocs\Core;
3 4
4 -if ( ! defined( 'ABSPATH' ) ) {
5 - exit;
6 -}
7 -
8 -
9 5 use WPDeveloper\BetterDocs\Utils\Base;
10 6 use WPDeveloper\BetterDocs\Core\Settings;
11 7 use WPDeveloper\BetterDocs\Utils\AIHelper;
12 -use WPDeveloper\BetterDocs\Utils\AIUsage;
13 8
14 9 class ArticleSummary extends Base {
15 10
16 11 public $settings;
@@ -56,50 +51,32 @@
56 51 wp_die();
57 52 }
58 53
59 54 // Verify the nonce
60 - $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
61 - if ( ! wp_verify_nonce( $nonce, 'betterdocs_article_summary_nonce' ) ) {
55 + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'betterdocs_article_summary_nonce' ) ) { //phpcs:ignore
62 56 wp_send_json_error( 'Invalid nonce' );
63 57 wp_die();
64 58 }
65 59
66 - $post_id = isset( $_POST['post_id'] ) ? intval( wp_unslash( $_POST['post_id'] ) ) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
60 + $post_id = intval( $_POST['post_id'] ); //phpcs:ignore
61 + $post_title = sanitize_text_field( $_POST['post_title'] ); //phpcs:ignore
62 + $post_content = wp_kses_post( $_POST['post_content'] ); //phpcs:ignore
67 63
68 - // A valid documentation post is required. The summary is always derived from
69 - // the canonical post record below, never from anything in the request body.
70 - if ( $post_id <= 0 ) {
71 - wp_send_json_error( 'Invalid document.' );
72 - wp_die();
73 - }
64 + // Validate that this is a docs post type
65 + if ( $post_id > 0 ) {
66 + $post_type = get_post_type( $post_id );
67 + if ( $post_type !== 'docs' ) {
68 + wp_send_json_error( 'Doc Summarizer is only available for documentation posts.' );
69 + wp_die();
70 + }
74 71
75 - $post = get_post( $post_id );
76 -
77 - // Validate that this is a docs post type.
78 - if ( ! $post || $post->post_type !== 'docs' ) {
79 - wp_send_json_error( 'Doc Summarizer is only available for documentation posts.' );
80 - wp_die();
72 + // Check if post is password protected and user hasn't provided correct password
73 + if ( post_password_required( $post_id ) ) {
74 + wp_send_json_error( 'This document is password protected. Please enter the correct password to access the summary.' );
75 + wp_die();
76 + }
81 77 }
82 78
83 - // Only summarize content the requester is actually allowed to view, so an
84 - // unauthenticated caller can't trigger summaries of drafts/private docs.
85 - if ( 'publish' !== $post->post_status && ! current_user_can( 'read_post', $post_id ) ) {
86 - wp_send_json_error( 'Doc Summarizer is only available for published documentation posts.' );
87 - wp_die();
88 - }
89 -
90 - // Check if post is password protected and user hasn't provided correct password.
91 - if ( post_password_required( $post_id ) ) {
92 - wp_send_json_error( 'This document is password protected. Please enter the correct password to access the summary.' );
93 - wp_die();
94 - }
95 -
96 - // SECURITY (fbs-82814): never trust client-supplied title/content. Load the
97 - // canonical values server-side so prompt-injection payloads in the request
98 - // body cannot reach the AI model or influence the cache key.
99 - $post_title = $post->post_title;
100 - $post_content = wp_strip_all_tags( $post->post_content );
101 -
102 79 if ( empty( $post_content ) ) {
103 80 wp_send_json_error( 'No content provided for summary generation.' );
104 81 wp_die();
105 82 }
@@ -111,12 +88,10 @@
111 88 $stored_hash = get_post_meta( $post_id, '_betterdocs_article_summary_hash', true );
112 89
113 90 // Return existing summary if content hasn't changed
114 91 if ( ! empty( $existing_summary ) && $content_hash === $stored_hash ) {
115 - // Clean existing summary in case it has old formatting, and run it
116 - // through wp_kses_post() so any previously-stored poisoned markup is
117 - // neutralized before it is returned to the browser. (fbs-82814)
118 - $cleaned_existing = wp_kses_post( $this->clean_summary_content( $existing_summary ) );
92 + // Clean existing summary in case it has old formatting
93 + $cleaned_existing = $this->clean_summary_content( $existing_summary );
119 94 wp_send_json_success( $cleaned_existing );
120 95 wp_die();
121 96 }
122 97 }
@@ -128,17 +103,11 @@
128 103 wp_send_json_error( $summary->get_error_message() );
129 104 wp_die();
130 105 }
131 106
132 - // Count only fresh generations (cache hits returned earlier).
133 - AIUsage::record( 'article_summary', $post_id );
107 + // Clean up the summary content
108 + $cleaned_summary = $this->clean_summary_content( $summary );
134 109
135 - // Clean up the summary content and sanitize the AI output with wp_kses_post()
136 - // before it is stored or returned. This strips dangerous attributes/tags
137 - // (onerror, <script>, etc.) even if a prompt injection ever succeeds, while
138 - // preserving safe formatting like <p>/<strong>/<ul>. (fbs-82814)
139 - $cleaned_summary = wp_kses_post( $this->clean_summary_content( $summary ) );
140 -
141 110 // Store summary in post meta if post ID is provided
142 111 if ( $post_id > 0 && ! empty( $cleaned_summary ) ) {
143 112 update_post_meta( $post_id, '_betterdocs_article_summary', $cleaned_summary );
144 113 update_post_meta( $post_id, '_betterdocs_article_summary_hash', md5( $post_content ) );
@@ -162,9 +131,9 @@
162 131 }
163 132
164 133 try {
165 134 if ( ! $this->ai_helper->has_api_key() ) {
166 - return new \WP_Error( 'no_api_key', 'AI API key is not configured. Please add your API key in BetterDocs settings.' );
135 + return new \WP_Error( 'no_api_key', 'OpenAI API key is not configured. Please add your API key in BetterDocs settings.' );
167 136 }
168 137
169 138 // Prepare content for AI processing
170 139 $prepared_content = $this->ai_helper->prepare_content_for_ai( $content, 4000 );