` rather than as a query * argument of its own. * * @param int $post_id Attachment ID. * @param string $separator Argument separator. `&` when the URL is * destined for HTML output, `&` otherwise. * @return string The media editor URL. */ function gutenberg_media_editor_get_url( $post_id, $separator = '&' ) { return admin_url( 'admin.php?page=media-editor-wp-admin' . $separator . 'p=' . rawurlencode( '/media-editor/' . (int) $post_id ) ); } /** * Points every "edit this attachment" link at the media editor. * * Filtering here rather than redirecting means the Media Library list table, * the media modal's "Edit more details" and "Edit Image" links, and the admin * bar all navigate straight to the media editor with no redirect hop. * * @param string|null $link The edit link, or null when the user cannot edit the post. * @param int $post_id Post ID. * @param string $context The link context. `display` expects an HTML-escaped separator. * @return string|null The filtered edit link. */ function gutenberg_media_editor_filter_edit_post_link( $link, $post_id, $context ) { // A null link means the user lacks the capability. Leave that alone. if ( ! $link || 'attachment' !== get_post_type( $post_id ) ) { return $link; } // `edit_post` is enough for the classic screen, but the media editor page // requires `upload_files`. A contributor can own an attachment and edit it // without being able to upload, so pointing them at the media editor would // send them to a screen they cannot open. if ( ! current_user_can( 'upload_files' ) ) { return $link; } return gutenberg_media_editor_get_url( $post_id, 'display' === $context ? '&' : '&' ); } add_filter( 'get_edit_post_link', 'gutenberg_media_editor_filter_edit_post_link', 10, 3 ); /** * Redirects the classic Edit Media screen to the media editor. * * The `get_edit_post_link` filter above covers links rendered by WordPress, * but bookmarks, hand-typed URLs, and hard-coded links still reach post.php. * This catches those. It deliberately bails on the failure paths so that * WordPress keeps rendering its own error messages instead of sending people * to a screen that would only fail again. */ function gutenberg_media_editor_redirect_classic_screen() { // GET renders the form. POST is the editattachment/editpost save handler, // which must run untouched. if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) { return; } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reading the same unauthenticated query args post.php itself reads to decide what to render. if ( ! isset( $_GET['action'] ) || 'edit' !== $_GET['action'] ) { return; } // Guard against `?post[]=1`, where an array casts to int 1 without warning. $post_id = isset( $_GET['post'] ) && is_scalar( $_GET['post'] ) ? (int) $_GET['post'] : 0; // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( ! $post_id || 'attachment' !== get_post_type( $post_id ) ) { return; } // Let post.php own these: it already renders the appropriate wp_die(). if ( ! current_user_can( 'edit_post', $post_id ) || 'trash' === get_post_status( $post_id ) ) { return; } // The media editor page requires `upload_files`, which `edit_post` does not // imply. Without this, a contributor who owns an attachment would be // redirected off a screen that works onto one that refuses them. if ( ! current_user_can( 'upload_files' ) ) { return; } wp_safe_redirect( gutenberg_media_editor_get_url( $post_id ) ); exit; } add_action( 'load-post.php', 'gutenberg_media_editor_redirect_classic_screen' );