__( 'Permission denied.', 'aiforya-faq-generator' ) ], 403 ); } check_ajax_referer( self::NONCE_ACTION, 'nonce' ); } /** * Nettoie une liste de paires question/réponse en entrée AJAX. * * @param mixed $raw Tableau brut [ [ 'question' => ..., 'answer' => ... ], ... ]. * @return array Items schema.org valides. */ private function sanitize_qa_items( $raw ): array { if ( ! is_array( $raw ) ) { return []; } $items = []; foreach ( $raw as $pair ) { if ( ! is_array( $pair ) ) { continue; } $q = isset( $pair['question'] ) ? sanitize_text_field( (string) $pair['question'] ) : ''; $a = isset( $pair['answer'] ) ? wp_kses_post( (string) $pair['answer'] ) : ''; $q = trim( $q ); $a = trim( $a ); if ( '' === $q || '' === $a ) { continue; } $items[] = [ '@type' => 'Question', 'name' => $q, 'acceptedAnswer' => [ '@type' => 'Answer', 'text' => $a, ], ]; } return $items; } // ========================================================================= // Réglages d'affichage. // ========================================================================= public function handle_save_settings(): void { $this->guard(); $db = $this->db(); $cur = $db->get_settings(); $post = wp_unslash( $_POST ); $position = sanitize_key( (string) ( $post['position'] ?? $cur['position'] ) ); $cur['position'] = in_array( $position, [ 'manual', 'after', 'before' ], true ) ? $position : 'manual'; $layout = sanitize_key( (string) ( $post['layout'] ?? $cur['layout'] ) ); $cur['layout'] = in_array( $layout, [ 'accordion', 'cards', 'list', 'numbered', 'minimal' ], true ) ? $layout : 'accordion'; $cur['default_title'] = sanitize_text_field( (string) ( $post['default_title'] ?? $cur['default_title'] ) ); foreach ( [ 'color_primary', 'color_bg', 'color_text', 'color_question' ] as $ck ) { $val = sanitize_hex_color( (string) ( $post[ $ck ] ?? '' ) ); if ( $val ) { $cur[ $ck ] = $val; } } $db->save_settings( $cur ); wp_send_json_success( [ 'message' => __( 'Settings saved.', 'aiforya-faq-generator' ), 'settings' => $cur, ] ); } // ========================================================================= // Éditeur manuel de FAQ. // ========================================================================= public function handle_save_faq(): void { $this->guard(); $post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) ); if ( $post_id <= 0 || ! get_post( $post_id ) ) { wp_send_json_error( [ 'message' => __( 'Invalid post.', 'aiforya-faq-generator' ) ], 400 ); } $items = $this->sanitize_qa_items( map_deep( wp_unslash( $_POST['items'] ?? [] ), 'wp_kses_post' ) ); // Aucune question valide → suppression de la FAQ existante. if ( empty( $items ) ) { $this->db()->delete_faq_by_post_id( $post_id ); wp_send_json_success( [ 'message' => __( 'FAQ emptied (no questions).', 'aiforya-faq-generator' ), 'count' => 0, ] ); } $schema = [ '@context' => 'https://schema.org', '@type' => 'FAQPage', 'mainEntity' => $items, ]; $post = get_post( $post_id ); $source_hash = md5( $post->post_title . "\n\n" . wp_strip_all_tags( $post->post_content ) ); $result = $this->db()->save_faq( $post_id, wp_json_encode( $schema ), $source_hash ); if ( is_wp_error( $result ) ) { wp_send_json_error( [ 'message' => $result->get_error_message() ], 400 ); } wp_send_json_success( [ 'message' => __( 'FAQ saved.', 'aiforya-faq-generator' ), 'count' => count( $items ), ] ); } public function handle_delete_faq(): void { $this->guard(); $post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) ); if ( $post_id <= 0 ) { wp_send_json_error( [ 'message' => __( 'Invalid post.', 'aiforya-faq-generator' ) ], 400 ); } $this->db()->delete_faq_by_post_id( $post_id ); wp_send_json_success( [ 'message' => __( 'FAQ deleted.', 'aiforya-faq-generator' ) ] ); } public function handle_get_post_content(): void { $this->guard(); $post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) ); $post = $post_id > 0 ? get_post( $post_id ) : null; if ( ! $post ) { wp_send_json_error( [ 'message' => __( 'Post not found.', 'aiforya-faq-generator' ) ], 400 ); } $existing = $this->db()->get_faq_by_post_id( $post_id ); $items = []; if ( $existing && ! empty( $existing->content_json ) ) { $data = json_decode( (string) $existing->content_json, true ); if ( ! empty( $data['mainEntity'] ) && is_array( $data['mainEntity'] ) ) { foreach ( $data['mainEntity'] as $it ) { if ( ! empty( $it['name'] ) && ! empty( $it['acceptedAnswer']['text'] ) ) { $items[] = [ 'question' => (string) $it['name'], 'answer' => (string) $it['acceptedAnswer']['text'], ]; } } } } wp_send_json_success( [ 'post_title' => $post->post_title, 'items' => $items, 'has_faq' => ! empty( $items ), ] ); } // ========================================================================= // Détection automatique (parser règle-based, sans IA, 0 réseau). // ========================================================================= public function handle_detect_faq(): void { $this->guard(); $post_id = absint( wp_unslash( $_POST['post_id'] ?? 0 ) ); $post = $post_id > 0 ? get_post( $post_id ) : null; if ( ! $post ) { wp_send_json_error( [ 'message' => __( 'Post not found.', 'aiforya-faq-generator' ) ], 400 ); } $parser = new Aiforya_Fg_Parser(); $items = $parser->extract_from_content( (string) $post->post_content ); if ( empty( $items ) ) { wp_send_json_error( [ 'message' => __( 'No question detected. Add headings (H2/H3) ending with a question mark followed by an answer paragraph, or enter your questions by hand.', 'aiforya-faq-generator' ), ] ); } wp_send_json_success( [ 'items' => $items, 'count' => count( $items ), 'message' => sprintf( /* translators: %d: nombre de questions détectées */ _n( '%d question detected.', '%d questions detected.', count( $items ), 'aiforya-faq-generator' ), count( $items ) ), ] ); } }