* @author Maximum Software Inc * @license GPL-3.0-only * @link https://agenistrator.com/ * * SPDX-FileCopyrightText: 2025-2026 Alex Ruban * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc * SPDX-License-Identifier: GPL-3.0-only */ namespace Agenistrator; // Direct access prevention if ( ! defined( 'ABSPATH' ) ) { exit; } /** * List_Posts_Tool class. */ class List_Posts_Tool extends Tool { /** * Get the tool ID. * * @return string The tool ID. * @since 1.0.0 */ public function get_id() { return 'list_posts'; } /** * Get the tool name. * * @return string The tool name. * @since 1.0.0 */ public function get_name() { return __( 'List Posts', 'agenistrator' ); } /** * Get the description of the tool. * * @return string The description of the tool. * @since 1.0.0 */ public function get_description() { return 'List WordPress posts with optional filtering.'; } /** * Get instructions for the LLM on how to interpret output fields. * * @return string Instructions for the LLM. * @since 1.0.0 */ public function get_instructions() { return 'EXCERPT FIELDS: `has_excerpt` indicates if a manual excerpt is set. If true, `excerpt` contains the stored value. If false, `auto_excerpt` contains a truncated auto-generated excerpt from content (not stored - do not use for updates).'; } /** * Get the parameters for this tool. * * @return array Parameters schema in JSON Schema format. * @since 1.0.0 */ public function get_parameters() { return array( 'type' => 'object', 'properties' => array( 'category' => array( 'type' => 'string', 'description' => 'Category slug or ID to filter by', ), 'tag' => array( 'type' => 'string', 'description' => 'Tag slug or ID to filter by', ), 'author' => array( 'type' => 'integer', 'description' => 'Author ID to filter by', ), 'status' => array( 'type' => 'string', 'description' => 'Post status to filter by (publish, draft, etc.)', 'enum' => array( 'publish', 'draft', 'pending', 'private', 'future', 'trash', 'any' ), ), 'limit' => array( 'type' => 'integer', 'description' => 'Maximum number of posts to return', 'default' => 10, ), 'offset' => array( 'type' => 'integer', 'description' => 'Number of posts to skip', 'default' => 0, ), 'order' => array( 'type' => 'string', 'description' => 'Order of posts (ASC or DESC)', 'enum' => array( 'ASC', 'DESC' ), 'default' => 'DESC', ), 'orderby' => array( 'type' => 'string', 'description' => 'Field to order posts by', 'enum' => array( 'date', 'title', 'modified', 'comment_count', 'rand' ), 'default' => 'date', ), 'search' => array( 'type' => 'string', 'description' => 'Search term to find in posts', ), ), 'required' => array(), ); } /** * Check if the current user has permission to execute this tool. * * @return bool Whether the user has permission. * @since 1.0.0 */ public function current_user_can_execute() { return current_user_can( 'read' ); } /** * Check if this tool is read-only. * * @return bool True - this tool only retrieves data. * @since 1.0.0 */ public function is_read_only() { return true; } /** * Execute the tool with the given arguments. * * @param array $args The arguments for the tool. * @return array The result of executing the tool. * @since 1.0.0 */ protected function execute( $args ) { // Start building the query args $query_args = array( 'post_type' => 'post', 'posts_per_page' => isset( $args['limit'] ) ? intval( $args['limit'] ) : 10, 'offset' => isset( $args['offset'] ) ? intval( $args['offset'] ) : 0, 'order' => $args['order'] ?? 'DESC', 'orderby' => $args['orderby'] ?? 'date', ); // Add other filter parameters if set if ( isset( $args['category'] ) && ! empty( $args['category'] ) ) { if ( is_numeric( $args['category'] ) ) { $query_args['cat'] = intval( $args['category'] ); } else { $query_args['category_name'] = sanitize_text_field( $args['category'] ); } } if ( isset( $args['tag'] ) && ! empty( $args['tag'] ) ) { if ( is_numeric( $args['tag'] ) ) { $query_args['tag_id'] = intval( $args['tag'] ); } else { $query_args['tag'] = sanitize_text_field( $args['tag'] ); } } if ( isset( $args['author'] ) && ! empty( $args['author'] ) ) { $query_args['author'] = intval( $args['author'] ); } if ( isset( $args['status'] ) && ! empty( $args['status'] ) ) { // Only allow viewing non-published posts if the user has appropriate permissions if ( 'publish' !== $args['status'] && ! current_user_can( 'edit_posts' ) ) { return $this->error_response( 'You do not have permission to view posts with this status' ); } $query_args['post_status'] = sanitize_text_field( $args['status'] ); } elseif ( ! current_user_can( 'edit_posts' ) ) { // Default to only published posts for regular users. $query_args['post_status'] = 'publish'; } if ( isset( $args['search'] ) && ! empty( $args['search'] ) ) { $query_args['s'] = sanitize_text_field( $args['search'] ); } // Get posts based on the query $posts_query = new \WP_Query( $query_args ); $posts = $posts_query->posts; // If no posts found if ( empty( $posts ) ) { return $this->success_response( 'No posts found matching the criteria', array( 'posts' => array(), 'count' => 0, 'total' => 0, 'limit' => intval( $query_args['posts_per_page'] ), 'offset' => intval( $query_args['offset'] ), 'has_more' => false, ) ); } // Format the posts data $formatted_posts = array(); foreach ( $posts as $post ) { $has_excerpt = has_excerpt( $post ); $post_data = array( 'id' => $post->ID, 'title' => $post->post_title, 'has_excerpt' => $has_excerpt, 'date' => $post->post_date, 'modified' => $post->post_modified, 'status' => $post->post_status, 'slug' => $post->post_name, 'permalink' => get_permalink( $post->ID ), 'author' => array( 'id' => $post->post_author, 'name' => get_the_author_meta( 'display_name', $post->post_author ), ), ); if ( $has_excerpt ) { $post_data['excerpt'] = $post->post_excerpt; } else { $auto_excerpt = html_entity_decode( wp_strip_all_tags( get_the_excerpt( $post ) ), ENT_QUOTES, 'UTF-8' ); if ( ! empty( $auto_excerpt ) ) { $post_data['auto_excerpt'] = String_Utils::truncate_to_token_boundary_with_ellipsis( $auto_excerpt, 200 ); } } // Add categories and tags $post_data['categories'] = array(); $categories = get_the_category( $post->ID ); if ( $categories ) { foreach ( $categories as $category ) { $post_data['categories'][] = array( 'id' => $category->term_id, 'name' => $category->name, 'slug' => $category->slug, ); } } $post_data['tags'] = array(); $tags = get_the_tags( $post->ID ); if ( $tags ) { foreach ( $tags as $tag ) { $post_data['tags'][] = array( 'id' => $tag->term_id, 'name' => $tag->name, 'slug' => $tag->slug, ); } } // Include featured image if exists if ( has_post_thumbnail( $post->ID ) ) { $post_data['featured_image'] = array( 'id' => get_post_thumbnail_id( $post->ID ), 'url' => get_the_post_thumbnail_url( $post->ID, 'thumbnail' ), ); } $formatted_posts[] = $post_data; } // Calculate pagination info $total_posts = $posts_query->found_posts; $limit = $query_args['posts_per_page']; $offset = $query_args['offset']; return $this->success_response( 'Posts retrieved successfully', array( 'posts' => $formatted_posts, 'count' => count( $formatted_posts ), 'total' => $total_posts, 'limit' => $limit, 'offset' => $offset, 'has_more' => ( $offset + count( $formatted_posts ) ) < $total_posts, ) ); } }