PluginProbe ʕ •ᴥ•ʔ
Modern Image Formats / 2.0.0
Modern Image Formats v2.0.0
2.7.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 2.0.0 2.0.1 2.0.2 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.5.1 2.6.0 2.6.1
webp-uploads / hooks.php
webp-uploads Last commit date
deprecated.php 2 years ago helper.php 2 years ago hooks.php 2 years ago image-edit.php 2 years ago load.php 2 years ago picture-element.php 2 years ago readme.txt 2 years ago rest-api.php 2 years ago settings.php 2 years ago uninstall.php 2 years ago
hooks.php
764 lines
1 <?php
2 /**
3 * Hook callbacks used for Modern Image Formats.
4 *
5 * @package webp-uploads
6 *
7 * @since 1.0.0
8 */
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 /**
15 * Hook called by `wp_generate_attachment_metadata` to create the `sources` property for every image
16 * size, the sources' property would create a new image size with all the mime types specified in
17 * `webp_uploads_get_upload_image_mime_transforms`. If the original image is one of the mimes from
18 * `webp_uploads_get_upload_image_mime_transforms` the image is just added to the `sources` property and not
19 * created again. If the uploaded attachment is not a supported mime by this function, the hook does not alter the
20 * metadata of the attachment. In addition to every single size the `sources` property is added at the
21 * top level of the image metadata to store the references for all the mime types for the `full` size image of the
22 * attachment.
23 *
24 * @since 1.0.0
25 *
26 * @see wp_generate_attachment_metadata()
27 * @see webp_uploads_get_upload_image_mime_transforms()
28 *
29 * @phpstan-param array{
30 * width: int,
31 * height: int,
32 * file: string,
33 * sizes: array<string, array{ file: string, width: int, height: int, 'mime-type': string }>,
34 * image_meta: array<string, mixed>,
35 * filesize: int
36 * } $metadata
37 *
38 * @param array<string, mixed> $metadata An array with the metadata from this attachment.
39 * @param int $attachment_id The ID of the attachment where the hook was dispatched.
40 *
41 * @return array{
42 * width: int,
43 * height: int,
44 * file: string,
45 * sizes: array<string, array{ file: string, width: int, height: int, 'mime-type': string, sources?: array<string, array{ file: string, filesize: int }> }>,
46 * image_meta: array<string, mixed>,
47 * filesize: int,
48 * sources?: array<string, array{
49 * file: string,
50 * filesize: int
51 * }>
52 * } An array with the updated structure for the metadata before is stored in the database.
53 */
54 function webp_uploads_create_sources_property( array $metadata, int $attachment_id ): array {
55 // This should take place only on the JPEG image.
56 $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms();
57
58 // Not a supported mime type to create the sources property.
59 $mime_type = get_post_mime_type( $attachment_id );
60 if ( ! is_string( $mime_type ) || ! isset( $valid_mime_transforms[ $mime_type ] ) ) {
61 return $metadata;
62 }
63
64 $file = get_attached_file( $attachment_id, true );
65 // File does not exist.
66 if ( ! $file || ! file_exists( $file ) ) {
67 return $metadata;
68 }
69
70 // Make sure the top level `sources` key is a valid array.
71 if ( ! isset( $metadata['sources'] ) || ! is_array( $metadata['sources'] ) ) {
72 $metadata['sources'] = array();
73 }
74
75 if ( empty( $metadata['sources'][ $mime_type ] ) ) {
76 $metadata['sources'][ $mime_type ] = array(
77 'file' => wp_basename( $file ),
78 'filesize' => wp_filesize( $file ),
79 );
80 wp_update_attachment_metadata( $attachment_id, $metadata );
81 }
82
83 $original_size_data = array(
84 'width' => isset( $metadata['width'] ) ? (int) $metadata['width'] : 0,
85 'height' => isset( $metadata['height'] ) ? (int) $metadata['height'] : 0,
86 'crop' => false,
87 );
88
89 $original_directory = pathinfo( $file, PATHINFO_DIRNAME );
90 $filename = pathinfo( $file, PATHINFO_FILENAME );
91 $ext = pathinfo( $file, PATHINFO_EXTENSION );
92 $allowed_mimes = array_flip( wp_get_mime_types() );
93
94 // Create the sources for the full sized image.
95 foreach ( $valid_mime_transforms[ $mime_type ] as $targeted_mime ) {
96 // If this property exists no need to create the image again.
97 if ( ! empty( $metadata['sources'][ $targeted_mime ] ) ) {
98 continue;
99 }
100
101 // The targeted mime is not allowed in the current installation.
102 if ( empty( $allowed_mimes[ $targeted_mime ] ) ) {
103 continue;
104 }
105
106 $extension = explode( '|', $allowed_mimes[ $targeted_mime ] );
107 $destination = trailingslashit( $original_directory ) . "{$filename}-{$ext}.{$extension[0]}";
108 $image = webp_uploads_generate_additional_image_source( $attachment_id, 'full', $original_size_data, $targeted_mime, $destination );
109
110 if ( is_wp_error( $image ) ) {
111 continue;
112 }
113
114 if ( webp_uploads_should_discard_additional_image_file( $metadata, $image ) ) {
115 wp_delete_file_from_directory( $destination, $original_directory );
116 continue;
117 }
118
119 $metadata['sources'][ $targeted_mime ] = $image;
120 wp_update_attachment_metadata( $attachment_id, $metadata );
121 }
122
123 // If the original MIME type should not be generated/used, override the main image
124 // with the first MIME type image that actually should be generated. In that case,
125 // the original should be backed up.
126 if (
127 ! in_array( $mime_type, $valid_mime_transforms[ $mime_type ], true ) &&
128 isset( $valid_mime_transforms[ $mime_type ][0] ) &&
129 isset( $allowed_mimes[ $mime_type ] ) &&
130 array_key_exists( 'file', $metadata ) &&
131 is_string( $metadata['file'] )
132 ) {
133 $valid_mime_type = $valid_mime_transforms[ $mime_type ][0];
134
135 // Only do the replacement if the attachment file is still set to the original MIME type one,
136 // and if there is a possible replacement source.
137 $file_data = wp_check_filetype( $metadata['file'], array( $allowed_mimes[ $mime_type ] => $mime_type ) );
138 if ( $file_data['type'] === $mime_type && isset( $metadata['sources'][ $valid_mime_type ] ) ) {
139 $saved_data = array(
140 'path' => trailingslashit( $original_directory ) . $metadata['sources'][ $valid_mime_type ]['file'],
141 'width' => $metadata['width'],
142 'height' => $metadata['height'],
143 );
144
145 $original_image = wp_get_original_image_path( $attachment_id );
146
147 // If WordPress already modified the original itself, keep the original and discard WordPress's generated version.
148 if ( ! empty( $metadata['original_image'] ) ) {
149 $uploadpath = wp_get_upload_dir();
150 $attached_file = get_attached_file( $attachment_id );
151 if ( $attached_file ) {
152 wp_delete_file_from_directory( $attached_file, $uploadpath['basedir'] );
153 }
154 }
155
156 // Replace the attached file with the custom MIME type version.
157 if ( $original_image ) {
158 $metadata = _wp_image_meta_replace_original( $saved_data, $original_image, $metadata, $attachment_id );
159 }
160
161 // Unset sources entry for the original MIME type, then save (to avoid inconsistent data
162 // in case of an error after this logic).
163 unset( $metadata['sources'][ $mime_type ] );
164 wp_update_attachment_metadata( $attachment_id, $metadata );
165 }
166 }
167
168 // Make sure we have some sizes to work with, otherwise avoid any work.
169 if ( empty( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ) {
170 return $metadata;
171 }
172
173 $sizes_with_mime_type_support = webp_uploads_get_image_sizes_additional_mime_type_support();
174
175 foreach ( $metadata['sizes'] as $size_name => $properties ) {
176 // Do nothing if this image size is not an array or is not allowed to have additional mime types.
177 if ( ! is_array( $properties ) || empty( $sizes_with_mime_type_support[ $size_name ] ) ) {
178 continue;
179 }
180
181 // Try to find the mime type of the image size.
182 $current_mime = '';
183 if ( isset( $properties['mime-type'] ) ) {
184 $current_mime = $properties['mime-type'];
185 } elseif ( isset( $properties['file'] ) ) {
186 $current_mime = wp_check_filetype( $properties['file'] )['type'];
187 }
188
189 // The mime type can't be determined.
190 if ( empty( $current_mime ) ) {
191 continue;
192 }
193
194 // Ensure a `sources` property exists on the existing size.
195 if ( empty( $properties['sources'] ) || ! is_array( $properties['sources'] ) ) {
196 $properties['sources'] = array();
197 }
198
199 if ( empty( $properties['sources'][ $current_mime ] ) && isset( $properties['file'] ) ) {
200 $properties['sources'][ $current_mime ] = array(
201 'file' => $properties['file'],
202 'filesize' => 0,
203 );
204 // Set the filesize from the current mime image.
205 $file_location = path_join( $original_directory, $properties['file'] );
206 if ( file_exists( $file_location ) ) {
207 $properties['sources'][ $current_mime ]['filesize'] = wp_filesize( $file_location );
208 }
209 $metadata['sizes'][ $size_name ] = $properties;
210 wp_update_attachment_metadata( $attachment_id, $metadata );
211 }
212
213 foreach ( $valid_mime_transforms[ $mime_type ] as $mime ) {
214 // If this property exists no need to create the image again.
215 if ( ! empty( $properties['sources'][ $mime ] ) ) {
216 continue;
217 }
218
219 $source = webp_uploads_generate_image_size( $attachment_id, $size_name, $mime );
220 if ( is_wp_error( $source ) ) {
221 continue;
222 }
223
224 if ( webp_uploads_should_discard_additional_image_file( $properties, $source ) ) {
225 $destination = path_join( $original_directory, $source['file'] );
226 wp_delete_file_from_directory( $destination, $original_directory );
227 continue;
228 }
229
230 $properties['sources'][ $mime ] = $source;
231 $metadata['sizes'][ $size_name ] = $properties;
232 wp_update_attachment_metadata( $attachment_id, $metadata );
233 }
234
235 $metadata['sizes'][ $size_name ] = $properties;
236 }
237
238 return $metadata;
239 }
240 add_filter( 'wp_generate_attachment_metadata', 'webp_uploads_create_sources_property', 10, 2 );
241
242 /**
243 * Filter on `wp_get_missing_image_subsizes` acting as an action for the logic of the plugin
244 * to determine if additional mime types still need to be created.
245 *
246 * This function only exists to work around a missing filter in WordPress core, to call the above
247 * `webp_uploads_create_sources_property()` function correctly.
248 *
249 * @since 1.0.0
250 *
251 * @see wp_get_missing_image_subsizes()
252 *
253 * @phpstan-param array{
254 * width: int,
255 * height: int,
256 * file: string,
257 * sizes: array<string, array{file: string, width: int, height: int, mime-type: string}>,
258 * image_meta: array<string, mixed>,
259 * filesize: int
260 * } $image_meta
261 *
262 * @param array|mixed $missing_sizes Associative array of arrays of image sub-sizes.
263 * @param array<string, mixed> $image_meta The metadata from the image.
264 * @param int $attachment_id The ID of the attachment.
265 * @return array<string, array{ width: int, height: int, crop: bool }> Associative array of arrays of image sub-sizes.
266 */
267 function webp_uploads_wp_get_missing_image_subsizes( $missing_sizes, array $image_meta, int $attachment_id ): array {
268 if ( ! is_array( $missing_sizes ) ) {
269 $missing_sizes = array();
270 }
271
272 // Only setup the trace array if we no longer have more sizes.
273 if ( ! empty( $missing_sizes ) ) {
274 return $missing_sizes;
275 }
276
277 /**
278 * The usage of `debug_backtrace` in this particular case is mainly to ensure the call to
279 * `wp_get_missing_image_subsizes()` originated from `wp_update_image_subsizes()`, since only then the
280 * additional image sizes should be generated. `wp_get_missing_image_subsizes()` could also be called
281 * from other places in which case the custom logic should not trigger. In an ideal world an action
282 * would exist in `wp_update_image_subsizes` that runs any time, but the current
283 * `wp_generate_attachment_metadata` filter is skipped when all core sub-sizes have been generated.
284 * An eventual core implementation will not require this workaround. The limit of 10 is used to allow
285 * for some flexibility. While by default the function would be on index 5, other custom code may
286 * cause the index to be slightly higher.
287 *
288 * @see wp_update_image_subsizes()
289 * @see wp_get_missing_image_subsizes()
290 */
291 // PHPCS ignore reason: Only the way to generate missing image subsize if all core sub-sizes have been generated.
292 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
293 $trace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 10 );
294
295 foreach ( $trace as $element ) {
296 if ( 'wp_update_image_subsizes' === $element['function'] ) {
297 webp_uploads_create_sources_property( $image_meta, $attachment_id );
298 break;
299 }
300 }
301
302 return array();
303 }
304 add_filter( 'wp_get_missing_image_subsizes', 'webp_uploads_wp_get_missing_image_subsizes', 10, 3 );
305
306 /**
307 * Filter the image editor default output format mapping to select the most appropriate
308 * output format depending on desired output formats and supported mime types by the image
309 * editor.
310 *
311 * @since 1.0.0
312 *
313 * @param array<string, string>|mixed $output_format An array of mime type mappings. Maps a source mime type to a new destination mime type. Default empty array.
314 * @param string|null $filename Path to the image.
315 * @param string|null $mime_type The source image mime type.
316 * @return array<string, string> The new output format mapping.
317 */
318 function webp_uploads_filter_image_editor_output_format( $output_format, ?string $filename, ?string $mime_type ): array {
319 if ( ! is_array( $output_format ) ) {
320 $output_format = array();
321 }
322
323 // Use the original mime type if this type is allowed.
324 $valid_mime_transforms = webp_uploads_get_upload_image_mime_transforms();
325 if (
326 ! isset( $valid_mime_transforms[ $mime_type ] ) ||
327 in_array( $mime_type, $valid_mime_transforms[ $mime_type ], true )
328 ) {
329 return $output_format;
330 }
331
332 // Find the first supported mime type by the image editor to use it as the default one.
333 foreach ( $valid_mime_transforms[ $mime_type ] as $target_mime ) {
334 if ( wp_image_editor_supports( array( 'mime_type' => $target_mime ) ) ) {
335 $output_format[ $mime_type ] = $target_mime;
336 break;
337 }
338 }
339
340 return $output_format;
341 }
342 add_filter( 'image_editor_output_format', 'webp_uploads_filter_image_editor_output_format', 10, 3 );
343
344 /**
345 * Hook fired when an attachment is deleted, this hook is in charge of removing any
346 * additional mime types created by this plugin besides the original image. Any source
347 * with the same as the main image would not be removed by this hook due this file would
348 * be removed by WordPress when the attachment is deleted, usually this happens after this
349 * hook is executed.
350 *
351 * @since 1.0.0
352 *
353 * @see wp_delete_attachment()
354 *
355 * @param int $attachment_id The ID of the attachment the sources are going to be deleted.
356 */
357 function webp_uploads_remove_sources_files( int $attachment_id ): void {
358 $file = get_attached_file( $attachment_id );
359
360 if ( empty( $file ) ) {
361 return;
362 }
363
364 $metadata = wp_get_attachment_metadata( $attachment_id );
365 // Make sure $sizes is always defined to allow the removal of original images after the first foreach loop.
366 $sizes = ! isset( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ? array() : $metadata['sizes'];
367
368 $upload_path = wp_get_upload_dir();
369 if ( empty( $upload_path['basedir'] ) ) {
370 return;
371 }
372
373 $intermediate_dir = path_join( $upload_path['basedir'], dirname( $file ) );
374 $basename = wp_basename( $file );
375
376 foreach ( $sizes as $size ) {
377 if ( ! isset( $size['sources'] ) || ! is_array( $size['sources'] ) ) {
378 continue;
379 }
380
381 $original_size_mime = empty( $size['mime-type'] ) ? '' : $size['mime-type'];
382
383 foreach ( $size['sources'] as $mime => $properties ) {
384 /**
385 * When we face the same mime type as the original image, we ignore this file as this file
386 * would be removed when the size is removed by WordPress itself. The meta information as well
387 * would be deleted as soon as the image is removed.
388 *
389 * @see wp_delete_attachment
390 */
391 if ( $original_size_mime === $mime ) {
392 continue;
393 }
394
395 if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
396 continue;
397 }
398
399 $intermediate_file = str_replace( $basename, $properties['file'], $file );
400 if ( ! $intermediate_file ) {
401 continue;
402 }
403
404 $intermediate_file = path_join( $upload_path['basedir'], $intermediate_file );
405 if ( ! file_exists( $intermediate_file ) ) {
406 continue;
407 }
408
409 wp_delete_file_from_directory( $intermediate_file, $intermediate_dir );
410 }
411 }
412
413 if ( ! isset( $metadata['sources'] ) || ! is_array( $metadata['sources'] ) ) {
414 return;
415 }
416
417 $original_mime_from_post = get_post_mime_type( $attachment_id );
418 $original_mime_from_file = wp_check_filetype( $file )['type'];
419
420 // Delete full sizes mime types.
421 foreach ( $metadata['sources'] as $mime => $properties ) {
422 // Don't remove the image with the same mime type as the original image as this would be removed by WordPress.
423 if ( $mime === $original_mime_from_post || $mime === $original_mime_from_file ) {
424 continue;
425 }
426
427 if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
428 continue;
429 }
430
431 $full_size = str_replace( $basename, $properties['file'], $file );
432 if ( ! $full_size ) {
433 continue;
434 }
435
436 $full_size_file = path_join( $upload_path['basedir'], $full_size );
437 if ( ! file_exists( $full_size_file ) ) {
438 continue;
439 }
440 wp_delete_file_from_directory( $full_size_file, $intermediate_dir );
441 }
442
443 $backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
444 $backup_sizes = is_array( $backup_sizes ) ? $backup_sizes : array();
445
446 foreach ( $backup_sizes as $backup_size ) {
447 if ( ! isset( $backup_size['sources'] ) || ! is_array( $backup_size['sources'] ) ) {
448 continue;
449 }
450
451 $original_backup_size_mime = empty( $backup_size['mime-type'] ) ? '' : $backup_size['mime-type'];
452
453 foreach ( $backup_size['sources'] as $backup_mime => $backup_properties ) {
454 /**
455 * When we face the same mime type as the original image, we ignore this file as this file
456 * would be removed when the size is removed by WordPress itself. The meta information as well
457 * would be deleted as soon as the image is removed.
458 *
459 * @see wp_delete_attachment
460 */
461 if ( $original_backup_size_mime === $backup_mime ) {
462 continue;
463 }
464
465 if ( ! is_array( $backup_properties ) || empty( $backup_properties['file'] ) ) {
466 continue;
467 }
468
469 $backup_intermediate_file = str_replace( $basename, $backup_properties['file'], $file );
470 if ( empty( $backup_intermediate_file ) ) {
471 continue;
472 }
473
474 $backup_intermediate_file = path_join( $upload_path['basedir'], $backup_intermediate_file );
475 if ( ! file_exists( $backup_intermediate_file ) ) {
476 continue;
477 }
478
479 wp_delete_file_from_directory( $backup_intermediate_file, $intermediate_dir );
480 }
481 }
482
483 $backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
484 $backup_sources = is_array( $backup_sources ) ? $backup_sources : array();
485
486 // Delete full sizes backup mime types.
487 foreach ( $backup_sources as $backup_mimes ) {
488
489 foreach ( $backup_mimes as $backup_mime_properties ) {
490 if ( ! is_array( $backup_mime_properties ) || empty( $backup_mime_properties['file'] ) ) {
491 continue;
492 }
493
494 $full_size = str_replace( $basename, $backup_mime_properties['file'], $file );
495 if ( empty( $full_size ) ) {
496 continue;
497 }
498
499 $full_size_file = path_join( $upload_path['basedir'], $full_size );
500 if ( ! file_exists( $full_size_file ) ) {
501 continue;
502 }
503 wp_delete_file_from_directory( $full_size_file, $intermediate_dir );
504 }
505 }
506 }
507 add_action( 'delete_attachment', 'webp_uploads_remove_sources_files', 10, 1 );
508
509 /**
510 * Filters `the_content` to update images so that they use the preferred MIME type where possible.
511 *
512 * By default, this is `image/webp`, if the current attachment contains the targeted MIME
513 * type. In the near future this will be filterable.
514 *
515 * Note that most of this function will not be needed for an eventual core implementation as it
516 * would rely on `wp_filter_content_tags()`.
517 *
518 * @since 1.0.0
519 *
520 * @see wp_filter_content_tags()
521 *
522 * @param string $content The content of the current post.
523 * @return string The content with the updated references to the images.
524 */
525 function webp_uploads_update_image_references( string $content ): string {
526 // Bail early if request is not for the frontend.
527 if ( ! webp_uploads_in_frontend_body() ) {
528 return $content;
529 }
530
531 // This content does not have any tag on it, move forward.
532 if ( ! preg_match_all( '/<(img)\s[^>]+>/', $content, $img_tags, PREG_SET_ORDER ) ) {
533 return $content;
534 }
535
536 $images = array();
537 foreach ( $img_tags as list( $img ) ) {
538 // Find the ID of each image by the class.
539 if ( ! preg_match( '/wp-image-([\d]+)/i', $img, $class_name ) ) {
540 continue;
541 }
542
543 if ( empty( $class_name ) ) {
544 continue;
545 }
546
547 // Make sure we use the last item on the list of matches.
548 $attachment_id = (int) $class_name[1];
549
550 if ( ! $attachment_id ) {
551 continue;
552 }
553
554 $images[ $img ] = $attachment_id;
555 }
556
557 $attachment_ids = array_unique( array_filter( array_values( $images ) ) );
558 if ( count( $attachment_ids ) > 1 ) {
559 /**
560 * Warm the object cache with post and meta information for all found
561 * images to avoid making individual database calls.
562 */
563 _prime_post_caches( $attachment_ids, false, true );
564 }
565
566 foreach ( $images as $img => $attachment_id ) {
567 $content = str_replace( $img, webp_uploads_img_tag_update_mime_type( $img, 'the_content', $attachment_id ), $content );
568 }
569
570 return $content;
571 }
572 add_filter( 'the_content', 'webp_uploads_update_image_references', 13 ); // Run after wp_filter_content_tags.
573
574 /**
575 * Finds all the urls with *.jpg and *.jpeg extension and updates with *.webp version for the provided image
576 * for the specified image sizes, the *.webp references are stored inside of each size.
577 *
578 * @since 1.0.0
579 *
580 * @param string $original_image An <img> tag where the urls would be updated.
581 * @param string $context The context where this is function is being used.
582 * @param int $attachment_id The ID of the attachment being modified.
583 * @return string The updated img tag.
584 */
585 function webp_uploads_img_tag_update_mime_type( string $original_image, string $context, int $attachment_id ): string {
586 $image = $original_image;
587 $metadata = wp_get_attachment_metadata( $attachment_id );
588
589 if ( empty( $metadata['file'] ) ) {
590 return $image;
591 }
592
593 $original_mime = get_post_mime_type( $attachment_id );
594 $target_mimes = webp_uploads_get_content_image_mimes( $attachment_id, $context );
595
596 foreach ( $target_mimes as $target_mime ) {
597 if ( $target_mime === $original_mime ) {
598 continue;
599 }
600
601 if ( ! isset( $metadata['sources'][ $target_mime ]['file'] ) ) {
602 continue;
603 }
604
605 /**
606 * Filter to replace additional image source file, by locating the original
607 * mime types of the file and return correct file path in the end.
608 *
609 * Altering the $image tag through this filter effectively short-circuits the default replacement logic using the preferred MIME type.
610 *
611 * @since 1.1.0
612 *
613 * @param string $image An <img> tag where the urls would be updated.
614 * @param int $attachment_id The ID of the attachment being modified.
615 * @param string $size The size name that would be used to create this image, out of the registered subsizes.
616 * @param string $target_mime The target mime in which the image should be created.
617 * @param string $context The context where this is function is being used.
618 */
619 $filtered_image = (string) apply_filters( 'webp_uploads_pre_replace_additional_image_source', $image, $attachment_id, 'full', $target_mime, $context );
620
621 // If filtered image is same as the image, run our own replacement logic, otherwise rely on the filtered image.
622 if ( $filtered_image === $image ) {
623 $basename = wp_basename( $metadata['file'] );
624 $image = str_replace(
625 $basename,
626 $metadata['sources'][ $target_mime ]['file'],
627 $image
628 );
629 } else {
630 $image = $filtered_image;
631 }
632 }
633
634 if ( isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) {
635 // Replace sub sizes for the image if present.
636 foreach ( $metadata['sizes'] as $size => $size_data ) {
637
638 if ( empty( $size_data['file'] ) ) {
639 continue;
640 }
641
642 foreach ( $target_mimes as $target_mime ) {
643 if ( $target_mime === $original_mime ) {
644 continue;
645 }
646
647 if ( ! isset( $size_data['sources'][ $target_mime ]['file'] ) ) {
648 continue;
649 }
650
651 if ( $size_data['file'] === $size_data['sources'][ $target_mime ]['file'] ) {
652 continue;
653 }
654
655 /** This filter is documented in plugins/webp-uploads/load.php */
656 $filtered_image = (string) apply_filters( 'webp_uploads_pre_replace_additional_image_source', $image, $attachment_id, $size, $target_mime, $context );
657
658 // If filtered image is same as the image, run our own replacement logic, otherwise rely on the filtered image.
659 if ( $filtered_image === $image ) {
660 $image = str_replace(
661 $size_data['file'],
662 $size_data['sources'][ $target_mime ]['file'],
663 $image
664 );
665 } else {
666 $image = $filtered_image;
667 }
668 }
669 }
670 }
671
672 return $image;
673 }
674
675 /**
676 * Updates the references of the featured image to the a new image format if available, in the same way it
677 * occurs in the_content of a post.
678 *
679 * @since 1.0.0
680 *
681 * @param string $html The current HTML markup of the featured image.
682 * @param int $post_id The current post ID where the featured image is requested.
683 * @param int $attachment_id The ID of the attachment image.
684 * @return string The updated HTML markup.
685 */
686 function webp_uploads_update_featured_image( string $html, int $post_id, int $attachment_id ): string {
687 return webp_uploads_img_tag_update_mime_type( $html, 'post_thumbnail_html', $attachment_id );
688 }
689 add_filter( 'post_thumbnail_html', 'webp_uploads_update_featured_image', 10, 3 );
690
691 /**
692 * Returns an array of image size names that have secondary mime type output enabled. Core sizes and
693 * core theme sizes are enabled by default.
694 *
695 * Developers can control the generation of additional mime images for all sizes using the
696 * webp_uploads_image_sizes_with_additional_mime_type_support filter.
697 *
698 * @since 1.0.0
699 *
700 * @return array<string, bool> An array of image sizes that can have additional mime types.
701 */
702 function webp_uploads_get_image_sizes_additional_mime_type_support(): array {
703 $additional_sizes = wp_get_additional_image_sizes();
704 $allowed_sizes = array(
705 'thumbnail' => true,
706 'medium' => true,
707 'medium_large' => true,
708 'large' => true,
709 'post-thumbnail' => true,
710 );
711
712 foreach ( $additional_sizes as $size => $size_details ) {
713 $allowed_sizes[ $size ] = ! empty( $size_details['provide_additional_mime_types'] );
714 }
715
716 /**
717 * Filters whether additional mime types are allowed for image sizes.
718 *
719 * @since 1.0.0
720 *
721 * @param array<string, bool> $allowed_sizes A map of image size names and whether they are allowed to have additional mime types.
722 */
723 $allowed_sizes = (array) apply_filters( 'webp_uploads_image_sizes_with_additional_mime_type_support', $allowed_sizes );
724
725 return $allowed_sizes;
726 }
727
728 /**
729 * Updates the quality of WebP image sizes generated by WordPress to 82.
730 *
731 * @since 1.0.0
732 *
733 * @param int $quality Quality level between 1 (low) and 100 (high).
734 * @param string $mime_type Image mime type.
735 * @return int The updated quality for mime types.
736 */
737 function webp_uploads_modify_webp_quality( int $quality, string $mime_type ): int {
738 // For WebP images, always return 82 (other MIME types were already using 82 by default anyway).
739 if ( 'image/webp' === $mime_type ) {
740 return 82;
741 }
742
743 // Return default quality for non-WebP images in WP.
744 return $quality;
745 }
746 add_filter( 'wp_editor_set_quality', 'webp_uploads_modify_webp_quality', 10, 2 );
747
748 /**
749 * Displays the HTML generator tag for the Modern Image Formats plugin.
750 *
751 * See {@see 'wp_head'}.
752 *
753 * @since 1.0.0
754 */
755 function webp_uploads_render_generator(): void {
756 // Use the plugin slug as it is immutable.
757 echo '<meta name="generator" content="webp-uploads ' . esc_attr( WEBP_UPLOADS_VERSION ) . '">' . "\n";
758 }
759 add_action( 'wp_head', 'webp_uploads_render_generator' );
760
761 if ( webp_uploads_is_picture_element_enabled() ) {
762 add_filter( 'wp_content_img_tag', 'webp_uploads_wrap_image_in_picture', 10, 3 );
763 }
764