PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
← All changes | includes/agents/abilities.php +59 -43 0.9.81.1.10 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Agents: abilities bridge.
3 + * OpenStation — Agents: abilities bridge.
4 4 *
5 5 * Two halves:
6 6 *
7 7 * 1. Registers the agent-oriented abilities against Core's Abilities
@@ -8,9 +8,9 @@
8 8 * API: `desktop-mode/get-post` and `desktop-mode/get-media`
9 9 * (read-only) plus the mutating trio `desktop-mode/update-post`,
10 10 * `desktop-mode/update-media` (alt text / title / caption /
11 11 * description), and `desktop-mode/create-post` (draft-only). The
12 - * `desktop-mode` category ships from the AI Copilot module
12 + * `openstation` category ships from the AI Copilot module
13 13 * (always loaded), so this file only adds abilities to it. The
14 14 * read abilities carry the `readonly` annotation and therefore
15 15 * also become available to the AI Copilot assistant; the mutating
16 16 * ones do not — they are reachable only through an agent whose
@@ -24,9 +24,9 @@
24 24 * compensating controls are the explicit per-agent allowlist set by
25 25 * an `edit_users` human, the agent's role, and each ability's own
26 26 * `permission_callback` evaluated against the agent user.
27 27 *
28 - * @package WPDesktopMode
28 + * @package OpenStation
29 29 */
30 30
31 31 defined( 'ABSPATH' ) || exit;
32 32
@@ -34,9 +34,9 @@
34 34 * Registers the agent-oriented abilities.
35 35 *
36 36 * @return void
37 37 */
38 -function desktop_mode_agents_register_abilities() {
38 +function openstation_agents_register_abilities() {
39 39 if ( ! function_exists( 'wp_register_ability' ) ) {
40 40 return;
41 41 }
42 42
@@ -51,9 +51,9 @@
51 51 // instructions leaves every other agent guessing, and a
52 52 // cautious one will refuse to write rather than risk
53 53 // flattening blocks.
54 54 'description' => 'Return a post — title, content, excerpt, status, author, dates — by its numeric id. `content` is the RAW stored content exactly as saved, with block delimiter comments (`<!-- wp:… -->`) intact; it is never rendered output, so it is safe to edit and write back. Honours the caller\'s read capability.',
55 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
55 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
56 56 'input_schema' => array(
57 57 'type' => 'object',
58 58 'additionalProperties' => false,
59 59 'required' => array( 'post_id' ),
@@ -63,9 +63,9 @@
63 63 'description' => 'The post id to fetch.',
64 64 ),
65 65 ),
66 66 ),
67 - 'output_schema' => desktop_mode_ai_ability_output_schema(
67 + 'output_schema' => openstation_ai_ability_output_schema(
68 68 array(
69 69 'id' => array( 'type' => 'integer' ),
70 70 'title' => array( 'type' => 'string' ),
71 71 'content' => array( 'type' => 'string' ),
@@ -71,10 +71,10 @@
71 71 'content' => array( 'type' => 'string' ),
72 72 'status' => array( 'type' => 'string' ),
73 73 )
74 74 ),
75 - 'execute_callback' => 'desktop_mode_agents_ability_get_post',
76 - 'permission_callback' => 'desktop_mode_agents_ability_get_post_can',
75 + 'execute_callback' => 'openstation_agents_ability_get_post',
76 + 'permission_callback' => 'openstation_agents_ability_get_post_can',
77 77 'meta' => array(
78 78 'annotations' => array(
79 79 'readonly' => true,
80 80 'idempotent' => true,
@@ -88,9 +88,9 @@
88 88 'desktop-mode/get-media',
89 89 array(
90 90 'label' => __( 'Get media details', 'desktop-mode' ),
91 91 'description' => 'Return details for a media library item (attachment) by numeric id: file URL, mime type, dimensions, alt text, caption, and the post it is attached to. Use this to read images or other media referenced by posts.',
92 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
92 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
93 93 'input_schema' => array(
94 94 'type' => 'object',
95 95 'additionalProperties' => false,
96 96 'required' => array( 'attachment_id' ),
@@ -100,9 +100,9 @@
100 100 'description' => 'The attachment (media library) id.',
101 101 ),
102 102 ),
103 103 ),
104 - 'output_schema' => desktop_mode_ai_ability_output_schema(
104 + 'output_schema' => openstation_ai_ability_output_schema(
105 105 array(
106 106 'id' => array( 'type' => 'integer' ),
107 107 'url' => array( 'type' => 'string' ),
108 108 'mime' => array( 'type' => 'string' ),
@@ -107,10 +107,10 @@
107 107 'url' => array( 'type' => 'string' ),
108 108 'mime' => array( 'type' => 'string' ),
109 109 )
110 110 ),
111 - 'execute_callback' => 'desktop_mode_agents_ability_get_media',
112 - 'permission_callback' => 'desktop_mode_agents_ability_get_media_can',
111 + 'execute_callback' => 'openstation_agents_ability_get_media',
112 + 'permission_callback' => 'openstation_agents_ability_get_media_can',
113 113 'meta' => array(
114 114 'annotations' => array(
115 115 'readonly' => true,
116 116 'idempotent' => true,
@@ -124,9 +124,9 @@
124 124 'desktop-mode/update-media',
125 125 array(
126 126 'label' => __( 'Update media details', 'desktop-mode' ),
127 127 'description' => 'Update metadata on a media library item (attachment): alt text, title, caption, and/or description. The file itself is never touched. Honours the edit capability on the attachment.',
128 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
128 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
129 129 'input_schema' => array(
130 130 'type' => 'object',
131 131 'additionalProperties' => false,
132 132 'required' => array( 'attachment_id' ),
@@ -152,16 +152,16 @@
152 152 'description' => 'New description.',
153 153 ),
154 154 ),
155 155 ),
156 - 'output_schema' => desktop_mode_ai_ability_output_schema(
156 + 'output_schema' => openstation_ai_ability_output_schema(
157 157 array(
158 158 'id' => array( 'type' => 'integer' ),
159 159 'updated' => array( 'type' => 'boolean' ),
160 160 )
161 161 ),
162 - 'execute_callback' => 'desktop_mode_agents_ability_update_media',
163 - 'permission_callback' => 'desktop_mode_agents_ability_update_media_can',
162 + 'execute_callback' => 'openstation_agents_ability_update_media',
163 + 'permission_callback' => 'openstation_agents_ability_update_media_can',
164 164 'meta' => array(
165 165 'show_in_rest' => true,
166 166 ),
167 167 )
@@ -171,9 +171,9 @@
171 171 'desktop-mode/create-post',
172 172 array(
173 173 'label' => __( 'Create draft post', 'desktop-mode' ),
174 174 'description' => 'Create a NEW post or page as a DRAFT, authored by the calling user. The status is always draft: this ability can never publish. Use it to produce reviewable content (translations, variants, generated drafts) without touching any existing post. `content` is stored RAW, exactly as passed, so send block markup with its delimiter comments (`<!-- wp:… -->`) intact.',
175 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
175 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
176 176 'input_schema' => array(
177 177 'type' => 'object',
178 178 'additionalProperties' => false,
179 179 'required' => array( 'title', 'content' ),
@@ -196,16 +196,16 @@
196 196 'description' => 'Post type. Defaults to post.',
197 197 ),
198 198 ),
199 199 ),
200 - 'output_schema' => desktop_mode_ai_ability_output_schema(
200 + 'output_schema' => openstation_ai_ability_output_schema(
201 201 array(
202 202 'id' => array( 'type' => 'integer' ),
203 203 'status' => array( 'type' => 'string' ),
204 204 )
205 205 ),
206 - 'execute_callback' => 'desktop_mode_agents_ability_create_post',
207 - 'permission_callback' => 'desktop_mode_agents_ability_create_post_can',
206 + 'execute_callback' => 'openstation_agents_ability_create_post',
207 + 'permission_callback' => 'openstation_agents_ability_create_post_can',
208 208 'meta' => array(
209 209 'show_in_rest' => true,
210 210 ),
211 211 )
@@ -215,9 +215,9 @@
215 215 'desktop-mode/update-post',
216 216 array(
217 217 'label' => __( 'Update post', 'desktop-mode' ),
218 218 'description' => 'Update fields on an existing post. Accepts any subset of title / content / excerpt / status. `content` is stored RAW, exactly as passed, so send block markup with its delimiter comments (`<!-- wp:… -->`) intact — passing rendered HTML would flatten the post\'s blocks. Honours the edit_post capability of the calling user.',
219 - 'category' => DESKTOP_MODE_AI_ABILITY_CATEGORY,
219 + 'category' => OPENSTATION_AI_ABILITY_CATEGORY,
220 220 'input_schema' => array(
221 221 'type' => 'object',
222 222 'additionalProperties' => false,
223 223 'required' => array( 'post_id' ),
@@ -244,16 +244,16 @@
244 244 'description' => 'New post status.',
245 245 ),
246 246 ),
247 247 ),
248 - 'output_schema' => desktop_mode_ai_ability_output_schema(
248 + 'output_schema' => openstation_ai_ability_output_schema(
249 249 array(
250 250 'id' => array( 'type' => 'integer' ),
251 251 'updated' => array( 'type' => 'boolean' ),
252 252 )
253 253 ),
254 - 'execute_callback' => 'desktop_mode_agents_ability_update_post',
255 - 'permission_callback' => 'desktop_mode_agents_ability_update_post_can',
254 + 'execute_callback' => 'openstation_agents_ability_update_post',
255 + 'permission_callback' => 'openstation_agents_ability_update_post_can',
256 256 'meta' => array(
257 257 'show_in_rest' => true,
258 258 ),
259 259 )
@@ -258,9 +258,9 @@
258 258 ),
259 259 )
260 260 );
261 261 }
262 -add_action( 'wp_abilities_api_init', 'desktop_mode_agents_register_abilities' );
262 +add_action( 'wp_abilities_api_init', 'openstation_agents_register_abilities' );
263 263
264 264 /**
265 265 * `desktop-mode/get-post` execute callback.
266 266 *
@@ -266,14 +266,14 @@
266 266 *
267 267 * @param array $args Validated input.
268 268 * @return array|WP_Error
269 269 */
270 -function desktop_mode_agents_ability_get_post( $args ) {
270 +function openstation_agents_ability_get_post( $args ) {
271 271 $args = (array) $args;
272 272 $post_id = isset( $args['post_id'] ) ? (int) $args['post_id'] : 0;
273 273 $post = $post_id > 0 ? get_post( $post_id ) : null;
274 274 if ( ! ( $post instanceof WP_Post ) ) {
275 - return new WP_Error( 'desktop_mode_agent_post_not_found', __( 'Post not found.', 'desktop-mode' ) );
275 + return new WP_Error( 'openstation_agent_post_not_found', __( 'Post not found.', 'desktop-mode' ) );
276 276 }
277 277 return array(
278 278 'id' => (int) $post->ID,
279 279 'title' => (string) $post->post_title,
@@ -290,18 +290,34 @@
290 290
291 291 /**
292 292 * `desktop-mode/get-post` permission callback.
293 293 *
294 + * `read_post` decides visibility (published / private / draft) and
295 + * never the post password — WordPress splits the two deliberately, so
296 + * a plain `read_post` check would hand a Subscriber the raw body of a
297 + * password-protected post. Mirror Core: a sealed post stays sealed
298 + * unless the caller can edit it (the same escape hatch
299 + * `WP_REST_Posts_Controller::check_password_required()` grants), and
300 + * because this ability returns RAW `post_content` there is no empty
301 + * rendered field to fall back to — the only safe answer is to refuse.
302 + *
294 303 * @param array $args Input args.
295 304 * @return bool
296 305 */
297 -function desktop_mode_agents_ability_get_post_can( $args ) {
306 +function openstation_agents_ability_get_post_can( $args ) {
298 307 $args = (array) $args;
299 308 $post_id = isset( $args['post_id'] ) ? (int) $args['post_id'] : 0;
300 309 if ( $post_id <= 0 ) {
301 310 return false;
302 311 }
303 - return current_user_can( 'read_post', $post_id );
312 + if ( ! current_user_can( 'read_post', $post_id ) ) {
313 + return false;
314 + }
315 + $post = get_post( $post_id );
316 + if ( $post instanceof WP_Post && post_password_required( $post ) && ! current_user_can( 'edit_post', $post_id ) ) {
317 + return false;
318 + }
319 + return true;
304 320 }
305 321
306 322 /**
307 323 * `desktop-mode/get-media` execute callback.
@@ -308,14 +324,14 @@
308 324 *
309 325 * @param array $args Validated input.
310 326 * @return array|WP_Error
311 327 */
312 -function desktop_mode_agents_ability_get_media( $args ) {
328 +function openstation_agents_ability_get_media( $args ) {
313 329 $args = (array) $args;
314 330 $attachment_id = isset( $args['attachment_id'] ) ? (int) $args['attachment_id'] : 0;
315 331 $post = $attachment_id > 0 ? get_post( $attachment_id ) : null;
316 332 if ( ! ( $post instanceof WP_Post ) || 'attachment' !== $post->post_type ) {
317 - return new WP_Error( 'desktop_mode_agent_media_not_found', __( 'Attachment not found.', 'desktop-mode' ) );
333 + return new WP_Error( 'openstation_agent_media_not_found', __( 'Attachment not found.', 'desktop-mode' ) );
318 334 }
319 335
320 336 $meta = wp_get_attachment_metadata( $attachment_id );
321 337 if ( ! is_array( $meta ) ) {
@@ -348,9 +364,9 @@
348 364 *
349 365 * @param array $args Input args.
350 366 * @return bool
351 367 */
352 -function desktop_mode_agents_ability_get_media_can( $args ) {
368 +function openstation_agents_ability_get_media_can( $args ) {
353 369 $args = (array) $args;
354 370 $attachment_id = isset( $args['attachment_id'] ) ? (int) $args['attachment_id'] : 0;
355 371 if ( $attachment_id <= 0 ) {
356 372 return false;
@@ -363,14 +379,14 @@
363 379 *
364 380 * @param array $args Validated input.
365 381 * @return array|WP_Error
366 382 */
367 -function desktop_mode_agents_ability_update_media( $args ) {
383 +function openstation_agents_ability_update_media( $args ) {
368 384 $args = (array) $args;
369 385 $attachment_id = isset( $args['attachment_id'] ) ? (int) $args['attachment_id'] : 0;
370 386 $post = $attachment_id > 0 ? get_post( $attachment_id ) : null;
371 387 if ( ! ( $post instanceof WP_Post ) || 'attachment' !== $post->post_type ) {
372 - return new WP_Error( 'desktop_mode_agent_media_not_found', __( 'Attachment not found.', 'desktop-mode' ) );
388 + return new WP_Error( 'openstation_agent_media_not_found', __( 'Attachment not found.', 'desktop-mode' ) );
373 389 }
374 390
375 391 if ( isset( $args['alt_text'] ) ) {
376 392 update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( (string) $args['alt_text'] ) );
@@ -405,9 +421,9 @@
405 421 *
406 422 * @param array $args Input args.
407 423 * @return bool
408 424 */
409 -function desktop_mode_agents_ability_update_media_can( $args ) {
425 +function openstation_agents_ability_update_media_can( $args ) {
410 426 $args = (array) $args;
411 427 $attachment_id = isset( $args['attachment_id'] ) ? (int) $args['attachment_id'] : 0;
412 428 if ( $attachment_id <= 0 ) {
413 429 return false;
@@ -422,9 +438,9 @@
422 438 *
423 439 * @param array $args Validated input.
424 440 * @return array|WP_Error
425 441 */
426 -function desktop_mode_agents_ability_create_post( $args ) {
442 +function openstation_agents_ability_create_post( $args ) {
427 443 $args = (array) $args;
428 444 $type = isset( $args['type'] ) && 'page' === $args['type'] ? 'page' : 'post';
429 445
430 446 $post_id = wp_insert_post(
@@ -456,9 +472,9 @@
456 472 *
457 473 * @param array $args Input args.
458 474 * @return bool
459 475 */
460 -function desktop_mode_agents_ability_create_post_can( $args ) {
476 +function openstation_agents_ability_create_post_can( $args ) {
461 477 $args = (array) $args;
462 478 if ( isset( $args['type'] ) && 'page' === $args['type'] ) {
463 479 return current_user_can( 'edit_pages' );
464 480 }
@@ -470,13 +486,13 @@
470 486 *
471 487 * @param array $args Validated input.
472 488 * @return array|WP_Error
473 489 */
474 -function desktop_mode_agents_ability_update_post( $args ) {
490 +function openstation_agents_ability_update_post( $args ) {
475 491 $args = (array) $args;
476 492 $post_id = isset( $args['post_id'] ) ? (int) $args['post_id'] : 0;
477 493 if ( $post_id <= 0 || ! get_post( $post_id ) ) {
478 - return new WP_Error( 'desktop_mode_agent_post_not_found', __( 'Post not found.', 'desktop-mode' ) );
494 + return new WP_Error( 'openstation_agent_post_not_found', __( 'Post not found.', 'desktop-mode' ) );
479 495 }
480 496
481 497 $update = array( 'ID' => $post_id );
482 498 if ( isset( $args['title'] ) ) {
@@ -490,9 +506,9 @@
490 506 }
491 507 if ( isset( $args['status'] ) ) {
492 508 $status = sanitize_key( (string) $args['status'] );
493 509 if ( ! in_array( $status, array( 'publish', 'draft', 'pending', 'private' ), true ) ) {
494 - return new WP_Error( 'desktop_mode_agent_invalid_status', __( 'Invalid post status.', 'desktop-mode' ) );
510 + return new WP_Error( 'openstation_agent_invalid_status', __( 'Invalid post status.', 'desktop-mode' ) );
495 511 }
496 512 $update['post_status'] = $status;
497 513 }
498 514
@@ -514,9 +530,9 @@
514 530 *
515 531 * @param array $args Input args.
516 532 * @return bool
517 533 */
518 -function desktop_mode_agents_ability_update_post_can( $args ) {
534 +function openstation_agents_ability_update_post_can( $args ) {
519 535 $args = (array) $args;
520 536 $post_id = isset( $args['post_id'] ) ? (int) $args['post_id'] : 0;
521 537 if ( $post_id <= 0 || ! current_user_can( 'edit_post', $post_id ) ) {
522 538 return false;
@@ -536,9 +552,9 @@
536 552 * readonly/mutating badge derived from `meta.annotations.readonly`.
537 553 *
538 554 * @return array<int, array{slug:string, label:string, description:string, category:string, readonly:bool}>
539 555 */
540 -function desktop_mode_agents_abilities_catalogue() {
556 +function openstation_agents_abilities_catalogue() {
541 557 $catalogue = array();
542 558
543 559 if ( function_exists( 'wp_get_abilities' ) ) {
544 560 foreach ( wp_get_abilities() as $ability ) {
@@ -567,9 +583,9 @@
567 583 * registry.
568 584 *
569 585 * @param array $catalogue Abilities projected from `wp_get_abilities()`.
570 586 */
571 - $catalogue = apply_filters( 'desktop_mode_agent_abilities_catalogue', $catalogue );
587 + $catalogue = apply_filters( 'openstation_agent_abilities_catalogue', $catalogue );
572 588 if ( ! is_array( $catalogue ) ) {
573 589 return array();
574 590 }
575 591