| @@ -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,16 +103,10 @@ | ||
| 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 ); | |
| 134 | - | |
| 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 ) ); | |
| 107 | + // Clean up the summary content | |
| 108 | + $cleaned_summary = $this->clean_summary_content( $summary ); | |
| 140 | 109 | |
| 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 ); |