PluginProbe
AI / 0.2.0
AI v0.2.0
1.3.0 1.2.0 1.1.0 1.0.2 1.0.1 1.0.0 0.9.0 trunk 0.1.1 0.2.0 0.2.1 0.3.0 0.3.1 0.4.0 0.4.1 0.5.0 0.6.0 0.7.0 0.8.0
ai / includes / Abilities / Utilities / Posts.php

Posts.php in AI 0.2.0, at includes/Abilities/Utilities/Posts.php

355 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Post-related WordPress Abilities.
4 *
5 * @package WordPress\AI
6 */
7
8 declare( strict_types=1 );
9
10 namespace WordPress\AI\Abilities\Utilities;
11
12 use WP_Error;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * Post utility WordPress Abilities.
20 *
21 * @since 0.1.0
22 */
23 class Posts {
24
25 /**
26 * The fields that we support.
27 *
28 * @since 0.1.0
29 * @var array<string>
30 */
31 private static array $post_details_fields = array( 'content', 'title', 'slug', 'author', 'type', 'excerpt' );
32
33 /**
34 * Register any needed hooks.
35 *
36 * @since 0.1.0
37 */
38 public function register(): void {
39 add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
40 }
41
42 /**
43 * Registers any needed abilities.
44 *
45 * @since 0.1.0
46 */
47 public function register_abilities(): void {
48 $this->register_get_post_details_ability();
49 $this->register_get_terms_ability();
50 }
51
52 /**
53 * Registers the get-post-details ability.
54 *
55 * @since 0.1.0
56 */
57 private function register_get_post_details_ability(): void {
58 wp_register_ability(
59 'ai/get-post-details',
60 array(
61 'label' => esc_html__( 'Get post details', 'ai' ),
62 'description' => esc_html__( 'Get the details of a post based on the post ID. Optionally, limit the details to specific fields.', 'ai' ),
63 'category' => AI_EXPERIMENTS_DEFAULT_ABILITY_CATEGORY,
64 'input_schema' => array(
65 'type' => 'object',
66 'properties' => array(
67 'post_id' => array(
68 'type' => 'integer',
69 'description' => esc_html__( 'The ID of the post to get the details of.', 'ai' ),
70 ),
71 'fields' => array(
72 'type' => 'array',
73 'description' => esc_html__( 'The fields to get the details of. Will default to all fields if not provided.', 'ai' ),
74 'items' => array(
75 'type' => 'string',
76 'enum' => self::$post_details_fields,
77 ),
78 ),
79 ),
80 'required' => array( 'post_id' ),
81 ),
82 'output_schema' => array(
83 'type' => 'object',
84 'description' => esc_html__( 'The details of the post.', 'ai' ),
85 'properties' => array(
86 'content' => array(
87 'type' => 'string',
88 'description' => esc_html__( 'The content of the post.', 'ai' ),
89 ),
90 'title' => array(
91 'type' => 'string',
92 'description' => esc_html__( 'The title of the post.', 'ai' ),
93 ),
94 'slug' => array(
95 'type' => 'string',
96 'description' => esc_html__( 'The slug of the post.', 'ai' ),
97 ),
98 'author' => array(
99 'type' => 'string',
100 'description' => esc_html__( 'The author of the post.', 'ai' ),
101 ),
102 'type' => array(
103 'type' => 'string',
104 'description' => esc_html__( 'The type of the post.', 'ai' ),
105 ),
106 'excerpt' => array(
107 'type' => 'string',
108 'description' => esc_html__( 'The excerpt of the post.', 'ai' ),
109 ),
110 ),
111 ),
112 'execute_callback' => static function ( array $input ) {
113 $post_id = absint( $input['post_id'] );
114 $post = self::get_post_object( $post_id );
115
116 // If the post doesn't exist, return an error.
117 if ( is_wp_error( $post ) ) {
118 return $post;
119 }
120
121 // See if we have specific fields to get or default to all fields.
122 $fields = isset( $input['fields'] ) && ! empty( $input['fields'] ) ? (array) $input['fields'] : self::$post_details_fields;
123
124 $details = array();
125
126 if ( in_array( 'content', $fields, true ) ) {
127 $details['content'] = $post->post_content;
128 }
129
130 if ( in_array( 'title', $fields, true ) ) {
131 $details['title'] = $post->post_title;
132 }
133
134 if ( in_array( 'slug', $fields, true ) ) {
135 $details['slug'] = $post->post_name;
136 }
137
138 if ( in_array( 'author', $fields, true ) ) {
139 // Get the author display name.
140 $author = get_user_by( 'ID', $post->post_author );
141 if ( $author ) {
142 $details['author'] = $author->display_name;
143 } else {
144 $details['author'] = '';
145 }
146 }
147
148 if ( in_array( 'type', $fields, true ) ) {
149 $details['type'] = $post->post_type;
150 }
151
152 if ( in_array( 'excerpt', $fields, true ) ) {
153 $details['excerpt'] = $post->post_excerpt;
154 }
155
156 // Return the post details.
157 return $details;
158 },
159 'permission_callback' => array( $this, 'permission_callback' ),
160 'meta' => array(
161 'mcp' => array(
162 'public' => true,
163 'type' => 'tool',
164 ),
165 ),
166 )
167 );
168 }
169
170 /**
171 * Registers the get-terms ability.
172 *
173 * @since 0.1.0
174 */
175 private function register_get_terms_ability(): void {
176 wp_register_ability(
177 'ai/get-post-terms',
178 array(
179 'label' => esc_html__( 'Get the post terms', 'ai' ),
180 'description' => esc_html__( 'Get the terms of a post based on the post ID and optionally filter by taxonomy.', 'ai' ),
181 'category' => AI_EXPERIMENTS_DEFAULT_ABILITY_CATEGORY,
182 'input_schema' => array(
183 'type' => 'object',
184 'properties' => array(
185 'post_id' => array(
186 'type' => 'integer',
187 'description' => esc_html__( 'The ID of the post to get the terms of.', 'ai' ),
188 ),
189 'taxonomy' => array(
190 'type' => 'string',
191 'description' => esc_html__( 'The taxonomy to filter the terms by.', 'ai' ),
192 ),
193 ),
194 'required' => array( 'post_id' ),
195 ),
196 'output_schema' => array(
197 'type' => 'object',
198 'description' => esc_html__( 'An array of WP_Term objects assigned to the post.', 'ai' ),
199 'properties' => array(
200 'type' => 'array',
201 'items' => array(
202 'type' => 'array',
203 'items' => array(
204 'term_id' => array(
205 'type' => 'integer',
206 'description' => esc_html__( 'The ID of the term.', 'ai' ),
207 ),
208 'name' => array(
209 'type' => 'string',
210 'description' => esc_html__( 'The name of the term.', 'ai' ),
211 ),
212 'slug' => array(
213 'type' => 'string',
214 'description' => esc_html__( 'The slug of the term.', 'ai' ),
215 ),
216 'term_group' => array(
217 'type' => 'integer',
218 'description' => esc_html__( 'The group ID of the term.', 'ai' ),
219 ),
220 'term_taxonomy_id' => array(
221 'type' => 'integer',
222 'description' => esc_html__( 'The taxonomy ID of the term.', 'ai' ),
223 ),
224 'taxonomy' => array(
225 'type' => 'string',
226 'description' => esc_html__( 'The taxonomy name of the term.', 'ai' ),
227 ),
228 'description' => array(
229 'type' => 'string',
230 'description' => esc_html__( 'The description of the term.', 'ai' ),
231 ),
232 'parent' => array(
233 'type' => 'integer',
234 'description' => esc_html__( 'The parent ID of the term.', 'ai' ),
235 ),
236 'count' => array(
237 'type' => 'integer',
238 'description' => esc_html__( 'How many times the term is used.', 'ai' ),
239 ),
240 'filter' => array(
241 'type' => 'string',
242 'description' => esc_html__( 'How the term should be filtered.', 'ai' ),
243 ),
244 ),
245 ),
246 ),
247 ),
248 'execute_callback' => static function ( array $input ) {
249 $post_id = absint( $input['post_id'] );
250 $post = self::get_post_object( $post_id );
251
252 if ( is_wp_error( $post ) ) {
253 return $post;
254 }
255
256 // See if we have a specific taxonomy to get terms for.
257 $taxonomy = $input['taxonomy'] ?? '';
258
259 if ( $taxonomy ) {
260 // If a taxonomy is provided, ensure it exists.
261 $taxonomy = get_taxonomy( $taxonomy );
262 if ( ! $taxonomy ) {
263 return new WP_Error(
264 'taxonomy_not_found',
265 esc_html__( 'Taxonomy not found.', 'ai' )
266 );
267 }
268 $taxonomies = array( $taxonomy );
269 } else {
270 $taxonomies = get_object_taxonomies( $post->post_type, 'objects' );
271 }
272
273 // Remove any taxonomies that are not allowed.
274 $allowed_taxonomies = array();
275 foreach ( $taxonomies as $taxonomy ) {
276 // If the taxonomy is not allowed in REST endpoints, skip it.
277 if ( empty( $taxonomy->show_in_rest ) ) {
278 continue;
279 }
280
281 // If the requested post isn't associated with this taxonomy, skip it.
282 if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy->name ) ) {
283 continue;
284 }
285
286 $allowed_taxonomies[] = $taxonomy->name;
287 }
288
289 $terms = wp_get_object_terms( $post_id, $allowed_taxonomies );
290
291 if ( is_wp_error( $terms ) ) {
292 return new WP_Error(
293 'get_terms_error',
294 /* translators: %1$s: Error message. */
295 sprintf( esc_html__( 'Error getting terms: %1$s', 'ai' ), $terms->get_error_message() )
296 );
297 }
298
299 return $terms;
300 },
301 'permission_callback' => array( $this, 'permission_callback' ),
302 'meta' => array(
303 'mcp' => array(
304 'public' => true,
305 'type' => 'tool',
306 ),
307 ),
308 ),
309 );
310 }
311
312 /**
313 * The default permission callback abilities can use.
314 *
315 * @since 0.1.0
316 *
317 * @param array<string, mixed> $args The input arguments to the ability.
318 * @return bool|\WP_Error True or false depending on whether the user has permission; WP_Error if the post doesn't exist.
319 */
320 public function permission_callback( array $args ) {
321 $post_id = absint( $args['post_id'] );
322 $post = self::get_post_object( $post_id );
323
324 // Ensure the post exists.
325 if ( is_wp_error( $post ) ) {
326 return $post;
327 }
328
329 // Return true if the user has permission to read the post.
330 return current_user_can( 'read_post', $post_id );
331 }
332
333 /**
334 * Gets the post object.
335 *
336 * @since 0.1.0
337 *
338 * @param int $post_id The ID of the post to get the object of.
339 * @return \WP_Post|\WP_Error The post object or WP_Error if the post doesn't exist.
340 */
341 private static function get_post_object( int $post_id ) {
342 $post = get_post( $post_id );
343
344 // If the post doesn't exist, return an error.
345 if ( ! $post ) {
346 return new WP_Error(
347 'post_not_found',
348 esc_html__( 'Post not found.', 'ai' )
349 );
350 }
351
352 return $post;
353 }
354 }
355