Skip to content

list posts ability needs to be able to list built in post types. #69

@artpi

Description

@artpi

We currently have the list posts ability but it cannot list the notes and todos, nor can it take proper search arguments.

Here is some inspiration from another project, this may be useful verbatim

<?php
/**
 * Get Posts ability - Get WordPress posts using various filters
 *
 * @package WPCOM\AI\Tools
 */
declare( strict_types = 1 );

add_action(
	'wp_abilities_api_init',
	function () {
		/**
		 * Get post types that have show_ui set to true
		 *
		 * @return array
		 */
		$get_allowed_post_types = function (): array {
			$post_types   = get_post_types( array( 'show_ui' => true ), 'names' );
			$post_types[] = 'any';
			return array_values( $post_types );
		};

		wp_register_ability(
			'wpcom/get-posts',
			array(
				'label'               => 'Get Posts',
				'description'         => 'Get WordPress posts using various filters',
				'category'            => 'wpcom',
				'meta'                => array(
					'instructions' => <<<PROMPT
					Call this tool if the user is asking to get posts or other data relying on wordpress post types from the database.
					When listing them:
					- note the status (draft, published, etc)
					- Always Markdown links to link to the post wp-admin page unless instructed otherwise.
				PROMPT,
				),
				'input_schema'        => array(
					'type'       => 'object',
					'properties' => array(
						's'           => array(
							'type'        => 'string',
							'description' => 'Search keyword for post content and title',
						),
						'cat'         => array(
							'type'        => 'integer',
							'description' => 'Category ID to filter by',
						),
						'author'      => array(
							'type'        => 'integer',
							'description' => 'Author ID to filter by',
						),
						'meta_key'    => array(
							'type'        => 'string',
							'description' => 'Meta key to search by',
						),
						'meta_value'  => array(
							'type'        => 'string',
							'description' => 'Meta value to search for',
						),
						'order'       => array(
							'type'        => 'string',
							'description' => 'Sort order (ASC or DESC)',
							'enum'        => array( 'ASC', 'DESC' ),
						),
						'orderby'     => array(
							'type'        => 'string',
							'description' => 'Field to order results by',
							'enum'        => array( 'none', 'ID', 'author', 'title', 'name', 'type', 'date', 'modified', 'parent', 'rand', 'comment_count', 'relevance', 'menu_order', 'meta_value', 'meta_value_num' ),
						),
						'paged'       => array(
							'type'        => 'integer',
							'description' => 'Page number for pagination',
						),
						'post_type'   => array(
							'type'        => 'string',
							'description' => 'Post type to search',
							'enum'        => function_exists( 'get_post_types' ) ? $get_allowed_post_types() : array( 'post', 'page', 'attachment', 'revision', 'nav_menu_item', 'any' ),
						),
						'post_status' => array(
							'type'        => 'string',
							'description' => 'Post status to filter by',
							'enum'        => array( 'publish', 'pending', 'draft', 'auto-draft', 'future', 'private', 'inherit', 'trash', 'any' ),
						),
						'include'     => array(
							'type'        => 'array',
							'items'       => array( 'type' => 'integer' ),
							'description' => 'Specific post IDs to include',
						),
					),
					'required'   => array(),
				),
				'output_schema'       => array(
					'type' => 'string',
				),
				'execute_callback'    => function ( array $input ): string {
					$args = array(
						'posts_per_page' => 10,
						'post_status'    => 'publish',
					);

					// Map input parameters to get_posts args
					$valid_params = array( 's', 'cat', 'author', 'meta_key', 'meta_value', 'order', 'orderby', 'paged', 'post_type', 'post_status', 'include' );
					foreach ( $input as $key => $value ) {
						if ( in_array( $key, $valid_params, true ) && ! empty( $value ) ) {
							$args[ $key ] = $value;
						}
					}

					// Apply attachment-specific defaults if not explicitly set by user
					if ( ! isset( $input['post_status'] ) && isset( $args['post_type'] ) && 'attachment' === $args['post_type'] ) {
						$args['post_status'] = 'inherit';
					}

					// Only the posts current user can read.
					// For attachments, we handle permissions manually in the loop below
					// WordPress's 'perm' => 'readable' doesn't work reliably with attachments
					if ( ! isset( $args['post_type'] ) || 'attachment' !== $args['post_type'] ) {
						$args['perm'] = 'readable';
					}

					$posts = get_posts( $args );

					// Whitelist of meta fields to include
					$meta_whitelist = array(
						'_price',           // WooCommerce product price
						'_regular_price',   // WooCommerce regular price
						'_sale_price',      // WooCommerce sale price
					);

					// Add whitelisted metadata to each post
					$posts_with_meta = array();
					foreach ( $posts as $post ) {
						// Additional security check - verify current user can read this post
						// For published posts, anonymous users should still be able to read them
						if ( 'publish' !== $post->post_status && ! current_user_can( 'read_post', $post->ID ) ) {
							continue;
						}

						// For attachments with inherit status, verify access permissions
						if ( 'attachment' === $post->post_type && 'inherit' === $post->post_status ) {
							if ( $post->post_parent ) {
								// Check parent post permissions for attached media
								if ( ! current_user_can( 'read_post', $post->post_parent ) ) {
									continue;
								}
							} elseif ( ! current_user_can( 'edit_post', $post->ID ) && get_current_user_id() !== (int) $post->post_author ) {
								// Orphaned attachments should only be readable by author or users with edit capabilities
								continue;
							}
						}

						$post_array = $post->to_array();

						// Get all meta data once
						$all_meta = get_post_meta( $post->ID );

						// Filter to only include whitelisted fields
						$post_array['meta'] = array();
						foreach ( $meta_whitelist as $meta_key ) {
							if ( isset( $all_meta[ $meta_key ] ) ) {
								// get_post_meta returns array, get first value
								$post_array['meta'][ $meta_key ] = $all_meta[ $meta_key ][0];
							}
						}

						// For attachments, add the URL
						if ( $post->post_type === 'attachment' ) {
							$post_array['attachment_url'] = wp_get_attachment_url( $post->ID );
						}

						$posts_with_meta[] = $post_array;
					}

					return wp_json_encode( $posts_with_meta );
				},
				'permission_callback' => function ( array $input ): bool {
					// Allow any authenticated user to use this tool.
					return is_user_logged_in();
				},
			)
		);
	}
);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions