PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
1.1.12 1.1.11 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 All 36 releases
← All changes | includes/agents/abilities.php +82 -7 1.0.0 → 1.1.12 View file →
@@ -290,8 +290,35 @@
290 290
291 291 /**
292 292 * `desktop-mode/get-post` permission callback.
293 293 *
294 + * Asks every gate a single read has, in the order Core's REST
295 + * controllers ask them:
296 + *
297 + * - A zero id is refused before any fetch: `get_post( 0 )` returns
298 + * the global post, which would judge the request against whatever
299 + * another plugin left there.
300 + * - `read_post` decides visibility (published / private / draft) and
301 + * stays the floor for every row.
302 + * - The post password is a separate question — WordPress splits the
303 + * two deliberately. A sealed post stays sealed unless the caller can
304 + * edit it (the same escape hatch
305 + * `WP_REST_Posts_Controller::check_password_required()` grants), and
306 + * because this ability returns RAW `post_content` there is no empty
307 + * rendered field to fall back to, so the answer is to refuse.
308 + * - A post type with no readable front end (`is_post_type_viewable()`
309 + * false: an order, a submission log, a queue entry) is read only by
310 + * a caller who can edit the row. `map_meta_cap()` resolves
311 + * `read_post` on a published row of such a type to plain `read`,
312 + * which every logged-in user holds, so `read_post` alone does not
313 + * answer the question for it.
314 + *
315 + * `openstation_ai_can_read_post()` (loaded unconditionally from the AI
316 + * Copilot bootstrap, ahead of this module) implements the password and
317 + * post-type gates; this callback keeps `read_post` in front of it so a
318 + * plugin that narrows `read_post` on a public post still narrows this
319 + * ability.
320 + *
294 321 * @param array $args Input args.
295 322 * @return bool
296 323 */
297 324 function openstation_agents_ability_get_post_can( $args ) {
@@ -299,9 +326,12 @@
299 326 $post_id = isset( $args['post_id'] ) ? (int) $args['post_id'] : 0;
300 327 if ( $post_id <= 0 ) {
301 328 return false;
302 329 }
303 - return current_user_can( 'read_post', $post_id );
330 + if ( ! current_user_can( 'read_post', $post_id ) ) {
331 + return false;
332 + }
333 + return openstation_ai_can_read_post( $post_id );
304 334 }
305 335
306 336 /**
307 337 * `desktop-mode/get-media` execute callback.
@@ -339,14 +369,33 @@
339 369
340 370 /**
341 371 * `desktop-mode/get-media` permission callback.
342 372 *
343 - * Gates on `upload_files` (author+), deliberately NOT on `read_post`:
344 - * for `inherit`-status attachments that check defers to the parent
345 - * post (and effectively requires edit rights when unattached), which
346 - * wrongly blocks read-only access to media whose file URL is public
347 - * on a standard site anyway.
373 + * Gates on `upload_files` (author+) — the capability the Media
374 + * Library itself requires — rather than on `read_post` of the
375 + * attachment.
348 376 *
377 + * An attached file is a child of its parent post, and the result
378 + * carries the attachment's title, caption and `attachedTo` (the parent
379 + * id), so an attached file also requires that the caller can read the
380 + * parent. That follows the shape of Core's rule for `inherit`-status
381 + * attachments, `WP_REST_Posts_Controller::check_read_permission()`
382 + * (the attachments controller inherits it): an attachment defers to
383 + * its parent whenever one exists. The parent is judged by `read_post`,
384 + * plus the post-type rule `desktop-mode/get-post` applies (a type with
385 + * no readable front end needs `edit_post`), which is stricter than Core
386 + * on a non-viewable parent: Core admits any `publish` parent of a
387 + * REST-enabled type. Core's other requirement, that the parent's type
388 + * be `show_in_rest`, is not copied: it would refuse media attached to a
389 + * non-REST type for every caller, administrators included. The
390 + * parent's password is not asked: the attachment's own fields are not
391 + * the parent's body, and Core's attachment read does not ask it either.
392 + *
393 + * An unattached file, or one whose parent row no longer exists, is
394 + * judged on `upload_files` alone, as Core treats a parentless
395 + * `inherit` attachment as published. A zero id is refused before any
396 + * fetch, because `get_post( 0 )` returns the global post.
397 + *
349 398 * @param array $args Input args.
350 399 * @return bool
351 400 */
352 401 function openstation_agents_ability_get_media_can( $args ) {
@@ -354,9 +403,35 @@
354 403 $attachment_id = isset( $args['attachment_id'] ) ? (int) $args['attachment_id'] : 0;
355 404 if ( $attachment_id <= 0 ) {
356 405 return false;
357 406 }
358 - return current_user_can( 'upload_files' );
407 + if ( ! current_user_can( 'upload_files' ) ) {
408 + return false;
409 + }
410 +
411 + $attachment = get_post( $attachment_id );
412 + if ( ! ( $attachment instanceof WP_Post ) || 'attachment' !== $attachment->post_type ) {
413 + // The execute callback answers "not found" for these.
414 + return true;
415 + }
416 +
417 + $parent_id = (int) $attachment->post_parent;
418 + if ( $parent_id <= 0 || $parent_id === $attachment_id ) {
419 + return true;
420 + }
421 + $parent = get_post( $parent_id );
422 + if ( ! ( $parent instanceof WP_Post ) ) {
423 + return true;
424 + }
425 +
426 + if ( ! current_user_can( 'read_post', $parent->ID ) ) {
427 + return false;
428 + }
429 + $parent_type = get_post_type_object( $parent->post_type );
430 + if ( ! $parent_type || ! is_post_type_viewable( $parent_type ) ) {
431 + return current_user_can( 'edit_post', $parent->ID );
432 + }
433 + return true;
359 434 }
360 435
361 436 /**
362 437 * `desktop-mode/update-media` execute callback.