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