'object', 'properties' => [ 'posts' => [ 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'type' => 'integer' ], 'title' => [ 'type' => 'string' ], 'post_type' => [ 'type' => 'string' ], 'status' => [ 'type' => 'string' ], 'date' => [ 'type' => 'string' ], 'modified' => [ 'type' => 'string' ], 'url' => [ 'type' => 'string' ], 'author' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'type' => 'integer' ], 'name' => [ 'type' => 'string' ], ], ], ], ], ], 'total' => [ 'type' => 'integer' ], 'page' => [ 'type' => 'integer' ], 'per_page' => [ 'type' => 'integer' ], 'llm_instructions' => [ 'type' => 'string' ], ], ], [ 'annotations' => [ 'readonly' => true, 'idempotent' => true, 'destructive' => false, ], ], fn() => is_user_logged_in(), [ 'type' => 'object', 'properties' => [ 'search' => [ 'type' => 'string', 'description' => 'Optional keyword matched against post title and content.', ], 'post_type' => [ 'type' => 'string', 'enum' => array_merge( [ self::POST_TYPE_ALL ], self::SUPPORTED_POST_TYPES ), 'default' => self::POST_TYPE_ALL, 'description' => 'Filter by post type. "all" returns posts, pages and products (when available).', ], 'page' => [ 'type' => 'integer', 'minimum' => 1, 'default' => 1, ], 'per_page' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => self::MAX_PER_PAGE, 'default' => self::DEFAULT_PER_PAGE, ], ], ] ); } public function execute( $input = [] ) { $input = is_array( $input ) ? $input : []; $page = $this->resolve_page( $input ); $per_page = $this->resolve_per_page( $input ); $post_types = $this->resolve_post_types( $input ); if ( is_wp_error( $post_types ) ) { return $post_types; } $search = isset( $input['search'] ) && is_string( $input['search'] ) ? trim( $input['search'] ) : ''; $query_args = [ 'post_type' => $post_types, 'post_status' => self::READABLE_STATUSES, 'perm' => 'readable', 'orderby' => 'date', 'order' => 'DESC', 'paged' => $page, 'posts_per_page' => $per_page, 'no_found_rows' => false, ]; if ( '' !== $search ) { $query_args['s'] = $search; } $query = new \WP_Query( $query_args ); $readable_posts = array_values( array_filter( $query->posts, fn( \WP_Post $post ) => current_user_can( 'read_post', $post->ID ) ) ); $response = [ 'posts' => array_map( [ $this, 'format_post' ], $readable_posts ), 'total' => (int) $query->found_posts, 'page' => $page, 'per_page' => $per_page, ]; if ( empty( $response['posts'] ) ) { $response['llm_instructions'] = self::EMPTY_RESULT_HINT; } return $response; } private function resolve_page( array $input ): int { $page = isset( $input['page'] ) ? (int) $input['page'] : 1; return max( 1, $page ); } private function resolve_per_page( array $input ): int { $per_page = isset( $input['per_page'] ) ? (int) $input['per_page'] : self::DEFAULT_PER_PAGE; return max( 1, min( self::MAX_PER_PAGE, $per_page ) ); } private function resolve_post_types( array $input ) { $type = isset( $input['post_type'] ) && is_string( $input['post_type'] ) ? sanitize_key( $input['post_type'] ) : self::POST_TYPE_ALL; if ( self::POST_TYPE_ALL === $type ) { return array_values( array_filter( self::SUPPORTED_POST_TYPES, 'post_type_exists' ) ); } if ( ! in_array( $type, self::SUPPORTED_POST_TYPES, true ) || ! post_type_exists( $type ) ) { return new \WP_Error( 'invalid_post_type', __( 'Unsupported post type. Allowed values: all, post, page, product.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } return [ $type ]; } private function format_post( \WP_Post $post ): array { $author = get_user_by( 'id', $post->post_author ); return [ 'id' => (int) $post->ID, 'title' => (string) $post->post_title, 'post_type' => (string) $post->post_type, 'status' => (string) $post->post_status, 'date' => (string) $post->post_date_gmt, 'modified' => (string) $post->post_modified_gmt, 'url' => (string) get_permalink( $post->ID ), 'author' => [ 'id' => $author ? (int) $author->ID : 0, 'name' => $author ? (string) $author->display_name : '', ], ]; } }