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

load.php in Gutenberg 22.3.0, at lib/experimental/media/load.php

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