PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
gutenberg / lib / experimental / media-editor / load.php

load.php in Gutenberg trunk, at lib/experimental/media-editor/load.php

157 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bootstraps the Media Editor page in wp-admin.
4 *
5 * @package gutenberg
6 */
7
8 /**
9 * Registers a hidden wp-admin page for direct media editor deep links.
10 */
11 function gutenberg_register_media_editor_admin_page() {
12 // Register with an empty parent to create a hidden admin.php?page= route
13 // without adding a Media submenu item for an editor that requires an ID.
14 $hook_suffix = add_submenu_page(
15 '',
16 __( 'Edit media', 'gutenberg' ),
17 __( 'Edit media', 'gutenberg' ),
18 'upload_files',
19 'media-editor-wp-admin',
20 'gutenberg_media_editor_wp_admin_render_page'
21 );
22
23 if ( $hook_suffix ) {
24 add_action( "load-$hook_suffix", 'gutenberg_media_editor_wp_admin_prepare_screen' );
25 }
26 }
27
28 /**
29 * Prepares the admin chrome before wp-admin/admin-header.php renders.
30 *
31 * @global string $title The admin page title.
32 * @global string $parent_file The current top-level menu item.
33 * @global string $submenu_file The current submenu item.
34 */
35 function gutenberg_media_editor_wp_admin_prepare_screen() {
36 global $title, $parent_file, $submenu_file;
37
38 // Hidden pages do not resolve a title from a visible menu item, so set one
39 // before admin-header.php formats the page title.
40 $title = __( 'Edit media', 'gutenberg' );
41
42 /*
43 * Take the page out of the hidden `''` submenu bucket it was registered in.
44 * Left in place, get_admin_page_parent() matches it there and resets
45 * $parent_file to '' — and it does so *after* the `parent_file` filter runs,
46 * so filtering cannot win. With no match it preserves a non-empty
47 * $parent_file instead.
48 *
49 * Safe at this point: `load-` fires after the capability check in admin.php,
50 * which needs the page registered, and before the menu is rendered.
51 */
52 remove_submenu_page( '', 'media-editor-wp-admin' );
53
54 // Match the classic Edit Media screen, which the media editor stands in for:
55 // Media expanded and current, Library the current submenu item.
56 $parent_file = 'upload.php';
57 $submenu_file = 'upload.php';
58 }
59
60 add_action( 'admin_menu', 'gutenberg_register_media_editor_admin_page' );
61
62 /**
63 * Builds the media editor URL for an attachment.
64 *
65 * The router reads its internal path from the `p` query argument, so the
66 * attachment id travels as `p=/media-editor/<id>` rather than as a query
67 * argument of its own.
68 *
69 * @param int $post_id Attachment ID.
70 * @param string $separator Argument separator. `&amp;` when the URL is
71 * destined for HTML output, `&` otherwise.
72 * @return string The media editor URL.
73 */
74 function gutenberg_media_editor_get_url( $post_id, $separator = '&' ) {
75 return admin_url(
76 'admin.php?page=media-editor-wp-admin' . $separator . 'p=' . rawurlencode( '/media-editor/' . (int) $post_id )
77 );
78 }
79
80 /**
81 * Points every "edit this attachment" link at the media editor.
82 *
83 * Filtering here rather than redirecting means the Media Library list table,
84 * the media modal's "Edit more details" and "Edit Image" links, and the admin
85 * bar all navigate straight to the media editor with no redirect hop.
86 *
87 * @param string|null $link The edit link, or null when the user cannot edit the post.
88 * @param int $post_id Post ID.
89 * @param string $context The link context. `display` expects an HTML-escaped separator.
90 * @return string|null The filtered edit link.
91 */
92 function gutenberg_media_editor_filter_edit_post_link( $link, $post_id, $context ) {
93 // A null link means the user lacks the capability. Leave that alone.
94 if ( ! $link || 'attachment' !== get_post_type( $post_id ) ) {
95 return $link;
96 }
97
98 // `edit_post` is enough for the classic screen, but the media editor page
99 // requires `upload_files`. A contributor can own an attachment and edit it
100 // without being able to upload, so pointing them at the media editor would
101 // send them to a screen they cannot open.
102 if ( ! current_user_can( 'upload_files' ) ) {
103 return $link;
104 }
105
106 return gutenberg_media_editor_get_url( $post_id, 'display' === $context ? '&amp;' : '&' );
107 }
108
109 add_filter( 'get_edit_post_link', 'gutenberg_media_editor_filter_edit_post_link', 10, 3 );
110
111 /**
112 * Redirects the classic Edit Media screen to the media editor.
113 *
114 * The `get_edit_post_link` filter above covers links rendered by WordPress,
115 * but bookmarks, hand-typed URLs, and hard-coded links still reach post.php.
116 * This catches those. It deliberately bails on the failure paths so that
117 * WordPress keeps rendering its own error messages instead of sending people
118 * to a screen that would only fail again.
119 */
120 function gutenberg_media_editor_redirect_classic_screen() {
121 // GET renders the form. POST is the editattachment/editpost save handler,
122 // which must run untouched.
123 if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) {
124 return;
125 }
126
127 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reading the same unauthenticated query args post.php itself reads to decide what to render.
128 if ( ! isset( $_GET['action'] ) || 'edit' !== $_GET['action'] ) {
129 return;
130 }
131
132 // Guard against `?post[]=1`, where an array casts to int 1 without warning.
133 $post_id = isset( $_GET['post'] ) && is_scalar( $_GET['post'] ) ? (int) $_GET['post'] : 0;
134 // phpcs:enable WordPress.Security.NonceVerification.Recommended
135
136 if ( ! $post_id || 'attachment' !== get_post_type( $post_id ) ) {
137 return;
138 }
139
140 // Let post.php own these: it already renders the appropriate wp_die().
141 if ( ! current_user_can( 'edit_post', $post_id ) || 'trash' === get_post_status( $post_id ) ) {
142 return;
143 }
144
145 // The media editor page requires `upload_files`, which `edit_post` does not
146 // imply. Without this, a contributor who owns an attachment would be
147 // redirected off a screen that works onto one that refuses them.
148 if ( ! current_user_can( 'upload_files' ) ) {
149 return;
150 }
151
152 wp_safe_redirect( gutenberg_media_editor_get_url( $post_id ) );
153 exit;
154 }
155
156 add_action( 'load-post.php', 'gutenberg_media_editor_redirect_classic_screen' );
157