PluginProbe
Gutenberg / 22.7.1
Gutenberg v22.7.1
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 12.6.0 7.4.0 All 402 releases
gutenberg / lib / media / load.php

load.php in Gutenberg 22.7.1, at lib/media/load.php

384 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Adds media-related functionality for client-side media processing.
4 *
5 * @package gutenberg
6 */
7
8 if ( ! gutenberg_is_client_side_media_processing_enabled() ) {
9 return;
10 }
11
12 /**
13 * Sets a global JS variable to indicate that client-side media processing is enabled.
14 */
15 function gutenberg_set_client_side_media_processing_flag() {
16 if ( ! gutenberg_is_client_side_media_processing_enabled() ) {
17 return;
18 }
19 wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' );
20 }
21 add_action( 'admin_init', 'gutenberg_set_client_side_media_processing_flag' );
22
23 /**
24 * Returns a list of all available image sizes.
25 *
26 * @return array Existing image sizes.
27 */
28 function gutenberg_get_all_image_sizes(): array {
29 $sizes = wp_get_registered_image_subsizes();
30
31 foreach ( $sizes as $name => &$size ) {
32 $size['height'] = (int) $size['height'];
33 $size['width'] = (int) $size['width'];
34 $size['name'] = $name;
35 }
36 unset( $size );
37
38 return $sizes;
39 }
40
41 /**
42 * Returns the default output format mapping for the supported image formats.
43 *
44 * @return array<string,string> Map of input formats to output formats.
45 */
46 function gutenberg_get_default_image_output_formats() {
47 $input_formats = array(
48 'image/jpeg',
49 'image/png',
50 'image/gif',
51 'image/webp',
52 'image/avif',
53 'image/heic',
54 );
55
56 $output_formats = array();
57
58 foreach ( $input_formats as $mime_type ) {
59 /** This filter is documented in wp-includes/media.php */
60 $output_formats = apply_filters(
61 'image_editor_output_format', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
62 $output_formats,
63 '',
64 $mime_type
65 );
66 }
67
68 return $output_formats;
69 }
70
71 /**
72 * Filters the REST API root index data to add custom settings.
73 *
74 * @param WP_REST_Response $response Response data.
75 */
76 function gutenberg_media_processing_filter_rest_index( WP_REST_Response $response ) {
77 /** This filter is documented in wp-admin/includes/images.php */
78 $image_size_threshold = (int) apply_filters( 'big_image_size_threshold', 2560, array( 0, 0 ), '', 0 ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
79
80 $default_image_output_formats = gutenberg_get_default_image_output_formats();
81
82 /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
83 $jpeg_interlaced = (bool) apply_filters( 'image_save_progressive', false, 'image/jpeg' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
84 /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
85 $png_interlaced = (bool) apply_filters( 'image_save_progressive', false, 'image/png' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
86 /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
87 $gif_interlaced = (bool) apply_filters( 'image_save_progressive', false, 'image/gif' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
88
89 if ( current_user_can( 'upload_files' ) ) {
90 $response->data['image_sizes'] = gutenberg_get_all_image_sizes();
91 $response->data['image_size_threshold'] = $image_size_threshold;
92 $response->data['image_output_formats'] = (object) $default_image_output_formats;
93 $response->data['jpeg_interlaced'] = $jpeg_interlaced;
94 $response->data['png_interlaced'] = $png_interlaced;
95 $response->data['gif_interlaced'] = $gif_interlaced;
96 }
97
98 return $response;
99 }
100
101 add_filter( 'rest_index', 'gutenberg_media_processing_filter_rest_index' );
102
103
104 /**
105 * Overrides the REST controller for the attachment post type.
106 *
107 * @param array $args Array of arguments for registering a post type.
108 * See the register_post_type() function for accepted arguments.
109 * @param string $post_type Post type key.
110 */
111 function gutenberg_filter_attachment_post_type_args( array $args, string $post_type ): array {
112 if ( 'attachment' === $post_type ) {
113 require_once __DIR__ . '/class-gutenberg-rest-attachments-controller.php';
114
115 $args['rest_controller_class'] = Gutenberg_REST_Attachments_Controller::class;
116 }
117
118 return $args;
119 }
120
121 add_filter( 'register_post_type_args', 'gutenberg_filter_attachment_post_type_args', 10, 2 );
122
123
124 /**
125 * Registers additional REST fields for attachments.
126 */
127 function gutenberg_media_processing_register_rest_fields(): void {
128 register_rest_field(
129 'attachment',
130 'filename',
131 array(
132 'schema' => array(
133 'description' => __( 'Original attachment file name', 'gutenberg' ),
134 'type' => 'string',
135 'context' => array( 'view', 'edit' ),
136 ),
137 'get_callback' => 'gutenberg_rest_get_attachment_filename',
138 )
139 );
140
141 register_rest_field(
142 'attachment',
143 'filesize',
144 array(
145 'schema' => array(
146 'description' => __( 'Attachment file size', 'gutenberg' ),
147 'type' => 'number',
148 'context' => array( 'view', 'edit' ),
149 ),
150 'get_callback' => 'gutenberg_rest_get_attachment_filesize',
151 )
152 );
153 }
154
155 add_action( 'rest_api_init', 'gutenberg_media_processing_register_rest_fields' );
156
157 /**
158 * Returns the attachment's original file name.
159 *
160 * @param array $post Post data.
161 * @return string|null Attachment file name.
162 */
163 function gutenberg_rest_get_attachment_filename( array $post ): ?string {
164 $path = wp_get_original_image_path( $post['id'] );
165
166 if ( $path ) {
167 return basename( $path );
168 }
169
170 $path = get_attached_file( $post['id'] );
171
172 if ( $path ) {
173 return basename( $path );
174 }
175
176 return null;
177 }
178
179 /**
180 * Returns the attachment's file size in bytes.
181 *
182 * @param array $post Post data.
183 * @return int|null Attachment file size.
184 */
185 function gutenberg_rest_get_attachment_filesize( array $post ): ?int {
186 $attachment_id = $post['id'];
187
188 $meta = wp_get_attachment_metadata( $attachment_id );
189
190 if ( isset( $meta['filesize'] ) ) {
191 return $meta['filesize'];
192 }
193
194 $original_path = wp_get_original_image_path( $attachment_id );
195 $attached_file = $original_path ? $original_path : get_attached_file( $attachment_id );
196
197 if ( is_string( $attached_file ) && file_exists( $attached_file ) ) {
198 return wp_filesize( $attached_file );
199 }
200
201 return null;
202 }
203
204 /**
205 * Filters the list of rewrite rules formatted for output to an .htaccess file.
206 *
207 * Adds support for serving wasm-vips locally.
208 *
209 * @param string $rules mod_rewrite Rewrite rules formatted for .htaccess.
210 * @return string Filtered rewrite rules.
211 */
212 function gutenberg_filter_mod_rewrite_rules( string $rules ): string {
213 $rules .= "\n# BEGIN Gutenberg client-side media processing\n" .
214 "AddType application/wasm wasm\n" .
215 "# END Gutenberg client-side media processing\n";
216
217 return $rules;
218 }
219
220 add_filter( 'mod_rewrite_rules', 'gutenberg_filter_mod_rewrite_rules' );
221
222 /**
223 * Enables cross-origin isolation in the block editor.
224 *
225 * Required for enabling SharedArrayBuffer for WebAssembly-based
226 * media processing in the editor.
227 *
228 * @link https://web.dev/coop-coep/
229 */
230 function gutenberg_set_up_cross_origin_isolation() {
231 // Re-check the filter at action time, since other plugins (loaded after Gutenberg)
232 // may have added a filter to disable client-side media processing.
233 if ( ! gutenberg_is_client_side_media_processing_enabled() ) {
234 return;
235 }
236
237 $screen = get_current_screen();
238
239 if ( ! $screen ) {
240 return;
241 }
242
243 if ( ! $screen->is_block_editor() && 'site-editor' !== $screen->id && ! ( 'widgets' === $screen->id && wp_use_widgets_block_editor() ) ) {
244 return;
245 }
246
247 $user_id = get_current_user_id();
248 if ( ! $user_id ) {
249 return;
250 }
251
252 // Cross-origin isolation is not needed if users can't upload files anyway.
253 if ( ! user_can( $user_id, 'upload_files' ) ) {
254 return;
255 }
256
257 gutenberg_start_cross_origin_isolation_output_buffer();
258 }
259
260 add_action( 'load-post.php', 'gutenberg_set_up_cross_origin_isolation' );
261 add_action( 'load-post-new.php', 'gutenberg_set_up_cross_origin_isolation' );
262 add_action( 'load-site-editor.php', 'gutenberg_set_up_cross_origin_isolation' );
263 add_action( 'load-widgets.php', 'gutenberg_set_up_cross_origin_isolation' );
264
265 /**
266 * Sends headers for cross-origin isolation.
267 *
268 * Uses an output buffer to add crossorigin="anonymous" where needed.
269 *
270 * @link https://web.dev/coop-coep/
271 *
272 * @global bool $is_safari
273 */
274 function gutenberg_start_cross_origin_isolation_output_buffer(): void {
275 global $is_safari;
276
277 $coep = $is_safari ? 'require-corp' : 'credentialless';
278
279 ob_start(
280 function ( string $output ) use ( $coep ): string {
281 header( 'Cross-Origin-Opener-Policy: same-origin' );
282 header( "Cross-Origin-Embedder-Policy: $coep" );
283
284 return gutenberg_add_crossorigin_attributes( $output );
285 }
286 );
287 }
288
289 /**
290 * Adds crossorigin="anonymous" to relevant tags in the given HTML string.
291 *
292 * @param string $html HTML input.
293 *
294 * @return string Modified HTML.
295 */
296 function gutenberg_add_crossorigin_attributes( string $html ): string {
297 $site_url = site_url();
298
299 $processor = new WP_HTML_Tag_Processor( $html );
300
301 // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin.
302 $tags = array(
303 'AUDIO' => 'src',
304 'IMG' => 'src',
305 'LINK' => 'href',
306 'SCRIPT' => 'src',
307 'VIDEO' => 'src',
308 'SOURCE' => 'src',
309 );
310
311 $tag_names = array_keys( $tags );
312
313 while ( $processor->next_tag() ) {
314 $tag = $processor->get_tag();
315
316 if ( ! in_array( $tag, $tag_names, true ) ) {
317 continue;
318 }
319
320 if ( 'AUDIO' === $tag || 'VIDEO' === $tag ) {
321 $processor->set_bookmark( 'audio-video-parent' );
322 }
323
324 $processor->set_bookmark( 'resume' );
325
326 $sought = false;
327
328 $crossorigin = $processor->get_attribute( 'crossorigin' );
329
330 $url = $processor->get_attribute( $tags[ $tag ] );
331
332 if ( is_string( $url ) && ! str_starts_with( $url, $site_url ) && ! str_starts_with( $url, '/' ) && ! is_string( $crossorigin ) ) {
333 if ( 'SOURCE' === $tag ) {
334 $sought = $processor->seek( 'audio-video-parent' );
335
336 if ( $sought ) {
337 $processor->set_attribute( 'crossorigin', 'anonymous' );
338 }
339 } else {
340 $processor->set_attribute( 'crossorigin', 'anonymous' );
341 }
342
343 if ( $sought ) {
344 $processor->seek( 'resume' );
345 $processor->release_bookmark( 'audio-video-parent' );
346 }
347 }
348 }
349
350 return $processor->get_updated_html();
351 }
352
353 /**
354 * Overrides templates from wp_print_media_templates with custom ones.
355 *
356 * Adds `crossorigin` attribute to all tags that
357 * could have assets loaded from a different domain.
358 */
359 function gutenberg_override_media_templates(): void {
360 remove_action( 'admin_footer', 'wp_print_media_templates' );
361 add_action(
362 'admin_footer',
363 static function (): void {
364 ob_start();
365 wp_print_media_templates();
366 $html = (string) ob_get_clean();
367
368 $tags = array(
369 'audio',
370 'img',
371 'video',
372 );
373
374 foreach ( $tags as $tag ) {
375 $html = (string) str_replace( "<$tag", "<$tag crossorigin=\"anonymous\"", $html );
376 }
377
378 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
379 }
380 );
381 }
382
383 add_action( 'wp_enqueue_media', 'gutenberg_override_media_templates' );
384