| @@ -1,6 +1,10 @@ | ||
| 1 | 1 | <?php |
| 2 | - | |
| 2 | +// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- core docs taxonomy REST endpoints; meta filtering required. | |
| 3 | +// phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- endpoint exposes user-driven exclusion. | |
| 4 | +// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table identifiers (WP-provided) and dynamic %s placeholders are intentional. | |
| 5 | +// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- the uncategorized-docs scan needs raw SQL because WP_Query has no NOT-IN-via-subquery primitive. | |
| 6 | +// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- list is rebuilt per-request from live post/term state; cache would mask uncategorized status changes. | |
| 3 | 7 | namespace WPDeveloper\BetterDocs\REST; |
| 4 | 8 | |
| 5 | 9 | use stdClass; |
| 6 | 10 | use WPDeveloper\BetterDocs\Core\BaseAPI; |
| @@ -14,9 +18,9 @@ | ||
| 14 | 18 | |
| 15 | 19 | public function register() { |
| 16 | 20 | $this->get( 'doc-categories', array( $this, 'get_response' ), array( |
| 17 | 21 | 'password' => array( |
| 18 | - 'description' => __( 'The password for password-protected docs.' ), | |
| 22 | + 'description' => __( 'The password for password-protected docs.', 'betterdocs' ), | |
| 19 | 23 | 'type' => 'string' |
| 20 | 24 | ) |
| 21 | 25 | ) ); |
| 22 | 26 | $this->get( 'doc-categories-kb', array( $this, 'doc_categories_kb_response' ) ); |
| @@ -190,9 +194,9 @@ | ||
| 190 | 194 | $posts = betterdocs()->query->get_posts( $query_args, true ); |
| 191 | 195 | $response[ $term->term_id ] = array(); |
| 192 | 196 | |
| 193 | 197 | if ( ! $posts->have_posts() ) { |
| 194 | - wp_reset_query(); | |
| 198 | + wp_reset_postdata(); | |
| 195 | 199 | } |
| 196 | 200 | while ( $posts->have_posts() ): |
| 197 | 201 | $posts->the_post(); |
| 198 | 202 | $post_obj = get_post( get_the_ID() ); |
| @@ -209,9 +213,9 @@ | ||
| 209 | 213 | array_push( $response[ $term->term_id ], $data ); |
| 210 | 214 | endwhile; |
| 211 | 215 | |
| 212 | 216 | wp_reset_postdata(); |
| 213 | - wp_reset_query(); | |
| 217 | + wp_reset_query(); // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- explicit global WP_Query reset after a custom loop; wp_reset_postdata() above only restores post data. | |
| 214 | 218 | |
| 215 | 219 | // WP_Query's `orderby=post__in` is stripped by some plugins/filters |
| 216 | 220 | // (notably WPML on REST requests), so apply the saved order in PHP |
| 217 | 221 | // here as the source of truth. Posts not in the saved order are |
| @@ -224,16 +228,20 @@ | ||
| 224 | 228 | |
| 225 | 229 | /** |
| 226 | 230 | * Uncategories Docs |
| 227 | 231 | */ |
| 228 | - // Build secure query for uncategorized docs with proper post status filtering | |
| 232 | + // Build secure query for uncategorized docs with proper post status filtering. | |
| 233 | + // $wpdb->posts / $wpdb->term_relationships / $wpdb->term_taxonomy are WP-provided | |
| 234 | + // table identifiers (safe to interpolate). Dynamic %s placeholder count is built | |
| 235 | + // from a fixed-shape $post_status array. | |
| 229 | 236 | $post_status_placeholders = implode( ',', array_fill( 0, count( $post_status ), '%s' ) ); |
| 230 | - $_post__not_in_query = $wpdb->prepare( | |
| 237 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- WP table identifiers; placeholders dynamically generated to match $post_status size. | |
| 238 | + $_post__not_in_query = $wpdb->prepare( | |
| 231 | 239 | "SELECT ID as post_id from $wpdb->posts WHERE post_type = %s AND post_status IN ($post_status_placeholders) AND post_status != 'trash' AND post_status != 'auto-draft' AND ID NOT IN ( SELECT object_id as post_id FROM $wpdb->term_relationships WHERE term_taxonomy_id IN ( SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s ) )", |
| 232 | 240 | array_merge( array( 'docs' ), $post_status, array( 'doc_category' ) ) |
| 233 | 241 | ); |
| 234 | 242 | |
| 235 | - $_post__not_in = $wpdb->get_col( $_post__not_in_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared | |
| 243 | + $_post__not_in = $wpdb->get_col( $_post__not_in_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- query is prepared above. | |
| 236 | 244 | |
| 237 | 245 | if ( ! empty( $_post__not_in ) ) { |
| 238 | 246 | $uncategorized_docs = array(); |
| 239 | 247 | $uncategorized_query_args = array( |
| @@ -249,9 +257,9 @@ | ||
| 249 | 257 | |
| 250 | 258 | $_uncategorized_docs_query = new \WP_Query( $uncategorized_query_args ); |
| 251 | 259 | |
| 252 | 260 | if ( ! $_uncategorized_docs_query->have_posts() ) { |
| 253 | - wp_reset_query(); | |
| 261 | + wp_reset_postdata(); | |
| 254 | 262 | } |
| 255 | 263 | while ( $_uncategorized_docs_query->have_posts() ): |
| 256 | 264 | $_uncategorized_docs_query->the_post(); |
| 257 | 265 | $post_obj = get_post( get_the_ID() ); |
| @@ -268,9 +276,9 @@ | ||
| 268 | 276 | array_push( $uncategorized_docs, $data ); |
| 269 | 277 | endwhile; |
| 270 | 278 | |
| 271 | 279 | wp_reset_postdata(); |
| 272 | - wp_reset_query(); | |
| 280 | + wp_reset_postdata(); | |
| 273 | 281 | |
| 274 | 282 | $response[ 'uncategorized' ] = $uncategorized_docs; |
| 275 | 283 | } |
| 276 | 284 | |