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
desktop-mode / includes / desktop-files / media-library.php

media-library.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.10, at includes/desktop-files/media-library.php

714 lines 23.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — stored files → the Media Library.
4 *
5 * A file dragged onto the desktop lives in the plugin's own storage,
6 * outside the Media Library on purpose (see `stored-files-store.php`).
7 * This module is the bridge back: a stored file the site would accept
8 * as an upload can be COPIED into the Media Library as a regular
9 * attachment, and — for media — used to start a new post or page
10 * with the attachment already in place.
11 *
12 * Two routes:
13 *
14 * POST /desktop-mode/v1/files/uploads/<id>/media
15 * Copy the bytes into the Media Library. Idempotent: the
16 * attachment remembers its source row in
17 * `_openstation_stored_file_id` post meta, and a second call
18 * returns the existing attachment instead of a duplicate.
19 *
20 * POST /desktop-mode/v1/files/uploads/<id>/post { postType }
21 * The same copy, then a fresh `auto-draft` of `postType` whose
22 * content is the attachment as a block (image / video / audio /
23 * file) and whose featured image is the attachment when it is
24 * an image. The response carries the edit URL; the client opens
25 * it in a window. `auto-draft` is exactly what `post-new.php`
26 * creates, so an abandoned "start a post" leaves nothing the
27 * daily auto-draft sweep will not clear.
28 *
29 * The stored file is never moved or altered — the placement keeps
30 * owning it, and trashing the tile later does not touch the
31 * attachment (nor the other way round).
32 *
33 * @package OpenStation
34 */
35
36 defined( 'ABSPATH' ) || exit;
37
38 /**
39 * Attachment post-meta key naming the stored-file row an attachment
40 * was copied from. One attachment per stored file, per site.
41 */
42 const OPENSTATION_MEDIA_SOURCE_META = '_openstation_stored_file_id';
43
44 /**
45 * Attachment post-meta key carrying the source row's disk name. The
46 * row id alone is not a safe identity — ids can be reissued after
47 * the table empties — but the disk name is a UUID, so the pair is.
48 */
49 const OPENSTATION_MEDIA_SOURCE_KEY_META = '_openstation_stored_file_key';
50
51 /**
52 * Whether a stored file is something the Media Library would accept.
53 *
54 * "Media" here is the WordPress definition — a MIME type on the
55 * site's upload allow-list — rather than only images, so a PDF or
56 * an audio file qualifies while an `.exe` never does.
57 *
58 * @param array $row Stored-file row.
59 * @return bool
60 */
61 function openstation_stored_file_is_media( $row ) {
62 $mime = is_array( $row ) ? strtolower( trim( (string) ( $row['mime'] ?? '' ) ) ) : '';
63 $is_media = '' !== $mime && in_array( $mime, array_values( get_allowed_mime_types() ), true );
64
65 /**
66 * Filters whether a stored file may be copied into the Media
67 * Library (and therefore whether the tile offers it).
68 *
69 * @param bool $is_media Default: the MIME type is on the site's upload allow-list.
70 * @param array $row Stored-file row.
71 */
72 return (bool) apply_filters( 'openstation_stored_file_is_media', $is_media, $row );
73 }
74
75 /**
76 * The attachment previously copied from a stored file, if any.
77 *
78 * @param array $row Stored-file row.
79 * @return int Attachment id, or 0.
80 */
81 function openstation_stored_file_find_attachment( $row ) {
82 $file_id = is_array( $row ) ? (int) ( $row['id'] ?? 0 ) : 0;
83 $disk_name = is_array( $row ) ? (string) ( $row['disk_name'] ?? '' ) : '';
84 if ( $file_id <= 0 || '' === $disk_name ) {
85 return 0;
86 }
87 $found = get_posts(
88 array(
89 'post_type' => 'attachment',
90 'post_status' => array( 'inherit', 'private' ),
91 'posts_per_page' => 1,
92 'fields' => 'ids',
93 'orderby' => 'ID',
94 'order' => 'ASC',
95 'no_found_rows' => true,
96 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- one attachment per stored file; the lookup is bounded to one row.
97 'meta_query' => array(
98 array(
99 'key' => OPENSTATION_MEDIA_SOURCE_META,
100 'value' => (string) $file_id,
101 ),
102 array(
103 'key' => OPENSTATION_MEDIA_SOURCE_KEY_META,
104 'value' => $disk_name,
105 ),
106 ),
107 )
108 );
109 return $found ? (int) $found[0] : 0;
110 }
111
112 /**
113 * The filename a copy of a stored file is registered under.
114 *
115 * The display name is what the user sees, but a rename can leave it
116 * without an extension, and `wp_check_filetype_and_ext()` needs one
117 * that agrees with the bytes — so a missing extension is derived
118 * from the row's MIME type.
119 *
120 * @param array $row Stored-file row.
121 * @return string
122 */
123 function openstation_stored_file_media_filename( $row ) {
124 $name = sanitize_file_name( (string) $row['display_name'] );
125 if ( '' === $name ) {
126 $name = 'file';
127 }
128 $check = wp_check_filetype( $name );
129 if ( empty( $check['ext'] ) ) {
130 $ext = wp_get_default_extension_for_mime_type( (string) $row['mime'] );
131 if ( $ext ) {
132 $name .= '.' . $ext;
133 }
134 }
135 return $name;
136 }
137
138 /**
139 * Copy a stored file into the Media Library as an attachment.
140 *
141 * Idempotent per stored file: a second call returns the attachment
142 * the first one created. The caller is responsible for the
143 * capability check (`upload_files`); this helper checks read access
144 * to the stored file and the media policy.
145 *
146 * @param int $file_id Stored-file id.
147 * @param int $user_id Acting user; becomes the attachment author.
148 * @return array|WP_Error `{ attachment_id, created }`.
149 */
150 function openstation_stored_file_to_attachment( $file_id, $user_id ) {
151 $file_id = (int) $file_id;
152 $user_id = (int) $user_id;
153 $row = openstation_stored_files_get( $file_id );
154 if ( ! $row || ! openstation_stored_file_user_can_read( $file_id, $user_id ) ) {
155 return openstation_files_download_not_found();
156 }
157 if ( ! openstation_stored_file_is_media( $row ) ) {
158 return new WP_Error(
159 'openstation_stored_file_not_media',
160 __( 'This file type cannot be added to the Media Library.', 'desktop-mode' ),
161 array( 'status' => 415 )
162 );
163 }
164
165 $existing = openstation_stored_file_find_attachment( $row );
166 if ( $existing > 0 ) {
167 return array(
168 'attachment_id' => $existing,
169 'created' => false,
170 );
171 }
172
173 $path = openstation_stored_file_path( $row );
174 if ( ! $path || ! file_exists( $path ) ) {
175 return openstation_files_download_not_found();
176 }
177
178 require_once ABSPATH . 'wp-admin/includes/file.php';
179 require_once ABSPATH . 'wp-admin/includes/media.php';
180 require_once ABSPATH . 'wp-admin/includes/image.php';
181
182 // The sideload consumes its input file, and the stored bytes
183 // stay the placement's — so it gets a scratch copy.
184 $name = openstation_stored_file_media_filename( $row );
185 $tmp = wp_tempnam( $name );
186 if ( ! $tmp || ! copy( $path, $tmp ) ) {
187 if ( $tmp ) {
188 wp_delete_file( $tmp );
189 }
190 return new WP_Error(
191 'openstation_stored_file_copy_failed',
192 __( 'The file could not be copied.', 'desktop-mode' ),
193 array( 'status' => 500 )
194 );
195 }
196
197 $post_data = array(
198 'post_author' => $user_id,
199 );
200 /**
201 * Filters the attachment post fields for a stored file copied into
202 * the Media Library (`post_title`, `post_excerpt` for the caption,
203 * `post_content` for the description, …).
204 *
205 * @param array $post_data Fields passed to `media_handle_sideload()`.
206 * @param array $row Stored-file row.
207 * @param int $user_id Acting user.
208 */
209 $post_data = (array) apply_filters( 'openstation_stored_file_media_post_data', $post_data, $row, $user_id );
210
211 $attachment_id = media_handle_sideload(
212 array(
213 'name' => $name,
214 'tmp_name' => $tmp,
215 'type' => (string) $row['mime'],
216 'size' => (int) $row['size_bytes'],
217 ),
218 0,
219 null,
220 $post_data
221 );
222 if ( is_wp_error( $attachment_id ) ) {
223 if ( file_exists( $tmp ) ) {
224 wp_delete_file( $tmp );
225 }
226 $attachment_id->add_data( array( 'status' => 400 ) );
227 return $attachment_id;
228 }
229 $attachment_id = (int) $attachment_id;
230 update_post_meta( $attachment_id, OPENSTATION_MEDIA_SOURCE_META, (string) $file_id );
231 update_post_meta( $attachment_id, OPENSTATION_MEDIA_SOURCE_KEY_META, (string) $row['disk_name'] );
232
233 /**
234 * Fires after a stored file has been copied into the Media Library.
235 *
236 * @param int $attachment_id The new attachment.
237 * @param int $file_id Source stored-file id.
238 * @param int $user_id Acting user.
239 */
240 do_action( 'openstation_stored_file_added_to_media', $attachment_id, $file_id, $user_id );
241
242 return array(
243 'attachment_id' => $attachment_id,
244 'created' => true,
245 );
246 }
247
248 /**
249 * Block markup that places an attachment in a fresh post.
250 *
251 * @param int $attachment_id Attachment id.
252 * @return string Serialized block.
253 */
254 function openstation_attachment_block_markup( $attachment_id ) {
255 $attachment_id = (int) $attachment_id;
256 $mime = (string) get_post_mime_type( $attachment_id );
257 $url = (string) wp_get_attachment_url( $attachment_id );
258 $major = strtok( $mime, '/' );
259
260 if ( 'image' === $major ) {
261 $src = wp_get_attachment_image_url( $attachment_id, 'large' );
262 $src = $src ? $src : $url;
263 return sprintf(
264 '<!-- wp:image {"id":%1$d,"sizeSlug":"large","linkDestination":"none"} -->' .
265 '<figure class="wp-block-image size-large"><img src="%2$s" alt="" class="wp-image-%1$d"/></figure>' .
266 '<!-- /wp:image -->',
267 $attachment_id,
268 esc_url( $src )
269 );
270 }
271 if ( 'video' === $major ) {
272 return sprintf(
273 '<!-- wp:video {"id":%1$d} --><figure class="wp-block-video"><video controls src="%2$s"></video></figure><!-- /wp:video -->',
274 $attachment_id,
275 esc_url( $url )
276 );
277 }
278 if ( 'audio' === $major ) {
279 return sprintf(
280 '<!-- wp:audio {"id":%1$d} --><figure class="wp-block-audio"><audio controls src="%2$s"></audio></figure><!-- /wp:audio -->',
281 $attachment_id,
282 esc_url( $url )
283 );
284 }
285 $title = get_the_title( $attachment_id );
286 $title = '' !== $title ? $title : wp_basename( $url );
287 return sprintf(
288 '<!-- wp:file {"id":%1$d,"href":"%2$s"} --><div class="wp-block-file"><a href="%2$s">%3$s</a>' .
289 '<a href="%2$s" class="wp-block-file__button wp-element-button" download>%4$s</a></div><!-- /wp:file -->',
290 $attachment_id,
291 esc_url( $url ),
292 esc_html( $title ),
293 esc_html__( 'Download', 'desktop-mode' )
294 );
295 }
296
297 /**
298 * Start a new post (or page, or any post type) from a stored file.
299 *
300 * Copies the file into the Media Library first (idempotently), then
301 * inserts an `auto-draft` whose content is that attachment as a
302 * block, with the attachment as the featured image when it is one.
303 *
304 * @param int $file_id Stored-file id.
305 * @param string $post_type Post type to create.
306 * @param int $user_id Acting user; becomes the author.
307 * @return array|WP_Error `{ post_id, post_type, attachment_id, created, edit_url }`.
308 */
309 function openstation_stored_file_start_post( $file_id, $post_type, $user_id ) {
310 $file_id = (int) $file_id;
311 $user_id = (int) $user_id;
312 $post_type = sanitize_key( (string) $post_type );
313 $pto = get_post_type_object( $post_type );
314 if ( ! $pto || ! post_type_supports( $post_type, 'editor' ) ) {
315 return new WP_Error(
316 'openstation_stored_file_bad_post_type',
317 __( 'That post type cannot be started from a file.', 'desktop-mode' ),
318 array( 'status' => 400 )
319 );
320 }
321 if ( ! user_can( $user_id, $pto->cap->create_posts ) ) {
322 return new WP_Error(
323 'openstation_stored_file_cannot_create_posts',
324 __( 'You are not allowed to create this kind of content.', 'desktop-mode' ),
325 array( 'status' => 403 )
326 );
327 }
328
329 $attached = openstation_stored_file_to_attachment( $file_id, $user_id );
330 if ( is_wp_error( $attached ) ) {
331 return $attached;
332 }
333 $attachment_id = (int) $attached['attachment_id'];
334 $row = openstation_stored_files_get( $file_id );
335
336 $content = openstation_attachment_block_markup( $attachment_id );
337 /**
338 * Filters the initial content of a post started from a stored file.
339 *
340 * @param string $content Serialized block markup.
341 * @param int $attachment_id The Media Library copy.
342 * @param string $post_type Post type being created.
343 * @param array $row Stored-file row.
344 */
345 $content = (string) apply_filters( 'openstation_stored_file_start_post_content', $content, $attachment_id, $post_type, $row );
346
347 $args = array(
348 'post_type' => $post_type,
349 'post_status' => 'auto-draft',
350 'post_title' => '',
351 'post_content' => $content,
352 'post_author' => $user_id,
353 );
354 /**
355 * Filters the `wp_insert_post()` arguments of a post started from
356 * a stored file. Keep `post_status` at `auto-draft` unless you
357 * want abandoned starts to persist as drafts.
358 *
359 * @param array $args Insert arguments.
360 * @param int $attachment_id The Media Library copy.
361 * @param string $post_type Post type being created.
362 * @param array $row Stored-file row.
363 */
364 $args = (array) apply_filters( 'openstation_stored_file_start_post_args', $args, $attachment_id, $post_type, $row );
365
366 $post_id = wp_insert_post( wp_slash( $args ), true );
367 if ( is_wp_error( $post_id ) ) {
368 $post_id->add_data( array( 'status' => 500 ) );
369 return $post_id;
370 }
371 $post_id = (int) $post_id;
372
373 if ( wp_attachment_is_image( $attachment_id ) && post_type_supports( $post_type, 'thumbnail' ) ) {
374 set_post_thumbnail( $post_id, $attachment_id );
375 }
376
377 /**
378 * Fires after a post has been started from a stored file.
379 *
380 * @param int $post_id The new auto-draft.
381 * @param int $attachment_id The Media Library copy.
382 * @param int $file_id Source stored-file id.
383 * @param int $user_id Acting user.
384 */
385 do_action( 'openstation_stored_file_post_started', $post_id, $attachment_id, $file_id, $user_id );
386
387 return array(
388 'post_id' => $post_id,
389 'post_type' => $post_type,
390 'attachment_id' => $attachment_id,
391 'created' => (bool) $attached['created'],
392 'edit_url' => admin_url( sprintf( 'post.php?post=%d&action=edit', $post_id ) ),
393 );
394 }
395
396 /**
397 * Put stored files into an existing post.
398 *
399 * Each file is copied into the Media Library (idempotently), its
400 * block is appended to the post content when the post type has an
401 * editor, it is attached to the post when it was attached to nothing,
402 * and the first image becomes the featured image when the post type
403 * supports one and the post has none. All-or-nothing on the copies:
404 * a file that cannot be copied fails the request before the post is
405 * touched.
406 *
407 * @param int $post_id Target post.
408 * @param int[] $file_ids Stored-file ids, in the order they were dragged.
409 * @param int $user_id Acting user; must be able to edit the post.
410 * @return array|WP_Error `{ post_id, attachment_ids, appended, featured_image_set, edit_url }`.
411 */
412 function openstation_stored_files_attach_to_post( $post_id, $file_ids, $user_id ) {
413 $post_id = (int) $post_id;
414 $user_id = (int) $user_id;
415 $file_ids = array_values( array_unique( array_filter( array_map( 'intval', (array) $file_ids ) ) ) );
416 $post = get_post( $post_id );
417 if ( ! $post || in_array( $post->post_type, array( 'attachment', 'revision' ), true ) ) {
418 return new WP_Error(
419 'openstation_stored_file_post_not_found',
420 __( 'Post not found.', 'desktop-mode' ),
421 array( 'status' => 404 )
422 );
423 }
424 if ( ! user_can( $user_id, 'edit_post', $post_id ) ) {
425 return new WP_Error(
426 'openstation_stored_file_cannot_edit_post',
427 __( 'You are not allowed to edit this post.', 'desktop-mode' ),
428 array( 'status' => 403 )
429 );
430 }
431 if ( 'trash' === $post->post_status ) {
432 return new WP_Error(
433 'openstation_stored_file_post_trashed',
434 __( 'That post is in the Trash.', 'desktop-mode' ),
435 array( 'status' => 400 )
436 );
437 }
438 if ( empty( $file_ids ) ) {
439 return new WP_Error(
440 'openstation_stored_file_no_files',
441 __( 'No files to add.', 'desktop-mode' ),
442 array( 'status' => 400 )
443 );
444 }
445
446 $attachment_ids = array();
447 foreach ( $file_ids as $file_id ) {
448 $attached = openstation_stored_file_to_attachment( $file_id, $user_id );
449 if ( is_wp_error( $attached ) ) {
450 return $attached;
451 }
452 $attachment_ids[] = (int) $attached['attachment_id'];
453 }
454
455 $markup = implode( "\n\n", array_map( 'openstation_attachment_block_markup', $attachment_ids ) );
456 /**
457 * Filters the block markup appended to a post when stored files
458 * are dropped onto it.
459 *
460 * @param string $markup Serialized blocks, one per attachment.
461 * @param int[] $attachment_ids The Media Library copies, in drop order.
462 * @param WP_Post $post The post being extended.
463 * @param int[] $file_ids Source stored-file ids.
464 */
465 $markup = (string) apply_filters( 'openstation_stored_file_attach_content', $markup, $attachment_ids, $post, $file_ids );
466
467 $appended = false;
468 if ( post_type_supports( $post->post_type, 'editor' ) && '' !== $markup ) {
469 $content = '' === trim( $post->post_content ) ? $markup : rtrim( $post->post_content ) . "\n\n" . $markup;
470 $updated = wp_update_post(
471 wp_slash(
472 array(
473 'ID' => $post_id,
474 'post_content' => $content,
475 )
476 ),
477 true
478 );
479 if ( is_wp_error( $updated ) ) {
480 $updated->add_data( array( 'status' => 500 ) );
481 return $updated;
482 }
483 $appended = true;
484 }
485
486 $featured_image_set = false;
487 foreach ( $attachment_ids as $attachment_id ) {
488 if ( 0 === (int) get_post_field( 'post_parent', $attachment_id ) ) {
489 wp_update_post(
490 array(
491 'ID' => $attachment_id,
492 'post_parent' => $post_id,
493 )
494 );
495 }
496 if (
497 ! $featured_image_set &&
498 post_type_supports( $post->post_type, 'thumbnail' ) &&
499 ! has_post_thumbnail( $post_id ) &&
500 wp_attachment_is_image( $attachment_id )
501 ) {
502 $featured_image_set = (bool) set_post_thumbnail( $post_id, $attachment_id );
503 }
504 }
505
506 /**
507 * Fires after stored files have been put into a post.
508 *
509 * @param int $post_id The post.
510 * @param int[] $attachment_ids The Media Library copies, in drop order.
511 * @param int[] $file_ids Source stored-file ids.
512 * @param int $user_id Acting user.
513 */
514 do_action( 'openstation_stored_file_attached_to_post', $post_id, $attachment_ids, $file_ids, $user_id );
515
516 return array(
517 'post_id' => $post_id,
518 'attachment_ids' => $attachment_ids,
519 'appended' => $appended,
520 'featured_image_set' => $featured_image_set,
521 'edit_url' => admin_url( sprintf( 'post.php?post=%d&action=edit', $post_id ) ),
522 );
523 }
524
525 /**
526 * Permission callback: the base files gate plus WordPress's own
527 * Media Library capability.
528 *
529 * @return true|WP_Error
530 */
531 function openstation_files_rest_media_permission() {
532 $base = openstation_files_rest_permission();
533 if ( true !== $base ) {
534 return $base;
535 }
536 if ( ! current_user_can( 'upload_files' ) ) {
537 return new WP_Error(
538 'openstation_stored_file_cannot_add_to_media',
539 __( 'You are not allowed to add files to the Media Library.', 'desktop-mode' ),
540 array( 'status' => 403 )
541 );
542 }
543 return true;
544 }
545
546 /**
547 * Register the two routes.
548 */
549 function openstation_files_register_media_rest_routes() {
550 register_rest_route(
551 'desktop-mode/v1',
552 '/files/uploads/(?P<id>\d+)/media',
553 array(
554 'methods' => WP_REST_Server::CREATABLE,
555 'permission_callback' => 'openstation_files_rest_media_permission',
556 'callback' => 'openstation_files_rest_add_to_media',
557 )
558 );
559 register_rest_route(
560 'desktop-mode/v1',
561 '/files/uploads/(?P<id>\d+)/post',
562 array(
563 'methods' => WP_REST_Server::CREATABLE,
564 'permission_callback' => 'openstation_files_rest_media_permission',
565 'callback' => 'openstation_files_rest_start_post',
566 'args' => array(
567 'postType' => array(
568 'type' => 'string',
569 'default' => 'post',
570 ),
571 ),
572 )
573 );
574 register_rest_route(
575 'desktop-mode/v1',
576 '/files/posts/(?P<id>\d+)/uploads',
577 array(
578 'methods' => WP_REST_Server::CREATABLE,
579 'permission_callback' => 'openstation_files_rest_media_permission',
580 'callback' => 'openstation_files_rest_attach_to_post',
581 'args' => array(
582 'fileIds' => array(
583 'type' => 'array',
584 'required' => true,
585 'items' => array( 'type' => 'integer' ),
586 ),
587 ),
588 )
589 );
590 }
591 add_action( 'rest_api_init', 'openstation_files_register_media_rest_routes' );
592
593 /**
594 * The attachment summary both routes return.
595 *
596 * @param int $attachment_id Attachment id.
597 * @param bool $created Whether this request created it.
598 * @return array
599 */
600 function openstation_files_attachment_summary( $attachment_id, $created ) {
601 $attachment_id = (int) $attachment_id;
602 return array(
603 'attachmentId' => $attachment_id,
604 'created' => (bool) $created,
605 'title' => get_the_title( $attachment_id ),
606 'url' => (string) wp_get_attachment_url( $attachment_id ),
607 'editUrl' => admin_url( sprintf( 'post.php?post=%d&action=edit', $attachment_id ) ),
608 );
609 }
610
611 /**
612 * POST /files/uploads/<id>/media
613 *
614 * @param WP_REST_Request $req Request.
615 * @return WP_REST_Response|WP_Error
616 */
617 function openstation_files_rest_add_to_media( WP_REST_Request $req ) {
618 $result = openstation_stored_file_to_attachment( (int) $req['id'], get_current_user_id() );
619 if ( is_wp_error( $result ) ) {
620 return $result;
621 }
622 return rest_ensure_response(
623 openstation_files_attachment_summary( $result['attachment_id'], $result['created'] )
624 );
625 }
626
627 /**
628 * POST /files/uploads/<id>/post
629 *
630 * @param WP_REST_Request $req Request.
631 * @return WP_REST_Response|WP_Error
632 */
633 function openstation_files_rest_start_post( WP_REST_Request $req ) {
634 $result = openstation_stored_file_start_post(
635 (int) $req['id'],
636 (string) $req->get_param( 'postType' ),
637 get_current_user_id()
638 );
639 if ( is_wp_error( $result ) ) {
640 return $result;
641 }
642 $summary = openstation_files_attachment_summary( $result['attachment_id'], $result['created'] );
643 return rest_ensure_response(
644 array(
645 'postId' => (int) $result['post_id'],
646 'postType' => (string) $result['post_type'],
647 'editUrl' => (string) $result['edit_url'],
648 'attachment' => $summary,
649 )
650 );
651 }
652
653 /**
654 * POST /files/posts/<id>/uploads { fileIds }
655 *
656 * @param WP_REST_Request $req Request.
657 * @return WP_REST_Response|WP_Error
658 */
659 function openstation_files_rest_attach_to_post( WP_REST_Request $req ) {
660 $result = openstation_stored_files_attach_to_post(
661 (int) $req['id'],
662 (array) $req->get_param( 'fileIds' ),
663 get_current_user_id()
664 );
665 if ( is_wp_error( $result ) ) {
666 return $result;
667 }
668 $attachments = array();
669 foreach ( $result['attachment_ids'] as $attachment_id ) {
670 $attachments[] = openstation_files_attachment_summary( $attachment_id, false );
671 }
672 return rest_ensure_response(
673 array(
674 'postId' => (int) $result['post_id'],
675 'title' => get_the_title( $result['post_id'] ),
676 'editUrl' => (string) $result['edit_url'],
677 'appended' => (bool) $result['appended'],
678 'featuredImageSet' => (bool) $result['featured_image_set'],
679 'attachments' => $attachments,
680 )
681 );
682 }
683
684 /**
685 * Whether the current user may start `$post_type` content.
686 *
687 * @param string $post_type Post type.
688 * @return bool
689 */
690 function openstation_user_can_start_post_type( $post_type ) {
691 $pto = get_post_type_object( $post_type );
692 return $pto && post_type_supports( $post_type, 'editor' ) && current_user_can( $pto->cap->create_posts );
693 }
694
695 /**
696 * Shell-config injection: which of the media actions the viewer may
697 * take, so the tile menu does not offer what the server would 403.
698 *
699 * @param array $config Shell config.
700 * @return array
701 */
702 function openstation_stored_files_inject_media_shell_config( $config ) {
703 $storage = isset( $config['desktopStorage'] ) && is_array( $config['desktopStorage'] )
704 ? $config['desktopStorage']
705 : array();
706 $can_media = is_user_logged_in() && current_user_can( 'upload_files' );
707 $storage['canAddToMedia'] = $can_media;
708 $storage['canStartPost'] = $can_media && openstation_user_can_start_post_type( 'post' );
709 $storage['canStartPage'] = $can_media && openstation_user_can_start_post_type( 'page' );
710 $config['desktopStorage'] = $storage;
711 return $config;
712 }
713 add_filter( 'openstation_shell_config', 'openstation_stored_files_inject_media_shell_config', 21 );
714