*/ public function extract_from_content( string $content ): array { // Applique les shortcodes/blocs pour obtenir du HTML rendu, puis on parse. $html = trim( $content ); if ( '' === $html ) { return []; } if ( function_exists( 'do_blocks' ) ) { $html = do_blocks( $html ); } $html = wpautop( $html ); $items = $this->extract_from_headings( $html ); // Repli : si aucun titre interrogatif, on tente les paragraphes en gras. if ( empty( $items ) ) { $items = $this->extract_from_bold_paragraphs( $html ); } return array_slice( $items, 0, self::MAX_QUESTIONS ); } /** * Règle principale : titres H2/H3/H4 interrogatifs → question, * blocs suivants (jusqu'au prochain titre) → réponse. * * @return array */ private function extract_from_headings( string $html ): array { // Découpe le HTML en segments à chaque titre Hn (n = 2..4). $parts = preg_split( '/(]*>.*?<\/h[2-4]>)/is', $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); if ( ! is_array( $parts ) || count( $parts ) < 2 ) { return []; } $items = []; $pending_question = null; foreach ( $parts as $segment ) { if ( preg_match( '/^]*>(.*?)<\/h[2-4]>$/is', $segment, $m ) ) { // C'est un titre. On finalise la question précédente sans réponse. $heading = $this->clean_text( $m[1] ); if ( $this->is_question( $heading ) ) { $pending_question = $heading; } else { $pending_question = null; } } elseif ( null !== $pending_question ) { // Contenu après un titre interrogatif → réponse. $answer = $this->clean_answer( $segment ); if ( '' !== $answer ) { $items[] = [ 'question' => $pending_question, 'answer' => $answer ]; $pending_question = null; } } } return $items; } /** * Règle de repli : paragraphes dont le texte en gras se termine par « ? ». * Ex :

Question ? Réponse…

. * * @return array */ private function extract_from_bold_paragraphs( string $html ): array { if ( ! preg_match_all( '/]*>(.*?)<\/p>/is', $html, $matches ) ) { return []; } $items = []; foreach ( $matches[1] as $inner ) { if ( preg_match( '/^\s*<(?:strong|b)[^>]*>(.*?)<\/(?:strong|b)>(.*)$/is', $inner, $m ) ) { $question = $this->clean_text( $m[1] ); $answer = $this->clean_answer( $m[2] ); if ( $this->is_question( $question ) && '' !== $answer ) { $items[] = [ 'question' => $question, 'answer' => $answer ]; } } } return $items; } /** * Une chaîne est-elle une question ? (se termine par « ? » après nettoyage). */ private function is_question( string $text ): bool { $text = trim( $text ); if ( '' === $text || mb_strlen( $text ) < 6 ) { return false; } // Retire la ponctuation finale résiduelle et vérifie le « ? ». return (bool) preg_match( '/\?\s*$/u', $text ); } /** * Nettoie un fragment HTML → texte simple (titre). */ private function clean_text( string $html ): string { $text = wp_strip_all_tags( $html ); $text = html_entity_decode( $text, ENT_QUOTES, 'UTF-8' ); $text = preg_replace( '/\s+/u', ' ', $text ); return trim( (string) $text ); } /** * Nettoie le HTML de réponse (paragraphes + listes autorisés), longueur bornée. */ private function clean_answer( string $html ): string { // On conserve un HTML minimal sûr (mise en forme + listes + liens). $allowed = [ 'p' => [], 'br' => [], 'strong' => [], 'b' => [], 'em' => [], 'i' => [], 'ul' => [], 'ol' => [], 'li' => [], 'a' => [ 'href' => [], 'title' => [], 'target' => [], 'rel' => [] ], 'code' => [], ]; $clean = wp_kses( $html, $allowed ); $clean = trim( $clean ); // Si le texte utile est vide (uniquement des balises), on ignore. if ( '' === trim( wp_strip_all_tags( $clean ) ) ) { return ''; } // Borne de sécurité : ~2000 caractères de texte visible. if ( mb_strlen( wp_strip_all_tags( $clean ) ) > 2000 ) { $clean = mb_substr( trim( wp_strip_all_tags( $clean ) ), 0, 2000 ) . '…'; } return $clean; } }