PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
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/Admin/CSVExporter.php +26 -7 4.5.54.9.2 View file →
@@ -621,9 +621,9 @@
621 621 $post->post_parent,
622 622 $post->menu_order,
623 623 $post->post_mime_type,
624 624 $post->comment_count,
625 - $this->get_term_ids( $post->ID, $post->post_type === 'betterdocs_faq' ? 'betterdocs_faq_category' : 'doc_category' ),
625 + $this->get_term_ids( $post->ID, $post->post_type === 'betterdocs_faq' ? [ 'betterdocs_faq_category', 'betterdocs_product_faq_category' ] : 'doc_category' ),
626 626 $this->get_term_ids( $post->ID, 'doc_tag' ),
627 627 $this->get_term_ids( $post->ID, 'knowledge_base' ),
628 628 $attachment_url ? $attachment_url : '',
629 629 $attachment_id ? $attachment_id : '',
@@ -635,13 +635,15 @@
635 635 return $csv_data_posts;
636 636 }
637 637
638 638 public function get_term_ids( $post_id, $taxonomy ) {
639 - $terms = get_the_terms( $post_id, $taxonomy );
639 + // Accept one or more taxonomies. FAQ posts can live in either the general
640 + // (betterdocs_faq_category) or the Product FAQ (betterdocs_product_faq_category)
641 + // taxonomy, so both are queried for the FAQ group column.
642 + $term_ids = wp_get_object_terms( $post_id, (array) $taxonomy, [ 'fields' => 'ids' ] );
640 643
641 - if ( $terms && ! is_wp_error( $terms ) ) {
642 - $term_ids = wp_list_pluck( $terms, 'term_id' );
643 - return implode( ', ', $term_ids );
644 + if ( $term_ids && ! is_wp_error( $term_ids ) ) {
645 + return implode( ', ', array_map( 'intval', $term_ids ) );
644 646 }
645 647
646 648 return '';
647 649 }
@@ -650,11 +652,14 @@
650 652 ob_start();
651 653
652 654 $output = fopen( 'php://output', 'w' );
653 655
654 - // Add CSV rows
656 + // Add CSV rows. Neutralize spreadsheet formula injection: a cell that a
657 + // lower-privileged author controls (e.g. a doc/FAQ title or term name) could
658 + // start with =, +, -, @, or a tab/CR and execute when the admin opens the
659 + // export in Excel/LibreOffice. Prefix such cells with a single quote.
655 660 foreach ( $data as $row ) {
656 - fputcsv( $output, $row );
661 + fputcsv( $output, array_map( [ $this, 'neutralize_csv_cell' ], (array) $row ) );
657 662 }
658 663
659 664 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- closing php://output stream; WP_Filesystem does not apply.
660 665 fclose( $output );
@@ -659,6 +664,20 @@
659 664 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- closing php://output stream; WP_Filesystem does not apply.
660 665 fclose( $output );
661 666
662 667 return ob_get_clean();
668 + }
669 +
670 + /**
671 + * Prefix a leading formula trigger (= + - @ tab CR) with a single quote so
672 + * spreadsheet apps treat the cell as text instead of executing it.
673 + */
674 + private function neutralize_csv_cell( $cell ) {
675 + $cell = (string) $cell;
676 +
677 + if ( $cell !== '' && preg_match( '/^[=+\-@\t\r]/', $cell ) ) {
678 + return "'" . $cell;
679 + }
680 +
681 + return $cell;
663 682 }
664 683 }