posts_repository = $posts_repository; $this->content_types_repository = $content_types_repository; $this->content_type_access_checker = $content_type_access_checker; $this->user_helper = $user_helper; $this->options_helper = $options_helper; $this->post_type_helper = $post_type_helper; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { \register_rest_route( self::ROUTE_NAMESPACE, self::ROUTE_PREFIX, [ 'methods' => 'GET', 'args' => [ 'content_type' => [ 'required' => true, 'type' => 'string', 'description' => 'The content type to fetch posts for.', 'sanitize_callback' => 'sanitize_text_field', ], 'per_page' => [ 'required' => false, 'type' => 'integer', 'default' => self::DEFAULT_PER_PAGE, 'minimum' => 1, 'maximum' => self::MAX_PER_PAGE, 'description' => 'The number of posts to fetch.', 'sanitize_callback' => 'absint', ], 'page' => [ 'required' => false, 'type' => 'integer', 'default' => 1, 'minimum' => 1, 'description' => 'The page of posts to fetch.', 'sanitize_callback' => 'absint', ], 'search' => [ 'required' => false, 'type' => 'string', 'default' => '', 'description' => 'The term to search posts by.', 'sanitize_callback' => 'sanitize_text_field', ], 'status' => [ 'required' => false, 'type' => 'array', 'default' => Posts_Collector_Interface::STATUSES, 'items' => [ 'type' => 'string', 'enum' => Posts_Collector_Interface::STATUSES, ], 'description' => 'The post statuses to include.', ], 'needs_improvement' => [ 'required' => false, 'type' => 'array', 'default' => [], 'items' => [ 'type' => 'string', 'enum' => Posts_Collector_Interface::NEEDS_IMPROVEMENT_FIELDS, ], 'description' => 'The fields to filter posts by; a field matches when it is empty, or (for search fields, while SEO analysis is enabled and the content type shows Yoast\'s controls and assessments) when its score needs improvement.', ], 'include' => [ 'required' => false, 'type' => 'array', 'default' => [], 'maxItems' => self::MAX_PER_PAGE, 'items' => [ 'type' => 'integer', 'minimum' => 1, ], 'description' => 'Limits the posts to these post IDs, e.g. a selection carried over from the posts overview.', ], ], 'callback' => [ $this, 'get_posts' ], 'permission_callback' => [ $this, 'check_permissions' ], ], ); } /** * Returns a page of posts for the requested content type. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response|WP_Error The posts, or an error when the content type is not editable. */ public function get_posts( WP_REST_Request $request ) { $content_type = $request->get_param( 'content_type' ); if ( ! $this->is_valid_content_type( $content_type ) ) { return new WP_Error( 'rest_invalid_content_type', 'The requested content type is not editable.', [ 'status' => 400 ], ); } // An empty selection (no status filter) means all statuses, never zero results. $statuses = (array) $request->get_param( 'status' ); if ( empty( $statuses ) ) { $statuses = Posts_Collector_Interface::STATUSES; } // Narrow the query to the user's own posts when they cannot edit other authors' posts. This keeps // pagination cheap; the exact per-post edit permission is still enforced when collecting the page. $author_id = null; if ( ! $this->content_type_access_checker->can_edit_others( $content_type ) ) { $author_id = $this->user_helper->get_current_user_id(); } // The per-field scores only back the filter while SEO analysis is on globally and the content type // shows Yoast's controls and assessments; otherwise they go stale and the filter falls back to the // empty-field check. $scores_enabled = $this->options_helper->get( 'keyword_analysis_active' ) === true && $this->post_type_helper->has_metabox( $content_type ); // The schema already coerces the items to positive integers; deduplicate on top of that. $include = \array_values( \array_unique( \array_map( 'intval', (array) $request->get_param( 'include' ) ) ) ); $query = new Posts_Query( $content_type, (int) $request->get_param( 'page' ), (int) $request->get_param( 'per_page' ), (string) $request->get_param( 'search' ), $statuses, $author_id, (array) $request->get_param( 'needs_improvement' ), $scores_enabled, $include, ); // Posts the current user cannot edit are returned locked and without their SEO data; the per-post // permission is resolved while collecting the page. return new WP_REST_Response( $this->posts_repository->get_posts( $query )->to_array() ); } /** * Checks whether the current user is allowed to use the bulk editor. * * @return bool Whether the current user is allowed to use the bulk editor. */ public function check_permissions(): bool { return \current_user_can( 'wpseo_manage_options' ); } /** * Checks whether the given content type is one the bulk editor can edit. * * @param string $content_type The content type to check. * * @return bool Whether the content type is editable. */ private function is_valid_content_type( string $content_type ): bool { foreach ( $this->content_types_repository->get_content_types() as $editable ) { if ( $editable['name'] === $content_type ) { return true; } } return false; } }