| @@ -10,8 +10,19 @@ | ||
| 10 | 10 | if ( ! defined( 'ABSPATH' ) ) { |
| 11 | 11 | exit; // Exit if accessed directly. |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | +/** | |
| 15 | + * SQL building helpers below interpolate WP-provided $wpdb table identifiers | |
| 16 | + * (`$wpdb->posts`, `$wpdb->term_relationships`, `$wpdb->term_taxonomy`) and use | |
| 17 | + * dynamic %d placeholder lists derived from integer arrays. Suppressing the | |
| 18 | + * related PHPCS notices class-wide rather than per-call. | |
| 19 | + * | |
| 20 | + * phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 21 | + * phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare | |
| 22 | + * phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery | |
| 23 | + * phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching | |
| 24 | + */ | |
| 14 | 25 | #[\AllowDynamicProperties] |
| 15 | 26 | class WPExporter { |
| 16 | 27 | /** |
| 17 | 28 | * @var array |
| @@ -61,9 +72,10 @@ | ||
| 61 | 72 | |
| 62 | 73 | // Handle additional filters (author, dates, meta) |
| 63 | 74 | $where .= $this->build_additional_filters(); |
| 64 | 75 | |
| 65 | - // Get the main doc post IDs | |
| 76 | + // $join/$where are composed from prepared fragments above; identifiers are WP-provided. | |
| 77 | + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 66 | 78 | $post_ids = $this->wpdb->get_col( "SELECT DISTINCT {$this->wpdb->posts}.ID FROM {$this->wpdb->posts} $join WHERE $where" ); |
| 67 | 79 | |
| 68 | 80 | // Handle FAQ posts separately |
| 69 | 81 | $faq_post_ids = []; |
| @@ -73,8 +85,10 @@ | ||
| 73 | 85 | |
| 74 | 86 | // Combine post IDs |
| 75 | 87 | $all_post_ids = array_merge( $post_ids, $faq_post_ids ); |
| 76 | 88 | |
| 89 | + $all_post_ids = WPMLSupport::expand_with_translations( $all_post_ids ); | |
| 90 | + | |
| 77 | 91 | // Handle featured images |
| 78 | 92 | $thumbnail_ids = $this->get_thumbnail_ids( $all_post_ids ); |
| 79 | 93 | |
| 80 | 94 | // Generate final post IDs array |
| @@ -92,14 +106,26 @@ | ||
| 92 | 106 | array_push($glossary_term_ids, $term_object->term_id); |
| 93 | 107 | } |
| 94 | 108 | } |
| 95 | 109 | } else { |
| 96 | - $glossary_term_ids = $this->wpdb->get_col( "SELECT term_id from {$this->wpdb->term_taxonomy} where taxonomy='{$this->args['content']}';" ); | |
| 110 | + // $this->wpdb->term_taxonomy is a WP-core table identifier; %s placeholder binds taxonomy name. | |
| 111 | + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 112 | + $glossary_term_ids = $this->wpdb->get_col( | |
| 113 | + $this->wpdb->prepare( | |
| 114 | + "SELECT term_id FROM {$this->wpdb->term_taxonomy} WHERE taxonomy = %s", | |
| 115 | + (string) $this->args['content'] | |
| 116 | + ) | |
| 117 | + ); | |
| 118 | + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter | |
| 97 | 119 | } |
| 98 | 120 | |
| 99 | 121 | return $glossary_term_ids; |
| 100 | 122 | } |
| 101 | 123 | |
| 124 | + // The query-builder methods below interpolate only $wpdb core table names | |
| 125 | + // ({$this->wpdb->posts}, etc.) — never user input — and bind every value via | |
| 126 | + // %s/%d placeholders, so the InterpolatedNotPrepared warnings are spurious. | |
| 127 | + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 102 | 128 | public function build_base_query(): array { |
| 103 | 129 | $where = $this->wpdb->prepare("{$this->wpdb->posts}.post_type = %s", 'docs'); |
| 104 | 130 | return [ |
| 105 | 131 | 'where' => $where, |
| @@ -197,14 +223,17 @@ | ||
| 197 | 223 | |
| 198 | 224 | return $where; |
| 199 | 225 | } |
| 200 | 226 | |
| 227 | + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 201 | 228 | public function get_faq_posts(): array { |
| 202 | 229 | return get_posts([ |
| 203 | - 'numberposts' => -1, | |
| 204 | - 'post_type' => 'betterdocs_faq', | |
| 205 | - 'fields' => 'ids', | |
| 206 | - 'post_status' => 'publish' | |
| 230 | + 'numberposts' => -1, | |
| 231 | + 'post_type' => 'betterdocs_faq', | |
| 232 | + 'fields' => 'ids', | |
| 233 | + 'post_status' => 'publish', | |
| 234 | + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- intentional: export the raw, untranslated FAQ set so multilingual filters don't drop or swap rows during export. | |
| 235 | + 'suppress_filters' => true, | |
| 207 | 236 | ]); |
| 208 | 237 | } |
| 209 | 238 | |
| 210 | 239 | public function get_thumbnail_ids(array $post_ids): array { |