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