KEMBAR78
media.php in tags/6.7.1/src/wp-includes – WordPress Trac

source: tags/6.7.1/src/wp-includes/media.php

Last change on this file was 59435, checked in by desrosj, 11 months ago

Media: Avoid images with sizes=auto to be displayed downsized in supporting browsers.

Based on the user agent stylesheet rules outlined in https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size, images that have sizes=auto while applying width: auto or width: fit-content would be constrained to only 300px width.

This changeset overrides said user agent stylesheet rule with a much larger constraint, to avoid the problem.

Additionally, it introduces a filter wp_img_tag_add_auto_sizes which can be used to opt out of the functionality, as an additional measure.

Reviewed by desrosj, joemcgill.
Merges [59415] to the 6.7 branch.

Props joemcgill, flixos90, dooperweb, SirLouen, azaozz, mukesh27, apermo.
Fixes #62413.
See #61847, #62345.

  • Property svn:eol-style set to native
File size: 213.3 KB
Line 
1<?php
2/**
3 * WordPress API for media display.
4 *
5 * @package WordPress
6 * @subpackage Media
7 */
8
9/**
10 * Retrieves additional image sizes.
11 *
12 * @since 4.7.0
13 *
14 * @global array $_wp_additional_image_sizes
15 *
16 * @return array Additional images size data.
17 */
18function wp_get_additional_image_sizes() {
19        global $_wp_additional_image_sizes;
20
21        if ( ! $_wp_additional_image_sizes ) {
22                $_wp_additional_image_sizes = array();
23        }
24
25        return $_wp_additional_image_sizes;
26}
27
28/**
29 * Scales down the default size of an image.
30 *
31 * This is so that the image is a better fit for the editor and theme.
32 *
33 * The `$size` parameter accepts either an array or a string. The supported string
34 * values are 'thumb' or 'thumbnail' for the given thumbnail size or defaults at
35 * 128 width and 96 height in pixels. Also supported for the string value is
36 * 'medium', 'medium_large' and 'full'. The 'full' isn't actually supported, but any value other
37 * than the supported will result in the content_width size or 500 if that is
38 * not set.
39 *
40 * Finally, there is a filter named {@see 'editor_max_image_size'}, that will be
41 * called on the calculated array for width and height, respectively.
42 *
43 * @since 2.5.0
44 *
45 * @global int $content_width
46 *
47 * @param int          $width   Width of the image in pixels.
48 * @param int          $height  Height of the image in pixels.
49 * @param string|int[] $size    Optional. Image size. Accepts any registered image size name, or an array
50 *                              of width and height values in pixels (in that order). Default 'medium'.
51 * @param string       $context Optional. Could be 'display' (like in a theme) or 'edit'
52 *                              (like inserting into an editor). Default null.
53 * @return int[] {
54 *     An array of width and height values.
55 *
56 *     @type int $0 The maximum width in pixels.
57 *     @type int $1 The maximum height in pixels.
58 * }
59 */
60function image_constrain_size_for_editor( $width, $height, $size = 'medium', $context = null ) {
61        global $content_width;
62
63        $_wp_additional_image_sizes = wp_get_additional_image_sizes();
64
65        if ( ! $context ) {
66                $context = is_admin() ? 'edit' : 'display';
67        }
68
69        if ( is_array( $size ) ) {
70                $max_width  = $size[0];
71                $max_height = $size[1];
72        } elseif ( 'thumb' === $size || 'thumbnail' === $size ) {
73                $max_width  = (int) get_option( 'thumbnail_size_w' );
74                $max_height = (int) get_option( 'thumbnail_size_h' );
75                // Last chance thumbnail size defaults.
76                if ( ! $max_width && ! $max_height ) {
77                        $max_width  = 128;
78                        $max_height = 96;
79                }
80        } elseif ( 'medium' === $size ) {
81                $max_width  = (int) get_option( 'medium_size_w' );
82                $max_height = (int) get_option( 'medium_size_h' );
83
84        } elseif ( 'medium_large' === $size ) {
85                $max_width  = (int) get_option( 'medium_large_size_w' );
86                $max_height = (int) get_option( 'medium_large_size_h' );
87
88                if ( (int) $content_width > 0 ) {
89                        $max_width = min( (int) $content_width, $max_width );
90                }
91        } elseif ( 'large' === $size ) {
92                /*
93                 * We're inserting a large size image into the editor. If it's a really
94                 * big image we'll scale it down to fit reasonably within the editor
95                 * itself, and within the theme's content width if it's known. The user
96                 * can resize it in the editor if they wish.
97                 */
98                $max_width  = (int) get_option( 'large_size_w' );
99                $max_height = (int) get_option( 'large_size_h' );
100
101                if ( (int) $content_width > 0 ) {
102                        $max_width = min( (int) $content_width, $max_width );
103                }
104        } elseif ( ! empty( $_wp_additional_image_sizes ) && in_array( $size, array_keys( $_wp_additional_image_sizes ), true ) ) {
105                $max_width  = (int) $_wp_additional_image_sizes[ $size ]['width'];
106                $max_height = (int) $_wp_additional_image_sizes[ $size ]['height'];
107                // Only in admin. Assume that theme authors know what they're doing.
108                if ( (int) $content_width > 0 && 'edit' === $context ) {
109                        $max_width = min( (int) $content_width, $max_width );
110                }
111        } else { // $size === 'full' has no constraint.
112                $max_width  = $width;
113                $max_height = $height;
114        }
115
116        /**
117         * Filters the maximum image size dimensions for the editor.
118         *
119         * @since 2.5.0
120         *
121         * @param int[]        $max_image_size {
122         *     An array of width and height values.
123         *
124         *     @type int $0 The maximum width in pixels.
125         *     @type int $1 The maximum height in pixels.
126         * }
127         * @param string|int[] $size     Requested image size. Can be any registered image size name, or
128         *                               an array of width and height values in pixels (in that order).
129         * @param string       $context  The context the image is being resized for.
130         *                               Possible values are 'display' (like in a theme)
131         *                               or 'edit' (like inserting into an editor).
132         */
133        list( $max_width, $max_height ) = apply_filters( 'editor_max_image_size', array( $max_width, $max_height ), $size, $context );
134
135        return wp_constrain_dimensions( $width, $height, $max_width, $max_height );
136}
137
138/**
139 * Retrieves width and height attributes using given width and height values.
140 *
141 * Both attributes are required in the sense that both parameters must have a
142 * value, but are optional in that if you set them to false or null, then they
143 * will not be added to the returned string.
144 *
145 * You can set the value using a string, but it will only take numeric values.
146 * If you wish to put 'px' after the numbers, then it will be stripped out of
147 * the return.
148 *
149 * @since 2.5.0
150 *
151 * @param int|string $width  Image width in pixels.
152 * @param int|string $height Image height in pixels.
153 * @return string HTML attributes for width and, or height.
154 */
155function image_hwstring( $width, $height ) {
156        $out = '';
157        if ( $width ) {
158                $out .= 'width="' . (int) $width . '" ';
159        }
160        if ( $height ) {
161                $out .= 'height="' . (int) $height . '" ';
162        }
163        return $out;
164}
165
166/**
167 * Scales an image to fit a particular size (such as 'thumb' or 'medium').
168 *
169 * The URL might be the original image, or it might be a resized version. This
170 * function won't create a new resized copy, it will just return an already
171 * resized one if it exists.
172 *
173 * A plugin may use the {@see 'image_downsize'} filter to hook into and offer image
174 * resizing services for images. The hook must return an array with the same
175 * elements that are normally returned from the function.
176 *
177 * @since 2.5.0
178 *
179 * @param int          $id   Attachment ID for image.
180 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
181 *                           of width and height values in pixels (in that order). Default 'medium'.
182 * @return array|false {
183 *     Array of image data, or boolean false if no image is available.
184 *
185 *     @type string $0 Image source URL.
186 *     @type int    $1 Image width in pixels.
187 *     @type int    $2 Image height in pixels.
188 *     @type bool   $3 Whether the image is a resized image.
189 * }
190 */
191function image_downsize( $id, $size = 'medium' ) {
192        $is_image = wp_attachment_is_image( $id );
193
194        /**
195         * Filters whether to preempt the output of image_downsize().
196         *
197         * Returning a truthy value from the filter will effectively short-circuit
198         * down-sizing the image, returning that value instead.
199         *
200         * @since 2.5.0
201         *
202         * @param bool|array   $downsize Whether to short-circuit the image downsize.
203         * @param int          $id       Attachment ID for image.
204         * @param string|int[] $size     Requested image size. Can be any registered image size name, or
205         *                               an array of width and height values in pixels (in that order).
206         */
207        $out = apply_filters( 'image_downsize', false, $id, $size );
208
209        if ( $out ) {
210                return $out;
211        }
212
213        $img_url          = wp_get_attachment_url( $id );
214        $meta             = wp_get_attachment_metadata( $id );
215        $width            = 0;
216        $height           = 0;
217        $is_intermediate  = false;
218        $img_url_basename = wp_basename( $img_url );
219
220        /*
221         * If the file isn't an image, attempt to replace its URL with a rendered image from its meta.
222         * Otherwise, a non-image type could be returned.
223         */
224        if ( ! $is_image ) {
225                if ( ! empty( $meta['sizes']['full'] ) ) {
226                        $img_url          = str_replace( $img_url_basename, $meta['sizes']['full']['file'], $img_url );
227                        $img_url_basename = $meta['sizes']['full']['file'];
228                        $width            = $meta['sizes']['full']['width'];
229                        $height           = $meta['sizes']['full']['height'];
230                } else {
231                        return false;
232                }
233        }
234
235        // Try for a new style intermediate size.
236        $intermediate = image_get_intermediate_size( $id, $size );
237
238        if ( $intermediate ) {
239                $img_url         = str_replace( $img_url_basename, $intermediate['file'], $img_url );
240                $width           = $intermediate['width'];
241                $height          = $intermediate['height'];
242                $is_intermediate = true;
243        } elseif ( 'thumbnail' === $size && ! empty( $meta['thumb'] ) && is_string( $meta['thumb'] ) ) {
244                // Fall back to the old thumbnail.
245                $imagefile = get_attached_file( $id );
246                $thumbfile = str_replace( wp_basename( $imagefile ), wp_basename( $meta['thumb'] ), $imagefile );
247
248                if ( file_exists( $thumbfile ) ) {
249                        $info = wp_getimagesize( $thumbfile );
250
251                        if ( $info ) {
252                                $img_url         = str_replace( $img_url_basename, wp_basename( $thumbfile ), $img_url );
253                                $width           = $info[0];
254                                $height          = $info[1];
255                                $is_intermediate = true;
256                        }
257                }
258        }
259
260        if ( ! $width && ! $height && isset( $meta['width'], $meta['height'] ) ) {
261                // Any other type: use the real image.
262                $width  = $meta['width'];
263                $height = $meta['height'];
264        }
265
266        if ( $img_url ) {
267                // We have the actual image size, but might need to further constrain it if content_width is narrower.
268                list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
269
270                return array( $img_url, $width, $height, $is_intermediate );
271        }
272
273        return false;
274}
275
276/**
277 * Registers a new image size.
278 *
279 * @since 2.9.0
280 *
281 * @global array $_wp_additional_image_sizes Associative array of additional image sizes.
282 *
283 * @param string     $name   Image size identifier.
284 * @param int        $width  Optional. Image width in pixels. Default 0.
285 * @param int        $height Optional. Image height in pixels. Default 0.
286 * @param bool|array $crop   {
287 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
288 *     If true, image will be cropped to the specified dimensions using center positions.
289 *     If an array, the image will be cropped using the array to specify the crop location:
290 *
291 *     @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
292 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
293 * }
294 */
295function add_image_size( $name, $width = 0, $height = 0, $crop = false ) {
296        global $_wp_additional_image_sizes;
297
298        $_wp_additional_image_sizes[ $name ] = array(
299                'width'  => absint( $width ),
300                'height' => absint( $height ),
301                'crop'   => $crop,
302        );
303}
304
305/**
306 * Checks if an image size exists.
307 *
308 * @since 3.9.0
309 *
310 * @param string $name The image size to check.
311 * @return bool True if the image size exists, false if not.
312 */
313function has_image_size( $name ) {
314        $sizes = wp_get_additional_image_sizes();
315        return isset( $sizes[ $name ] );
316}
317
318/**
319 * Removes a new image size.
320 *
321 * @since 3.9.0
322 *
323 * @global array $_wp_additional_image_sizes
324 *
325 * @param string $name The image size to remove.
326 * @return bool True if the image size was successfully removed, false on failure.
327 */
328function remove_image_size( $name ) {
329        global $_wp_additional_image_sizes;
330
331        if ( isset( $_wp_additional_image_sizes[ $name ] ) ) {
332                unset( $_wp_additional_image_sizes[ $name ] );
333                return true;
334        }
335
336        return false;
337}
338
339/**
340 * Registers an image size for the post thumbnail.
341 *
342 * @since 2.9.0
343 *
344 * @see add_image_size() for details on cropping behavior.
345 *
346 * @param int        $width  Image width in pixels.
347 * @param int        $height Image height in pixels.
348 * @param bool|array $crop   {
349 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
350 *     If true, image will be cropped to the specified dimensions using center positions.
351 *     If an array, the image will be cropped using the array to specify the crop location:
352 *
353 *     @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
354 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
355 * }
356 */
357function set_post_thumbnail_size( $width = 0, $height = 0, $crop = false ) {
358        add_image_size( 'post-thumbnail', $width, $height, $crop );
359}
360
361/**
362 * Gets an img tag for an image attachment, scaling it down if requested.
363 *
364 * The {@see 'get_image_tag_class'} filter allows for changing the class name for the
365 * image without having to use regular expressions on the HTML content. The
366 * parameters are: what WordPress will use for the class, the Attachment ID,
367 * image align value, and the size the image should be.
368 *
369 * The second filter, {@see 'get_image_tag'}, has the HTML content, which can then be
370 * further manipulated by a plugin to change all attribute values and even HTML
371 * content.
372 *
373 * @since 2.5.0
374 *
375 * @param int          $id    Attachment ID.
376 * @param string       $alt   Image description for the alt attribute.
377 * @param string       $title Image description for the title attribute.
378 * @param string       $align Part of the class name for aligning the image.
379 * @param string|int[] $size  Optional. Image size. Accepts any registered image size name, or an array of
380 *                            width and height values in pixels (in that order). Default 'medium'.
381 * @return string HTML IMG element for given image attachment.
382 */
383function get_image_tag( $id, $alt, $title, $align, $size = 'medium' ) {
384
385        list( $img_src, $width, $height ) = image_downsize( $id, $size );
386        $hwstring                         = image_hwstring( $width, $height );
387
388        $title = $title ? 'title="' . esc_attr( $title ) . '" ' : '';
389
390        $size_class = is_array( $size ) ? implode( 'x', $size ) : $size;
391        $class      = 'align' . esc_attr( $align ) . ' size-' . esc_attr( $size_class ) . ' wp-image-' . $id;
392
393        /**
394         * Filters the value of the attachment's image tag class attribute.
395         *
396         * @since 2.6.0
397         *
398         * @param string       $class CSS class name or space-separated list of classes.
399         * @param int          $id    Attachment ID.
400         * @param string       $align Part of the class name for aligning the image.
401         * @param string|int[] $size  Requested image size. Can be any registered image size name, or
402         *                            an array of width and height values in pixels (in that order).
403         */
404        $class = apply_filters( 'get_image_tag_class', $class, $id, $align, $size );
405
406        $html = '<img src="' . esc_url( $img_src ) . '" alt="' . esc_attr( $alt ) . '" ' . $title . $hwstring . 'class="' . $class . '" />';
407
408        /**
409         * Filters the HTML content for the image tag.
410         *
411         * @since 2.6.0
412         *
413         * @param string       $html  HTML content for the image.
414         * @param int          $id    Attachment ID.
415         * @param string       $alt   Image description for the alt attribute.
416         * @param string       $title Image description for the title attribute.
417         * @param string       $align Part of the class name for aligning the image.
418         * @param string|int[] $size  Requested image size. Can be any registered image size name, or
419         *                            an array of width and height values in pixels (in that order).
420         */
421        return apply_filters( 'get_image_tag', $html, $id, $alt, $title, $align, $size );
422}
423
424/**
425 * Calculates the new dimensions for a down-sampled image.
426 *
427 * If either width or height are empty, no constraint is applied on
428 * that dimension.
429 *
430 * @since 2.5.0
431 *
432 * @param int $current_width  Current width of the image.
433 * @param int $current_height Current height of the image.
434 * @param int $max_width      Optional. Max width in pixels to constrain to. Default 0.
435 * @param int $max_height     Optional. Max height in pixels to constrain to. Default 0.
436 * @return int[] {
437 *     An array of width and height values.
438 *
439 *     @type int $0 The width in pixels.
440 *     @type int $1 The height in pixels.
441 * }
442 */
443function wp_constrain_dimensions( $current_width, $current_height, $max_width = 0, $max_height = 0 ) {
444        if ( ! $max_width && ! $max_height ) {
445                return array( $current_width, $current_height );
446        }
447
448        $width_ratio  = 1.0;
449        $height_ratio = 1.0;
450        $did_width    = false;
451        $did_height   = false;
452
453        if ( $max_width > 0 && $current_width > 0 && $current_width > $max_width ) {
454                $width_ratio = $max_width / $current_width;
455                $did_width   = true;
456        }
457
458        if ( $max_height > 0 && $current_height > 0 && $current_height > $max_height ) {
459                $height_ratio = $max_height / $current_height;
460                $did_height   = true;
461        }
462
463        // Calculate the larger/smaller ratios.
464        $smaller_ratio = min( $width_ratio, $height_ratio );
465        $larger_ratio  = max( $width_ratio, $height_ratio );
466
467        if ( (int) round( $current_width * $larger_ratio ) > $max_width || (int) round( $current_height * $larger_ratio ) > $max_height ) {
468                // The larger ratio is too big. It would result in an overflow.
469                $ratio = $smaller_ratio;
470        } else {
471                // The larger ratio fits, and is likely to be a more "snug" fit.
472                $ratio = $larger_ratio;
473        }
474
475        // Very small dimensions may result in 0, 1 should be the minimum.
476        $w = max( 1, (int) round( $current_width * $ratio ) );
477        $h = max( 1, (int) round( $current_height * $ratio ) );
478
479        /*
480         * Sometimes, due to rounding, we'll end up with a result like this:
481         * 465x700 in a 177x177 box is 117x176... a pixel short.
482         * We also have issues with recursive calls resulting in an ever-changing result.
483         * Constraining to the result of a constraint should yield the original result.
484         * Thus we look for dimensions that are one pixel shy of the max value and bump them up.
485         */
486
487        // Note: $did_width means it is possible $smaller_ratio == $width_ratio.
488        if ( $did_width && $w === $max_width - 1 ) {
489                $w = $max_width; // Round it up.
490        }
491
492        // Note: $did_height means it is possible $smaller_ratio == $height_ratio.
493        if ( $did_height && $h === $max_height - 1 ) {
494                $h = $max_height; // Round it up.
495        }
496
497        /**
498         * Filters dimensions to constrain down-sampled images to.
499         *
500         * @since 4.1.0
501         *
502         * @param int[] $dimensions     {
503         *     An array of width and height values.
504         *
505         *     @type int $0 The width in pixels.
506         *     @type int $1 The height in pixels.
507         * }
508         * @param int   $current_width  The current width of the image.
509         * @param int   $current_height The current height of the image.
510         * @param int   $max_width      The maximum width permitted.
511         * @param int   $max_height     The maximum height permitted.
512         */
513        return apply_filters( 'wp_constrain_dimensions', array( $w, $h ), $current_width, $current_height, $max_width, $max_height );
514}
515
516/**
517 * Retrieves calculated resize dimensions for use in WP_Image_Editor.
518 *
519 * Calculates dimensions and coordinates for a resized image that fits
520 * within a specified width and height.
521 *
522 * @since 2.5.0
523 *
524 * @param int        $orig_w Original width in pixels.
525 * @param int        $orig_h Original height in pixels.
526 * @param int        $dest_w New width in pixels.
527 * @param int        $dest_h New height in pixels.
528 * @param bool|array $crop   {
529 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
530 *     If true, image will be cropped to the specified dimensions using center positions.
531 *     If an array, the image will be cropped using the array to specify the crop location:
532 *
533 *     @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
534 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
535 * }
536 * @return array|false Returned array matches parameters for `imagecopyresampled()`. False on failure.
537 */
538function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) {
539
540        if ( $orig_w <= 0 || $orig_h <= 0 ) {
541                return false;
542        }
543        // At least one of $dest_w or $dest_h must be specific.
544        if ( $dest_w <= 0 && $dest_h <= 0 ) {
545                return false;
546        }
547
548        /**
549         * Filters whether to preempt calculating the image resize dimensions.
550         *
551         * Returning a non-null value from the filter will effectively short-circuit
552         * image_resize_dimensions(), returning that value instead.
553         *
554         * @since 3.4.0
555         *
556         * @param null|mixed $null   Whether to preempt output of the resize dimensions.
557         * @param int        $orig_w Original width in pixels.
558         * @param int        $orig_h Original height in pixels.
559         * @param int        $dest_w New width in pixels.
560         * @param int        $dest_h New height in pixels.
561         * @param bool|array $crop   Whether to crop image to specified width and height or resize.
562         *                           An array can specify positioning of the crop area. Default false.
563         */
564        $output = apply_filters( 'image_resize_dimensions', null, $orig_w, $orig_h, $dest_w, $dest_h, $crop );
565
566        if ( null !== $output ) {
567                return $output;
568        }
569
570        // Stop if the destination size is larger than the original image dimensions.
571        if ( empty( $dest_h ) ) {
572                if ( $orig_w < $dest_w ) {
573                        return false;
574                }
575        } elseif ( empty( $dest_w ) ) {
576                if ( $orig_h < $dest_h ) {
577                        return false;
578                }
579        } else {
580                if ( $orig_w < $dest_w && $orig_h < $dest_h ) {
581                        return false;
582                }
583        }
584
585        if ( $crop ) {
586                /*
587                 * Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h.
588                 * Note that the requested crop dimensions are used as a maximum bounding box for the original image.
589                 * If the original image's width or height is less than the requested width or height
590                 * only the greater one will be cropped.
591                 * For example when the original image is 600x300, and the requested crop dimensions are 400x400,
592                 * the resulting image will be 400x300.
593                 */
594                $aspect_ratio = $orig_w / $orig_h;
595                $new_w        = min( $dest_w, $orig_w );
596                $new_h        = min( $dest_h, $orig_h );
597
598                if ( ! $new_w ) {
599                        $new_w = (int) round( $new_h * $aspect_ratio );
600                }
601
602                if ( ! $new_h ) {
603                        $new_h = (int) round( $new_w / $aspect_ratio );
604                }
605
606                $size_ratio = max( $new_w / $orig_w, $new_h / $orig_h );
607
608                $crop_w = round( $new_w / $size_ratio );
609                $crop_h = round( $new_h / $size_ratio );
610
611                if ( ! is_array( $crop ) || count( $crop ) !== 2 ) {
612                        $crop = array( 'center', 'center' );
613                }
614
615                list( $x, $y ) = $crop;
616
617                if ( 'left' === $x ) {
618                        $s_x = 0;
619                } elseif ( 'right' === $x ) {
620                        $s_x = $orig_w - $crop_w;
621                } else {
622                        $s_x = floor( ( $orig_w - $crop_w ) / 2 );
623                }
624
625                if ( 'top' === $y ) {
626                        $s_y = 0;
627                } elseif ( 'bottom' === $y ) {
628                        $s_y = $orig_h - $crop_h;
629                } else {
630                        $s_y = floor( ( $orig_h - $crop_h ) / 2 );
631                }
632        } else {
633                // Resize using $dest_w x $dest_h as a maximum bounding box.
634                $crop_w = $orig_w;
635                $crop_h = $orig_h;
636
637                $s_x = 0;
638                $s_y = 0;
639
640                list( $new_w, $new_h ) = wp_constrain_dimensions( $orig_w, $orig_h, $dest_w, $dest_h );
641        }
642
643        if ( wp_fuzzy_number_match( $new_w, $orig_w ) && wp_fuzzy_number_match( $new_h, $orig_h ) ) {
644                // The new size has virtually the same dimensions as the original image.
645
646                /**
647                 * Filters whether to proceed with making an image sub-size with identical dimensions
648                 * with the original/source image. Differences of 1px may be due to rounding and are ignored.
649                 *
650                 * @since 5.3.0
651                 *
652                 * @param bool $proceed The filtered value.
653                 * @param int  $orig_w  Original image width.
654                 * @param int  $orig_h  Original image height.
655                 */
656                $proceed = (bool) apply_filters( 'wp_image_resize_identical_dimensions', false, $orig_w, $orig_h );
657
658                if ( ! $proceed ) {
659                        return false;
660                }
661        }
662
663        /*
664         * The return array matches the parameters to imagecopyresampled().
665         * int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h
666         */
667        return array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h );
668}
669
670/**
671 * Resizes an image to make a thumbnail or intermediate size.
672 *
673 * The returned array has the file size, the image width, and image height. The
674 * {@see 'image_make_intermediate_size'} filter can be used to hook in and change the
675 * values of the returned array. The only parameter is the resized file path.
676 *
677 * @since 2.5.0
678 *
679 * @param string     $file   File path.
680 * @param int        $width  Image width.
681 * @param int        $height Image height.
682 * @param bool|array $crop   {
683 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
684 *     If true, image will be cropped to the specified dimensions using center positions.
685 *     If an array, the image will be cropped using the array to specify the crop location:
686 *
687 *     @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
688 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
689 * }
690 * @return array|false Metadata array on success. False if no image was created.
691 */
692function image_make_intermediate_size( $file, $width, $height, $crop = false ) {
693        if ( $width || $height ) {
694                $editor = wp_get_image_editor( $file );
695
696                if ( is_wp_error( $editor ) || is_wp_error( $editor->resize( $width, $height, $crop ) ) ) {
697                        return false;
698                }
699
700                $resized_file = $editor->save();
701
702                if ( ! is_wp_error( $resized_file ) && $resized_file ) {
703                        unset( $resized_file['path'] );
704                        return $resized_file;
705                }
706        }
707        return false;
708}
709
710/**
711 * Helper function to test if aspect ratios for two images match.
712 *
713 * @since 4.6.0
714 *
715 * @param int $source_width  Width of the first image in pixels.
716 * @param int $source_height Height of the first image in pixels.
717 * @param int $target_width  Width of the second image in pixels.
718 * @param int $target_height Height of the second image in pixels.
719 * @return bool True if aspect ratios match within 1px. False if not.
720 */
721function wp_image_matches_ratio( $source_width, $source_height, $target_width, $target_height ) {
722        /*
723         * To test for varying crops, we constrain the dimensions of the larger image
724         * to the dimensions of the smaller image and see if they match.
725         */
726        if ( $source_width > $target_width ) {
727                $constrained_size = wp_constrain_dimensions( $source_width, $source_height, $target_width );
728                $expected_size    = array( $target_width, $target_height );
729        } else {
730                $constrained_size = wp_constrain_dimensions( $target_width, $target_height, $source_width );
731                $expected_size    = array( $source_width, $source_height );
732        }
733
734        // If the image dimensions are within 1px of the expected size, we consider it a match.
735        $matched = ( wp_fuzzy_number_match( $constrained_size[0], $expected_size[0] ) && wp_fuzzy_number_match( $constrained_size[1], $expected_size[1] ) );
736
737        return $matched;
738}
739
740/**
741 * Retrieves the image's intermediate size (resized) path, width, and height.
742 *
743 * The $size parameter can be an array with the width and height respectively.
744 * If the size matches the 'sizes' metadata array for width and height, then it
745 * will be used. If there is no direct match, then the nearest image size larger
746 * than the specified size will be used. If nothing is found, then the function
747 * will break out and return false.
748 *
749 * The metadata 'sizes' is used for compatible sizes that can be used for the
750 * parameter $size value.
751 *
752 * The url path will be given, when the $size parameter is a string.
753 *
754 * If you are passing an array for the $size, you should consider using
755 * add_image_size() so that a cropped version is generated. It's much more
756 * efficient than having to find the closest-sized image and then having the
757 * browser scale down the image.
758 *
759 * @since 2.5.0
760 *
761 * @param int          $post_id Attachment ID.
762 * @param string|int[] $size    Optional. Image size. Accepts any registered image size name, or an array
763 *                              of width and height values in pixels (in that order). Default 'thumbnail'.
764 * @return array|false {
765 *     Array of file relative path, width, and height on success. Additionally includes absolute
766 *     path and URL if registered size is passed to `$size` parameter. False on failure.
767 *
768 *     @type string $file   Filename of image.
769 *     @type int    $width  Width of image in pixels.
770 *     @type int    $height Height of image in pixels.
771 *     @type string $path   Path of image relative to uploads directory.
772 *     @type string $url    URL of image.
773 * }
774 */
775function image_get_intermediate_size( $post_id, $size = 'thumbnail' ) {
776        $imagedata = wp_get_attachment_metadata( $post_id );
777
778        if ( ! $size || ! is_array( $imagedata ) || empty( $imagedata['sizes'] ) ) {
779                return false;
780        }
781
782        $data = array();
783
784        // Find the best match when '$size' is an array.
785        if ( is_array( $size ) ) {
786                $candidates = array();
787
788                if ( ! isset( $imagedata['file'] ) && isset( $imagedata['sizes']['full'] ) ) {
789                        $imagedata['height'] = $imagedata['sizes']['full']['height'];
790                        $imagedata['width']  = $imagedata['sizes']['full']['width'];
791                }
792
793                foreach ( $imagedata['sizes'] as $_size => $data ) {
794                        // If there's an exact match to an existing image size, short circuit.
795                        if ( (int) $data['width'] === (int) $size[0] && (int) $data['height'] === (int) $size[1] ) {
796                                $candidates[ $data['width'] * $data['height'] ] = $data;
797                                break;
798                        }
799
800                        // If it's not an exact match, consider larger sizes with the same aspect ratio.
801                        if ( $data['width'] >= $size[0] && $data['height'] >= $size[1] ) {
802                                // If '0' is passed to either size, we test ratios against the original file.
803                                if ( 0 === $size[0] || 0 === $size[1] ) {
804                                        $same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $imagedata['width'], $imagedata['height'] );
805                                } else {
806                                        $same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $size[0], $size[1] );
807                                }
808
809                                if ( $same_ratio ) {
810                                        $candidates[ $data['width'] * $data['height'] ] = $data;
811                                }
812                        }
813                }
814
815                if ( ! empty( $candidates ) ) {
816                        // Sort the array by size if we have more than one candidate.
817                        if ( 1 < count( $candidates ) ) {
818                                ksort( $candidates );
819                        }
820
821                        $data = array_shift( $candidates );
822                        /*
823                        * When the size requested is smaller than the thumbnail dimensions, we
824                        * fall back to the thumbnail size to maintain backward compatibility with
825                        * pre 4.6 versions of WordPress.
826                        */
827                } elseif ( ! empty( $imagedata['sizes']['thumbnail'] ) && $imagedata['sizes']['thumbnail']['width'] >= $size[0] && $imagedata['sizes']['thumbnail']['width'] >= $size[1] ) {
828                        $data = $imagedata['sizes']['thumbnail'];
829                } else {
830                        return false;
831                }
832
833                // Constrain the width and height attributes to the requested values.
834                list( $data['width'], $data['height'] ) = image_constrain_size_for_editor( $data['width'], $data['height'], $size );
835
836        } elseif ( ! empty( $imagedata['sizes'][ $size ] ) ) {
837                $data = $imagedata['sizes'][ $size ];
838        }
839
840        // If we still don't have a match at this point, return false.
841        if ( empty( $data ) ) {
842                return false;
843        }
844
845        // Include the full filesystem path of the intermediate file.
846        if ( empty( $data['path'] ) && ! empty( $data['file'] ) && ! empty( $imagedata['file'] ) ) {
847                $file_url     = wp_get_attachment_url( $post_id );
848                $data['path'] = path_join( dirname( $imagedata['file'] ), $data['file'] );
849                $data['url']  = path_join( dirname( $file_url ), $data['file'] );
850        }
851
852        /**
853         * Filters the output of image_get_intermediate_size()
854         *
855         * @since 4.4.0
856         *
857         * @see image_get_intermediate_size()
858         *
859         * @param array        $data    Array of file relative path, width, and height on success. May also include
860         *                              file absolute path and URL.
861         * @param int          $post_id The ID of the image attachment.
862         * @param string|int[] $size    Requested image size. Can be any registered image size name, or
863         *                              an array of width and height values in pixels (in that order).
864         */
865        return apply_filters( 'image_get_intermediate_size', $data, $post_id, $size );
866}
867
868/**
869 * Gets the available intermediate image size names.
870 *
871 * @since 3.0.0
872 *
873 * @return string[] An array of image size names.
874 */
875function get_intermediate_image_sizes() {
876        $default_sizes    = array( 'thumbnail', 'medium', 'medium_large', 'large' );
877        $additional_sizes = wp_get_additional_image_sizes();
878
879        if ( ! empty( $additional_sizes ) ) {
880                $default_sizes = array_merge( $default_sizes, array_keys( $additional_sizes ) );
881        }
882
883        /**
884         * Filters the list of intermediate image sizes.
885         *
886         * @since 2.5.0
887         *
888         * @param string[] $default_sizes An array of intermediate image size names. Defaults
889         *                                are 'thumbnail', 'medium', 'medium_large', 'large'.
890         */
891        return apply_filters( 'intermediate_image_sizes', $default_sizes );
892}
893
894/**
895 * Returns a normalized list of all currently registered image sub-sizes.
896 *
897 * @since 5.3.0
898 * @uses wp_get_additional_image_sizes()
899 * @uses get_intermediate_image_sizes()
900 *
901 * @return array[] Associative array of arrays of image sub-size information,
902 *                 keyed by image size name.
903 */
904function wp_get_registered_image_subsizes() {
905        $additional_sizes = wp_get_additional_image_sizes();
906        $all_sizes        = array();
907
908        foreach ( get_intermediate_image_sizes() as $size_name ) {
909                $size_data = array(
910                        'width'  => 0,
911                        'height' => 0,
912                        'crop'   => false,
913                );
914
915                if ( isset( $additional_sizes[ $size_name ]['width'] ) ) {
916                        // For sizes added by plugins and themes.
917                        $size_data['width'] = (int) $additional_sizes[ $size_name ]['width'];
918                } else {
919                        // For default sizes set in options.
920                        $size_data['width'] = (int) get_option( "{$size_name}_size_w" );
921                }
922
923                if ( isset( $additional_sizes[ $size_name ]['height'] ) ) {
924                        $size_data['height'] = (int) $additional_sizes[ $size_name ]['height'];
925                } else {
926                        $size_data['height'] = (int) get_option( "{$size_name}_size_h" );
927                }
928
929                if ( empty( $size_data['width'] ) && empty( $size_data['height'] ) ) {
930                        // This size isn't set.
931                        continue;
932                }
933
934                if ( isset( $additional_sizes[ $size_name ]['crop'] ) ) {
935                        $size_data['crop'] = $additional_sizes[ $size_name ]['crop'];
936                } else {
937                        $size_data['crop'] = get_option( "{$size_name}_crop" );
938                }
939
940                if ( ! is_array( $size_data['crop'] ) || empty( $size_data['crop'] ) ) {
941                        $size_data['crop'] = (bool) $size_data['crop'];
942                }
943
944                $all_sizes[ $size_name ] = $size_data;
945        }
946
947        return $all_sizes;
948}
949
950/**
951 * Retrieves an image to represent an attachment.
952 *
953 * @since 2.5.0
954 *
955 * @param int          $attachment_id Image attachment ID.
956 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
957 *                                    width and height values in pixels (in that order). Default 'thumbnail'.
958 * @param bool         $icon          Optional. Whether the image should fall back to a mime type icon. Default false.
959 * @return array|false {
960 *     Array of image data, or boolean false if no image is available.
961 *
962 *     @type string $0 Image source URL.
963 *     @type int    $1 Image width in pixels.
964 *     @type int    $2 Image height in pixels.
965 *     @type bool   $3 Whether the image is a resized image.
966 * }
967 */
968function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon = false ) {
969        // Get a thumbnail or intermediate image if there is one.
970        $image = image_downsize( $attachment_id, $size );
971        if ( ! $image ) {
972                $src = false;
973
974                if ( $icon ) {
975                        $src = wp_mime_type_icon( $attachment_id, '.svg' );
976
977                        if ( $src ) {
978                                /** This filter is documented in wp-includes/post.php */
979                                $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
980
981                                $src_file = $icon_dir . '/' . wp_basename( $src );
982
983                                list( $width, $height ) = wp_getimagesize( $src_file );
984
985                                $ext = strtolower( substr( $src_file, -4 ) );
986
987                                if ( '.svg' === $ext ) {
988                                        // SVG does not have true dimensions, so this assigns width and height directly.
989                                        $width  = 48;
990                                        $height = 64;
991                                } else {
992                                        list( $width, $height ) = wp_getimagesize( $src_file );
993                                }
994                        }
995                }
996
997                if ( $src && $width && $height ) {
998                        $image = array( $src, $width, $height, false );
999                }
1000        }
1001        /**
1002         * Filters the attachment image source result.
1003         *
1004         * @since 4.3.0
1005         *
1006         * @param array|false  $image         {
1007         *     Array of image data, or boolean false if no image is available.
1008         *
1009         *     @type string $0 Image source URL.
1010         *     @type int    $1 Image width in pixels.
1011         *     @type int    $2 Image height in pixels.
1012         *     @type bool   $3 Whether the image is a resized image.
1013         * }
1014         * @param int          $attachment_id Image attachment ID.
1015         * @param string|int[] $size          Requested image size. Can be any registered image size name, or
1016         *                                    an array of width and height values in pixels (in that order).
1017         * @param bool         $icon          Whether the image should be treated as an icon.
1018         */
1019        return apply_filters( 'wp_get_attachment_image_src', $image, $attachment_id, $size, $icon );
1020}
1021
1022/**
1023 * Gets an HTML img element representing an image attachment.
1024 *
1025 * While `$size` will accept an array, it is better to register a size with
1026 * add_image_size() so that a cropped version is generated. It's much more
1027 * efficient than having to find the closest-sized image and then having the
1028 * browser scale down the image.
1029 *
1030 * @since 2.5.0
1031 * @since 4.4.0 The `$srcset` and `$sizes` attributes were added.
1032 * @since 5.5.0 The `$loading` attribute was added.
1033 * @since 6.1.0 The `$decoding` attribute was added.
1034 *
1035 * @param int          $attachment_id Image attachment ID.
1036 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array
1037 *                                    of width and height values in pixels (in that order). Default 'thumbnail'.
1038 * @param bool         $icon          Optional. Whether the image should be treated as an icon. Default false.
1039 * @param string|array $attr {
1040 *     Optional. Attributes for the image markup.
1041 *
1042 *     @type string       $src           Image attachment URL.
1043 *     @type string       $class         CSS class name or space-separated list of classes.
1044 *                                       Default `attachment-$size_class size-$size_class`,
1045 *                                       where `$size_class` is the image size being requested.
1046 *     @type string       $alt           Image description for the alt attribute.
1047 *     @type string       $srcset        The 'srcset' attribute value.
1048 *     @type string       $sizes         The 'sizes' attribute value.
1049 *     @type string|false $loading       The 'loading' attribute value. Passing a value of false
1050 *                                       will result in the attribute being omitted for the image.
1051 *                                       Default determined by {@see wp_get_loading_optimization_attributes()}.
1052 *     @type string       $decoding      The 'decoding' attribute value. Possible values are
1053 *                                       'async' (default), 'sync', or 'auto'. Passing false or an empty
1054 *                                       string will result in the attribute being omitted.
1055 *     @type string       $fetchpriority The 'fetchpriority' attribute value, whether `high`, `low`, or `auto`.
1056 *                                       Default determined by {@see wp_get_loading_optimization_attributes()}.
1057 * }
1058 * @return string HTML img element or empty string on failure.
1059 */
1060function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = '' ) {
1061        $html  = '';
1062        $image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
1063
1064        if ( $image ) {
1065                list( $src, $width, $height ) = $image;
1066
1067                $attachment = get_post( $attachment_id );
1068                $hwstring   = image_hwstring( $width, $height );
1069                $size_class = $size;
1070
1071                if ( is_array( $size_class ) ) {
1072                        $size_class = implode( 'x', $size_class );
1073                }
1074
1075                $default_attr = array(
1076                        'src'   => $src,
1077                        'class' => "attachment-$size_class size-$size_class",
1078                        'alt'   => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),
1079                );
1080
1081                /**
1082                 * Filters the context in which wp_get_attachment_image() is used.
1083                 *
1084                 * @since 6.3.0
1085                 *
1086                 * @param string $context The context. Default 'wp_get_attachment_image'.
1087                 */
1088                $context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' );
1089                $attr    = wp_parse_args( $attr, $default_attr );
1090
1091                $loading_attr              = $attr;
1092                $loading_attr['width']     = $width;
1093                $loading_attr['height']    = $height;
1094                $loading_optimization_attr = wp_get_loading_optimization_attributes(
1095                        'img',
1096                        $loading_attr,
1097                        $context
1098                );
1099
1100                // Add loading optimization attributes if not available.
1101                $attr = array_merge( $attr, $loading_optimization_attr );
1102
1103                // Omit the `decoding` attribute if the value is invalid according to the spec.
1104                if ( empty( $attr['decoding'] ) || ! in_array( $attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) {
1105                        unset( $attr['decoding'] );
1106                }
1107
1108                /*
1109                 * If the default value of `lazy` for the `loading` attribute is overridden
1110                 * to omit the attribute for this image, ensure it is not included.
1111                 */
1112                if ( isset( $attr['loading'] ) && ! $attr['loading'] ) {
1113                        unset( $attr['loading'] );
1114                }
1115
1116                // If the `fetchpriority` attribute is overridden and set to false or an empty string.
1117                if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) {
1118                        unset( $attr['fetchpriority'] );
1119                }
1120
1121                // Generate 'srcset' and 'sizes' if not already present.
1122                if ( empty( $attr['srcset'] ) ) {
1123                        $image_meta = wp_get_attachment_metadata( $attachment_id );
1124
1125                        if ( is_array( $image_meta ) ) {
1126                                $size_array = array( absint( $width ), absint( $height ) );
1127                                $srcset     = wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id );
1128                                $sizes      = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id );
1129
1130                                if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) {
1131                                        $attr['srcset'] = $srcset;
1132
1133                                        if ( empty( $attr['sizes'] ) ) {
1134                                                $attr['sizes'] = $sizes;
1135                                        }
1136                                }
1137                        }
1138                }
1139
1140                /** This filter is documented in wp-includes/media.php */
1141                $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true );
1142
1143                // Adds 'auto' to the sizes attribute if applicable.
1144                if (
1145                        $add_auto_sizes &&
1146                        isset( $attr['loading'] ) &&
1147                        'lazy' === $attr['loading'] &&
1148                        isset( $attr['sizes'] ) &&
1149                        ! wp_sizes_attribute_includes_valid_auto( $attr['sizes'] )
1150                ) {
1151                        $attr['sizes'] = 'auto, ' . $attr['sizes'];
1152                }
1153
1154                /**
1155                 * Filters the list of attachment image attributes.
1156                 *
1157                 * @since 2.8.0
1158                 *
1159                 * @param string[]     $attr       Array of attribute values for the image markup, keyed by attribute name.
1160                 *                                 See wp_get_attachment_image().
1161                 * @param WP_Post      $attachment Image attachment post.
1162                 * @param string|int[] $size       Requested image size. Can be any registered image size name, or
1163                 *                                 an array of width and height values in pixels (in that order).
1164                 */
1165                $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size );
1166
1167                $attr = array_map( 'esc_attr', $attr );
1168                $html = rtrim( "<img $hwstring" );
1169
1170                foreach ( $attr as $name => $value ) {
1171                        $html .= " $name=" . '"' . $value . '"';
1172                }
1173
1174                $html .= ' />';
1175        }
1176
1177        /**
1178         * Filters the HTML img element representing an image attachment.
1179         *
1180         * @since 5.6.0
1181         *
1182         * @param string       $html          HTML img element or empty string on failure.
1183         * @param int          $attachment_id Image attachment ID.
1184         * @param string|int[] $size          Requested image size. Can be any registered image size name, or
1185         *                                    an array of width and height values in pixels (in that order).
1186         * @param bool         $icon          Whether the image should be treated as an icon.
1187         * @param string[]     $attr          Array of attribute values for the image markup, keyed by attribute name.
1188         *                                    See wp_get_attachment_image().
1189         */
1190        return apply_filters( 'wp_get_attachment_image', $html, $attachment_id, $size, $icon, $attr );
1191}
1192
1193/**
1194 * Gets the URL of an image attachment.
1195 *
1196 * @since 4.4.0
1197 *
1198 * @param int          $attachment_id Image attachment ID.
1199 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
1200 *                                    width and height values in pixels (in that order). Default 'thumbnail'.
1201 * @param bool         $icon          Optional. Whether the image should be treated as an icon. Default false.
1202 * @return string|false Attachment URL or false if no image is available. If `$size` does not match
1203 *                      any registered image size, the original image URL will be returned.
1204 */
1205function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon = false ) {
1206        $image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
1207        return isset( $image[0] ) ? $image[0] : false;
1208}
1209
1210/**
1211 * Gets the attachment path relative to the upload directory.
1212 *
1213 * @since 4.4.1
1214 * @access private
1215 *
1216 * @param string $file Attachment file name.
1217 * @return string Attachment path relative to the upload directory.
1218 */
1219function _wp_get_attachment_relative_path( $file ) {
1220        $dirname = dirname( $file );
1221
1222        if ( '.' === $dirname ) {
1223                return '';
1224        }
1225
1226        if ( str_contains( $dirname, 'wp-content/uploads' ) ) {
1227                // Get the directory name relative to the upload directory (back compat for pre-2.7 uploads).
1228                $dirname = substr( $dirname, strpos( $dirname, 'wp-content/uploads' ) + 18 );
1229                $dirname = ltrim( $dirname, '/' );
1230        }
1231
1232        return $dirname;
1233}
1234
1235/**
1236 * Gets the image size as array from its meta data.
1237 *
1238 * Used for responsive images.
1239 *
1240 * @since 4.4.0
1241 * @access private
1242 *
1243 * @param string $size_name  Image size. Accepts any registered image size name.
1244 * @param array  $image_meta The image meta data.
1245 * @return array|false {
1246 *     Array of width and height or false if the size isn't present in the meta data.
1247 *
1248 *     @type int $0 Image width.
1249 *     @type int $1 Image height.
1250 * }
1251 */
1252function _wp_get_image_size_from_meta( $size_name, $image_meta ) {
1253        if ( 'full' === $size_name ) {
1254                return array(
1255                        absint( $image_meta['width'] ),
1256                        absint( $image_meta['height'] ),
1257                );
1258        } elseif ( ! empty( $image_meta['sizes'][ $size_name ] ) ) {
1259                return array(
1260                        absint( $image_meta['sizes'][ $size_name ]['width'] ),
1261                        absint( $image_meta['sizes'][ $size_name ]['height'] ),
1262                );
1263        }
1264
1265        return false;
1266}
1267
1268/**
1269 * Retrieves the value for an image attachment's 'srcset' attribute.
1270 *
1271 * @since 4.4.0
1272 *
1273 * @see wp_calculate_image_srcset()
1274 *
1275 * @param int          $attachment_id Image attachment ID.
1276 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
1277 *                                    width and height values in pixels (in that order). Default 'medium'.
1278 * @param array|null   $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
1279 *                                    Default null.
1280 * @return string|false A 'srcset' value string or false.
1281 */
1282function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium', $image_meta = null ) {
1283        $image = wp_get_attachment_image_src( $attachment_id, $size );
1284
1285        if ( ! $image ) {
1286                return false;
1287        }
1288
1289        if ( ! is_array( $image_meta ) ) {
1290                $image_meta = wp_get_attachment_metadata( $attachment_id );
1291        }
1292
1293        $image_src  = $image[0];
1294        $size_array = array(
1295                absint( $image[1] ),
1296                absint( $image[2] ),
1297        );
1298
1299        return wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
1300}
1301
1302/**
1303 * A helper function to calculate the image sources to include in a 'srcset' attribute.
1304 *
1305 * @since 4.4.0
1306 *
1307 * @param int[]  $size_array    {
1308 *     An array of width and height values.
1309 *
1310 *     @type int $0 The width in pixels.
1311 *     @type int $1 The height in pixels.
1312 * }
1313 * @param string $image_src     The 'src' of the image.
1314 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
1315 * @param int    $attachment_id Optional. The image attachment ID. Default 0.
1316 * @return string|false The 'srcset' attribute value. False on error or when only one source exists.
1317 */
1318function wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id = 0 ) {
1319        /**
1320         * Pre-filters the image meta to be able to fix inconsistencies in the stored data.
1321         *
1322         * @since 4.5.0
1323         *
1324         * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
1325         * @param int[]  $size_array    {
1326         *     An array of requested width and height values.
1327         *
1328         *     @type int $0 The width in pixels.
1329         *     @type int $1 The height in pixels.
1330         * }
1331         * @param string $image_src     The 'src' of the image.
1332         * @param int    $attachment_id The image attachment ID or 0 if not supplied.
1333         */
1334        $image_meta = apply_filters( 'wp_calculate_image_srcset_meta', $image_meta, $size_array, $image_src, $attachment_id );
1335
1336        if ( empty( $image_meta['sizes'] ) || ! isset( $image_meta['file'] ) || strlen( $image_meta['file'] ) < 4 ) {
1337                return false;
1338        }
1339
1340        $image_sizes = $image_meta['sizes'];
1341
1342        // Get the width and height of the image.
1343        $image_width  = (int) $size_array[0];
1344        $image_height = (int) $size_array[1];
1345
1346        // Bail early if error/no width.
1347        if ( $image_width < 1 ) {
1348                return false;
1349        }
1350
1351        $image_basename = wp_basename( $image_meta['file'] );
1352
1353        /*
1354         * WordPress flattens animated GIFs into one frame when generating intermediate sizes.
1355         * To avoid hiding animation in user content, if src is a full size GIF, a srcset attribute is not generated.
1356         * If src is an intermediate size GIF, the full size is excluded from srcset to keep a flattened GIF from becoming animated.
1357         */
1358        if ( ! isset( $image_sizes['thumbnail']['mime-type'] ) || 'image/gif' !== $image_sizes['thumbnail']['mime-type'] ) {
1359                $image_sizes[] = array(
1360                        'width'  => $image_meta['width'],
1361                        'height' => $image_meta['height'],
1362                        'file'   => $image_basename,
1363                );
1364        } elseif ( str_contains( $image_src, $image_meta['file'] ) ) {
1365                return false;
1366        }
1367
1368        // Retrieve the uploads sub-directory from the full size image.
1369        $dirname = _wp_get_attachment_relative_path( $image_meta['file'] );
1370
1371        if ( $dirname ) {
1372                $dirname = trailingslashit( $dirname );
1373        }
1374
1375        $upload_dir    = wp_get_upload_dir();
1376        $image_baseurl = trailingslashit( $upload_dir['baseurl'] ) . $dirname;
1377
1378        /*
1379         * If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain
1380         * (which is to say, when they share the domain name of the current request).
1381         */
1382        if ( is_ssl() && ! str_starts_with( $image_baseurl, 'https' ) ) {
1383                /*
1384                 * Since the `Host:` header might contain a port, it should
1385                 * be compared against the image URL using the same port.
1386                 */
1387                $parsed = parse_url( $image_baseurl );
1388                $domain = isset( $parsed['host'] ) ? $parsed['host'] : '';
1389
1390                if ( isset( $parsed['port'] ) ) {
1391                        $domain .= ':' . $parsed['port'];
1392                }
1393
1394                if ( $_SERVER['HTTP_HOST'] === $domain ) {
1395                        $image_baseurl = set_url_scheme( $image_baseurl, 'https' );
1396                }
1397        }
1398
1399        /*
1400         * Images that have been edited in WordPress after being uploaded will
1401         * contain a unique hash. Look for that hash and use it later to filter
1402         * out images that are leftovers from previous versions.
1403         */
1404        $image_edited = preg_match( '/-e[0-9]{13}/', wp_basename( $image_src ), $image_edit_hash );
1405
1406        /**
1407         * Filters the maximum image width to be included in a 'srcset' attribute.
1408         *
1409         * @since 4.4.0
1410         *
1411         * @param int   $max_width  The maximum image width to be included in the 'srcset'. Default '2048'.
1412         * @param int[] $size_array {
1413         *     An array of requested width and height values.
1414         *
1415         *     @type int $0 The width in pixels.
1416         *     @type int $1 The height in pixels.
1417         * }
1418         */
1419        $max_srcset_image_width = apply_filters( 'max_srcset_image_width', 2048, $size_array );
1420
1421        // Array to hold URL candidates.
1422        $sources = array();
1423
1424        /**
1425         * To make sure the ID matches our image src, we will check to see if any sizes in our attachment
1426         * meta match our $image_src. If no matches are found we don't return a srcset to avoid serving
1427         * an incorrect image. See #35045.
1428         */
1429        $src_matched = false;
1430
1431        /*
1432         * Loop through available images. Only use images that are resized
1433         * versions of the same edit.
1434         */
1435        foreach ( $image_sizes as $image ) {
1436                $is_src = false;
1437
1438                // Check if image meta isn't corrupted.
1439                if ( ! is_array( $image ) ) {
1440                        continue;
1441                }
1442
1443                // If the file name is part of the `src`, we've confirmed a match.
1444                if ( ! $src_matched && str_contains( $image_src, $dirname . $image['file'] ) ) {
1445                        $src_matched = true;
1446                        $is_src      = true;
1447                }
1448
1449                // Filter out images that are from previous edits.
1450                if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) {
1451                        continue;
1452                }
1453
1454                /*
1455                 * Filters out images that are wider than '$max_srcset_image_width' unless
1456                 * that file is in the 'src' attribute.
1457                 */
1458                if ( $max_srcset_image_width && $image['width'] > $max_srcset_image_width && ! $is_src ) {
1459                        continue;
1460                }
1461
1462                // If the image dimensions are within 1px of the expected size, use it.
1463                if ( wp_image_matches_ratio( $image_width, $image_height, $image['width'], $image['height'] ) ) {
1464                        // Add the URL, descriptor, and value to the sources array to be returned.
1465                        $source = array(
1466                                'url'        => $image_baseurl . $image['file'],
1467                                'descriptor' => 'w',
1468                                'value'      => $image['width'],
1469                        );
1470
1471                        // The 'src' image has to be the first in the 'srcset', because of a bug in iOS8. See #35030.
1472                        if ( $is_src ) {
1473                                $sources = array( $image['width'] => $source ) + $sources;
1474                        } else {
1475                                $sources[ $image['width'] ] = $source;
1476                        }
1477                }
1478        }
1479
1480        /**
1481         * Filters an image's 'srcset' sources.
1482         *
1483         * @since 4.4.0
1484         *
1485         * @param array  $sources {
1486         *     One or more arrays of source data to include in the 'srcset'.
1487         *
1488         *     @type array $width {
1489         *         @type string $url        The URL of an image source.
1490         *         @type string $descriptor The descriptor type used in the image candidate string,
1491         *                                  either 'w' or 'x'.
1492         *         @type int    $value      The source width if paired with a 'w' descriptor, or a
1493         *                                  pixel density value if paired with an 'x' descriptor.
1494         *     }
1495         * }
1496         * @param array $size_array     {
1497         *     An array of requested width and height values.
1498         *
1499         *     @type int $0 The width in pixels.
1500         *     @type int $1 The height in pixels.
1501         * }
1502         * @param string $image_src     The 'src' of the image.
1503         * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
1504         * @param int    $attachment_id Image attachment ID or 0.
1505         */
1506        $sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id );
1507
1508        // Only return a 'srcset' value if there is more than one source.
1509        if ( ! $src_matched || ! is_array( $sources ) || count( $sources ) < 2 ) {
1510                return false;
1511        }
1512
1513        $srcset = '';
1514
1515        foreach ( $sources as $source ) {
1516                $srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
1517        }
1518
1519        return rtrim( $srcset, ', ' );
1520}
1521
1522/**
1523 * Retrieves the value for an image attachment's 'sizes' attribute.
1524 *
1525 * @since 4.4.0
1526 *
1527 * @see wp_calculate_image_sizes()
1528 *
1529 * @param int          $attachment_id Image attachment ID.
1530 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
1531 *                                    width and height values in pixels (in that order). Default 'medium'.
1532 * @param array|null   $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
1533 *                                    Default null.
1534 * @return string|false A valid source size value for use in a 'sizes' attribute or false.
1535 */
1536function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null ) {
1537        $image = wp_get_attachment_image_src( $attachment_id, $size );
1538
1539        if ( ! $image ) {
1540                return false;
1541        }
1542
1543        if ( ! is_array( $image_meta ) ) {
1544                $image_meta = wp_get_attachment_metadata( $attachment_id );
1545        }
1546
1547        $image_src  = $image[0];
1548        $size_array = array(
1549                absint( $image[1] ),
1550                absint( $image[2] ),
1551        );
1552
1553        return wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
1554}
1555
1556/**
1557 * Creates a 'sizes' attribute value for an image.
1558 *
1559 * @since 4.4.0
1560 *
1561 * @param string|int[] $size          Image size. Accepts any registered image size name, or an array of
1562 *                                    width and height values in pixels (in that order).
1563 * @param string|null  $image_src     Optional. The URL to the image file. Default null.
1564 * @param array|null   $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
1565 *                                    Default null.
1566 * @param int          $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id`
1567 *                                    is needed when using the image size name as argument for `$size`. Default 0.
1568 * @return string|false A valid source size value for use in a 'sizes' attribute or false.
1569 */
1570function wp_calculate_image_sizes( $size, $image_src = null, $image_meta = null, $attachment_id = 0 ) {
1571        $width = 0;
1572
1573        if ( is_array( $size ) ) {
1574                $width = absint( $size[0] );
1575        } elseif ( is_string( $size ) ) {
1576                if ( ! $image_meta && $attachment_id ) {
1577                        $image_meta = wp_get_attachment_metadata( $attachment_id );
1578                }
1579
1580                if ( is_array( $image_meta ) ) {
1581                        $size_array = _wp_get_image_size_from_meta( $size, $image_meta );
1582                        if ( $size_array ) {
1583                                $width = absint( $size_array[0] );
1584                        }
1585                }
1586        }
1587
1588        if ( ! $width ) {
1589                return false;
1590        }
1591
1592        // Setup the default 'sizes' attribute.
1593        $sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width );
1594
1595        /**
1596         * Filters the output of 'wp_calculate_image_sizes()'.
1597         *
1598         * @since 4.4.0
1599         *
1600         * @param string       $sizes         A source size value for use in a 'sizes' attribute.
1601         * @param string|int[] $size          Requested image size. Can be any registered image size name, or
1602         *                                    an array of width and height values in pixels (in that order).
1603         * @param string|null  $image_src     The URL to the image file or null.
1604         * @param array|null   $image_meta    The image meta data as returned by wp_get_attachment_metadata() or null.
1605         * @param int          $attachment_id Image attachment ID of the original image or 0.
1606         */
1607        return apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id );
1608}
1609
1610/**
1611 * Determines if the image meta data is for the image source file.
1612 *
1613 * The image meta data is retrieved by attachment post ID. In some cases the post IDs may change.
1614 * For example when the website is exported and imported at another website. Then the
1615 * attachment post IDs that are in post_content for the exported website may not match
1616 * the same attachments at the new website.
1617 *
1618 * @since 5.5.0
1619 *
1620 * @param string $image_location The full path or URI to the image file.
1621 * @param array  $image_meta     The attachment meta data as returned by 'wp_get_attachment_metadata()'.
1622 * @param int    $attachment_id  Optional. The image attachment ID. Default 0.
1623 * @return bool Whether the image meta is for this image file.
1624 */
1625function wp_image_file_matches_image_meta( $image_location, $image_meta, $attachment_id = 0 ) {
1626        $match = false;
1627
1628        // Ensure the $image_meta is valid.
1629        if ( isset( $image_meta['file'] ) && strlen( $image_meta['file'] ) > 4 ) {
1630                // Remove query args in image URI.
1631                list( $image_location ) = explode( '?', $image_location );
1632
1633                // Check if the relative image path from the image meta is at the end of $image_location.
1634                if ( strrpos( $image_location, $image_meta['file'] ) === strlen( $image_location ) - strlen( $image_meta['file'] ) ) {
1635                        $match = true;
1636                } else {
1637                        // Retrieve the uploads sub-directory from the full size image.
1638                        $dirname = _wp_get_attachment_relative_path( $image_meta['file'] );
1639
1640                        if ( $dirname ) {
1641                                $dirname = trailingslashit( $dirname );
1642                        }
1643
1644                        if ( ! empty( $image_meta['original_image'] ) ) {
1645                                $relative_path = $dirname . $image_meta['original_image'];
1646
1647                                if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
1648                                        $match = true;
1649                                }
1650                        }
1651
1652                        if ( ! $match && ! empty( $image_meta['sizes'] ) ) {
1653                                foreach ( $image_meta['sizes'] as $image_size_data ) {
1654                                        $relative_path = $dirname . $image_size_data['file'];
1655
1656                                        if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
1657                                                $match = true;
1658                                                break;
1659                                        }
1660                                }
1661                        }
1662                }
1663        }
1664
1665        /**
1666         * Filters whether an image path or URI matches image meta.
1667         *
1668         * @since 5.5.0
1669         *
1670         * @param bool   $match          Whether the image relative path from the image meta
1671         *                               matches the end of the URI or path to the image file.
1672         * @param string $image_location Full path or URI to the tested image file.
1673         * @param array  $image_meta     The image meta data as returned by 'wp_get_attachment_metadata()'.
1674         * @param int    $attachment_id  The image attachment ID or 0 if not supplied.
1675         */
1676        return apply_filters( 'wp_image_file_matches_image_meta', $match, $image_location, $image_meta, $attachment_id );
1677}
1678
1679/**
1680 * Determines an image's width and height dimensions based on the source file.
1681 *
1682 * @since 5.5.0
1683 *
1684 * @param string $image_src     The image source file.
1685 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
1686 * @param int    $attachment_id Optional. The image attachment ID. Default 0.
1687 * @return array|false Array with first element being the width and second element being the height,
1688 *                     or false if dimensions cannot be determined.
1689 */
1690function wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id = 0 ) {
1691        $dimensions = false;
1692
1693        // Is it a full size image?
1694        if (
1695                isset( $image_meta['file'] ) &&
1696                str_contains( $image_src, wp_basename( $image_meta['file'] ) )
1697        ) {
1698                $dimensions = array(
1699                        (int) $image_meta['width'],
1700                        (int) $image_meta['height'],
1701                );
1702        }
1703
1704        if ( ! $dimensions && ! empty( $image_meta['sizes'] ) ) {
1705                $src_filename = wp_basename( $image_src );
1706
1707                foreach ( $image_meta['sizes'] as $image_size_data ) {
1708                        if ( $src_filename === $image_size_data['file'] ) {
1709                                $dimensions = array(
1710                                        (int) $image_size_data['width'],
1711                                        (int) $image_size_data['height'],
1712                                );
1713
1714                                break;
1715                        }
1716                }
1717        }
1718
1719        /**
1720         * Filters the 'wp_image_src_get_dimensions' value.
1721         *
1722         * @since 5.7.0
1723         *
1724         * @param array|false $dimensions    Array with first element being the width
1725         *                                   and second element being the height, or
1726         *                                   false if dimensions could not be determined.
1727         * @param string      $image_src     The image source file.
1728         * @param array       $image_meta    The image meta data as returned by
1729         *                                   'wp_get_attachment_metadata()'.
1730         * @param int         $attachment_id The image attachment ID. Default 0.
1731         */
1732        return apply_filters( 'wp_image_src_get_dimensions', $dimensions, $image_src, $image_meta, $attachment_id );
1733}
1734
1735/**
1736 * Adds 'srcset' and 'sizes' attributes to an existing 'img' element.
1737 *
1738 * @since 4.4.0
1739 *
1740 * @see wp_calculate_image_srcset()
1741 * @see wp_calculate_image_sizes()
1742 *
1743 * @param string $image         An HTML 'img' element to be filtered.
1744 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
1745 * @param int    $attachment_id Image attachment ID.
1746 * @return string Converted 'img' element with 'srcset' and 'sizes' attributes added.
1747 */
1748function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) {
1749        // Ensure the image meta exists.
1750        if ( empty( $image_meta['sizes'] ) ) {
1751                return $image;
1752        }
1753
1754        $image_src         = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
1755        list( $image_src ) = explode( '?', $image_src );
1756
1757        // Return early if we couldn't get the image source.
1758        if ( ! $image_src ) {
1759                return $image;
1760        }
1761
1762        // Bail early if an image has been inserted and later edited.
1763        if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash )
1764                && ! str_contains( wp_basename( $image_src ), $img_edit_hash[0] )
1765        ) {
1766                return $image;
1767        }
1768
1769        $width  = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0;
1770        $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0;
1771
1772        if ( $width && $height ) {
1773                $size_array = array( $width, $height );
1774        } else {
1775                $size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id );
1776                if ( ! $size_array ) {
1777                        return $image;
1778                }
1779        }
1780
1781        $srcset = wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
1782
1783        if ( $srcset ) {
1784                // Check if there is already a 'sizes' attribute.
1785                $sizes = strpos( $image, ' sizes=' );
1786
1787                if ( ! $sizes ) {
1788                        $sizes = wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
1789                }
1790        }
1791
1792        if ( $srcset && $sizes ) {
1793                // Format the 'srcset' and 'sizes' string and escape attributes.
1794                $attr = sprintf( ' srcset="%s"', esc_attr( $srcset ) );
1795
1796                if ( is_string( $sizes ) ) {
1797                        $attr .= sprintf( ' sizes="%s"', esc_attr( $sizes ) );
1798                }
1799
1800                // Add the srcset and sizes attributes to the image markup.
1801                return preg_replace( '/<img ([^>]+?)[\/ ]*>/', '<img $1' . $attr . ' />', $image );
1802        }
1803
1804        return $image;
1805}
1806
1807/**
1808 * Determines whether to add the `loading` attribute to the specified tag in the specified context.
1809 *
1810 * @since 5.5.0
1811 * @since 5.7.0 Now returns `true` by default for `iframe` tags.
1812 *
1813 * @param string $tag_name The tag name.
1814 * @param string $context  Additional context, like the current filter name
1815 *                         or the function name from where this was called.
1816 * @return bool Whether to add the attribute.
1817 */
1818function wp_lazy_loading_enabled( $tag_name, $context ) {
1819        /*
1820         * By default add to all 'img' and 'iframe' tags.
1821         * See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading
1822         * See https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-loading
1823         */
1824        $default = ( 'img' === $tag_name || 'iframe' === $tag_name );
1825
1826        /**
1827         * Filters whether to add the `loading` attribute to the specified tag in the specified context.
1828         *
1829         * @since 5.5.0
1830         *
1831         * @param bool   $default  Default value.
1832         * @param string $tag_name The tag name.
1833         * @param string $context  Additional context, like the current filter name
1834         *                         or the function name from where this was called.
1835         */
1836        return (bool) apply_filters( 'wp_lazy_loading_enabled', $default, $tag_name, $context );
1837}
1838
1839/**
1840 * Filters specific tags in post content and modifies their markup.
1841 *
1842 * Modifies HTML tags in post content to include new browser and HTML technologies
1843 * that may not have existed at the time of post creation. These modifications currently
1844 * include adding `srcset`, `sizes`, and `loading` attributes to `img` HTML tags, as well
1845 * as adding `loading` attributes to `iframe` HTML tags.
1846 * Future similar optimizations should be added/expected here.
1847 *
1848 * @since 5.5.0
1849 * @since 5.7.0 Now supports adding `loading` attributes to `iframe` tags.
1850 *
1851 * @see wp_img_tag_add_width_and_height_attr()
1852 * @see wp_img_tag_add_srcset_and_sizes_attr()
1853 * @see wp_img_tag_add_loading_optimization_attrs()
1854 * @see wp_iframe_tag_add_loading_attr()
1855 *
1856 * @param string $content The HTML content to be filtered.
1857 * @param string $context Optional. Additional context to pass to the filters.
1858 *                        Defaults to `current_filter()` when not set.
1859 * @return string Converted content with images modified.
1860 */
1861function wp_filter_content_tags( $content, $context = null ) {
1862        if ( null === $context ) {
1863                $context = current_filter();
1864        }
1865
1866        $add_iframe_loading_attr = wp_lazy_loading_enabled( 'iframe', $context );
1867
1868        if ( ! preg_match_all( '/<(img|iframe)\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ) {
1869                return $content;
1870        }
1871
1872        // List of the unique `img` tags found in $content.
1873        $images = array();
1874
1875        // List of the unique `iframe` tags found in $content.
1876        $iframes = array();
1877
1878        foreach ( $matches as $match ) {
1879                list( $tag, $tag_name ) = $match;
1880
1881                switch ( $tag_name ) {
1882                        case 'img':
1883                                if ( preg_match( '/wp-image-([0-9]+)/i', $tag, $class_id ) ) {
1884                                        $attachment_id = absint( $class_id[1] );
1885
1886                                        if ( $attachment_id ) {
1887                                                /*
1888                                                 * If exactly the same image tag is used more than once, overwrite it.
1889                                                 * All identical tags will be replaced later with 'str_replace()'.
1890                                                 */
1891                                                $images[ $tag ] = $attachment_id;
1892                                                break;
1893                                        }
1894                                }
1895                                $images[ $tag ] = 0;
1896                                break;
1897                        case 'iframe':
1898                                $iframes[ $tag ] = 0;
1899                                break;
1900                }
1901        }
1902
1903        // Reduce the array to unique attachment IDs.
1904        $attachment_ids = array_unique( array_filter( array_values( $images ) ) );
1905
1906        if ( count( $attachment_ids ) > 1 ) {
1907                /*
1908                 * Warm the object cache with post and meta information for all found
1909                 * images to avoid making individual database calls.
1910                 */
1911                _prime_post_caches( $attachment_ids, false, true );
1912        }
1913
1914        // Iterate through the matches in order of occurrence as it is relevant for whether or not to lazy-load.
1915        foreach ( $matches as $match ) {
1916                // Filter an image match.
1917                if ( isset( $images[ $match[0] ] ) ) {
1918                        $filtered_image = $match[0];
1919                        $attachment_id  = $images[ $match[0] ];
1920
1921                        // Add 'width' and 'height' attributes if applicable.
1922                        if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' width=' ) && ! str_contains( $filtered_image, ' height=' ) ) {
1923                                $filtered_image = wp_img_tag_add_width_and_height_attr( $filtered_image, $context, $attachment_id );
1924                        }
1925
1926                        // Add 'srcset' and 'sizes' attributes if applicable.
1927                        if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' srcset=' ) ) {
1928                                $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id );
1929                        }
1930
1931                        // Add loading optimization attributes if applicable.
1932                        $filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context );
1933
1934                        // Adds 'auto' to the sizes attribute if applicable.
1935                        $filtered_image = wp_img_tag_add_auto_sizes( $filtered_image );
1936
1937                        /**
1938                         * Filters an img tag within the content for a given context.
1939                         *
1940                         * @since 6.0.0
1941                         *
1942                         * @param string $filtered_image Full img tag with attributes that will replace the source img tag.
1943                         * @param string $context        Additional context, like the current filter name or the function name from where this was called.
1944                         * @param int    $attachment_id  The image attachment ID. May be 0 in case the image is not an attachment.
1945                         */
1946                        $filtered_image = apply_filters( 'wp_content_img_tag', $filtered_image, $context, $attachment_id );
1947
1948                        if ( $filtered_image !== $match[0] ) {
1949                                $content = str_replace( $match[0], $filtered_image, $content );
1950                        }
1951
1952                        /*
1953                         * Unset image lookup to not run the same logic again unnecessarily if the same image tag is used more than
1954                         * once in the same blob of content.
1955                         */
1956                        unset( $images[ $match[0] ] );
1957                }
1958
1959                // Filter an iframe match.
1960                if ( isset( $iframes[ $match[0] ] ) ) {
1961                        $filtered_iframe = $match[0];
1962
1963                        // Add 'loading' attribute if applicable.
1964                        if ( $add_iframe_loading_attr && ! str_contains( $filtered_iframe, ' loading=' ) ) {
1965                                $filtered_iframe = wp_iframe_tag_add_loading_attr( $filtered_iframe, $context );
1966                        }
1967
1968                        if ( $filtered_iframe !== $match[0] ) {
1969                                $content = str_replace( $match[0], $filtered_iframe, $content );
1970                        }
1971
1972                        /*
1973                         * Unset iframe lookup to not run the same logic again unnecessarily if the same iframe tag is used more
1974                         * than once in the same blob of content.
1975                         */
1976                        unset( $iframes[ $match[0] ] );
1977                }
1978        }
1979
1980        return $content;
1981}
1982
1983/**
1984 * Adds 'auto' to the sizes attribute to the image, if the image is lazy loaded and does not already include it.
1985 *
1986 * @since 6.7.0
1987 *
1988 * @param string $image The image tag markup being filtered.
1989 * @return string The filtered image tag markup.
1990 */
1991function wp_img_tag_add_auto_sizes( string $image ): string {
1992        /**
1993         * Filters whether auto-sizes for lazy loaded images is enabled.
1994         *
1995         * @since 6.7.1
1996         *
1997         * @param boolean $enabled Whether auto-sizes for lazy loaded images is enabled.
1998         */
1999        if ( ! apply_filters( 'wp_img_tag_add_auto_sizes', true ) ) {
2000                return $image;
2001        }
2002
2003        $processor = new WP_HTML_Tag_Processor( $image );
2004
2005        // Bail if there is no IMG tag.
2006        if ( ! $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) {
2007                return $image;
2008        }
2009
2010        // Bail early if the image is not lazy-loaded.
2011        $loading = $processor->get_attribute( 'loading' );
2012        if ( ! is_string( $loading ) || 'lazy' !== strtolower( trim( $loading, " \t\f\r\n" ) ) ) {
2013                return $image;
2014        }
2015
2016        /*
2017         * Bail early if the image doesn't have a width attribute.
2018         * Per WordPress Core itself, lazy-loaded images should always have a width attribute.
2019         * However, it is possible that lazy-loading could be added by a plugin, where we don't have that guarantee.
2020         * As such, it still makes sense to ensure presence of a width attribute here in order to use `sizes=auto`.
2021         */
2022        $width = $processor->get_attribute( 'width' );
2023        if ( ! is_string( $width ) || '' === $width ) {
2024                return $image;
2025        }
2026
2027        $sizes = $processor->get_attribute( 'sizes' );
2028
2029        // Bail early if the image is not responsive.
2030        if ( ! is_string( $sizes ) ) {
2031                return $image;
2032        }
2033
2034        // Don't add 'auto' to the sizes attribute if it already exists.
2035        if ( wp_sizes_attribute_includes_valid_auto( $sizes ) ) {
2036                return $image;
2037        }
2038
2039        $processor->set_attribute( 'sizes', "auto, $sizes" );
2040        return $processor->get_updated_html();
2041}
2042
2043/**
2044 * Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list.
2045 *
2046 * Per the HTML spec, if present it must be the first entry.
2047 *
2048 * @since 6.7.0
2049 *
2050 * @param string $sizes_attr The 'sizes' attribute value.
2051 * @return bool True if the 'auto' keyword is present, false otherwise.
2052 */
2053function wp_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool {
2054        list( $first_size ) = explode( ',', $sizes_attr, 2 );
2055        return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) );
2056}
2057
2058/**
2059 * Prints a CSS rule to fix potential visual issues with images using `sizes=auto`.
2060 *
2061 * This rule overrides the similar rule in the default user agent stylesheet, to avoid images that use e.g.
2062 * `width: auto` or `width: fit-content` to appear smaller.
2063 *
2064 * @since 6.7.1
2065 * @see https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size
2066 * @see https://core.trac.wordpress.org/ticket/62413
2067 */
2068function wp_print_auto_sizes_contain_css_fix() {
2069        /** This filter is documented in wp-includes/media.php */
2070        $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true );
2071        if ( ! $add_auto_sizes ) {
2072                return;
2073        }
2074
2075        ?>
2076        <style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style>
2077        <?php
2078}
2079
2080/**
2081 * Adds optimization attributes to an `img` HTML tag.
2082 *
2083 * @since 6.3.0
2084 *
2085 * @param string $image   The HTML `img` tag where the attribute should be added.
2086 * @param string $context Additional context to pass to the filters.
2087 * @return string Converted `img` tag with optimization attributes added.
2088 */
2089function wp_img_tag_add_loading_optimization_attrs( $image, $context ) {
2090        $src               = preg_match( '/ src=["\']?([^"\']*)/i', $image, $matche_src ) ? $matche_src[1] : null;
2091        $width             = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null;
2092        $height            = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null;
2093        $loading_val       = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null;
2094        $fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null;
2095        $decoding_val      = preg_match( '/ decoding=["\']([A-Za-z]+)["\']/', $image, $match_decoding ) ? $match_decoding[1] : null;
2096
2097        /*
2098         * Get loading optimization attributes to use.
2099         * This must occur before the conditional check below so that even images
2100         * that are ineligible for being lazy-loaded are considered.
2101         */
2102        $optimization_attrs = wp_get_loading_optimization_attributes(
2103                'img',
2104                array(
2105                        'src'           => $src,
2106                        'width'         => $width,
2107                        'height'        => $height,
2108                        'loading'       => $loading_val,
2109                        'fetchpriority' => $fetchpriority_val,
2110                        'decoding'      => $decoding_val,
2111                ),
2112                $context
2113        );
2114
2115        // Images should have source for the loading optimization attributes to be added.
2116        if ( ! str_contains( $image, ' src="' ) ) {
2117                return $image;
2118        }
2119
2120        if ( empty( $decoding_val ) ) {
2121                /**
2122                 * Filters the `decoding` attribute value to add to an image. Default `async`.
2123                 *
2124                 * Returning a falsey value will omit the attribute.
2125                 *
2126                 * @since 6.1.0
2127                 *
2128                 * @param string|false|null $value      The `decoding` attribute value. Returning a falsey value
2129                 *                                      will result in the attribute being omitted for the image.
2130                 *                                      Otherwise, it may be: 'async', 'sync', or 'auto'. Defaults to false.
2131                 * @param string            $image      The HTML `img` tag to be filtered.
2132                 * @param string            $context    Additional context about how the function was called
2133                 *                                      or where the img tag is.
2134                 */
2135                $filtered_decoding_attr = apply_filters(
2136                        'wp_img_tag_add_decoding_attr',
2137                        isset( $optimization_attrs['decoding'] ) ? $optimization_attrs['decoding'] : false,
2138                        $image,
2139                        $context
2140                );
2141
2142                // Validate the values after filtering.
2143                if ( isset( $optimization_attrs['decoding'] ) && ! $filtered_decoding_attr ) {
2144                        // Unset `decoding` attribute if `$filtered_decoding_attr` is set to `false`.
2145                        unset( $optimization_attrs['decoding'] );
2146                } elseif ( in_array( $filtered_decoding_attr, array( 'async', 'sync', 'auto' ), true ) ) {
2147                        $optimization_attrs['decoding'] = $filtered_decoding_attr;
2148                }
2149
2150                if ( ! empty( $optimization_attrs['decoding'] ) ) {
2151                        $image = str_replace( '<img', '<img decoding="' . esc_attr( $optimization_attrs['decoding'] ) . '"', $image );
2152                }
2153        }
2154
2155        // Images should have dimension attributes for the 'loading' and 'fetchpriority' attributes to be added.
2156        if ( ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) {
2157                return $image;
2158        }
2159
2160        // Retained for backward compatibility.
2161        $loading_attrs_enabled = wp_lazy_loading_enabled( 'img', $context );
2162
2163        if ( empty( $loading_val ) && $loading_attrs_enabled ) {
2164                /**
2165                 * Filters the `loading` attribute value to add to an image. Default `lazy`.
2166                 *
2167                 * Returning `false` or an empty string will not add the attribute.
2168                 * Returning `true` will add the default value.
2169                 *
2170                 * @since 5.5.0
2171                 *
2172                 * @param string|bool $value   The `loading` attribute value. Returning a falsey value will result in
2173                 *                             the attribute being omitted for the image.
2174                 * @param string      $image   The HTML `img` tag to be filtered.
2175                 * @param string      $context Additional context about how the function was called or where the img tag is.
2176                 */
2177                $filtered_loading_attr = apply_filters(
2178                        'wp_img_tag_add_loading_attr',
2179                        isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false,
2180                        $image,
2181                        $context
2182                );
2183
2184                // Validate the values after filtering.
2185                if ( isset( $optimization_attrs['loading'] ) && ! $filtered_loading_attr ) {
2186                        // Unset `loading` attributes if `$filtered_loading_attr` is set to `false`.
2187                        unset( $optimization_attrs['loading'] );
2188                } elseif ( in_array( $filtered_loading_attr, array( 'lazy', 'eager' ), true ) ) {
2189                        /*
2190                         * If the filter changed the loading attribute to "lazy" when a fetchpriority attribute
2191                         * with value "high" is already present, trigger a warning since those two attribute
2192                         * values should be mutually exclusive.
2193                         *
2194                         * The same warning is present in `wp_get_loading_optimization_attributes()`, and here it
2195                         * is only intended for the specific scenario where the above filtered caused the problem.
2196                         */
2197                        if ( isset( $optimization_attrs['fetchpriority'] ) && 'high' === $optimization_attrs['fetchpriority'] &&
2198                                ( isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false ) !== $filtered_loading_attr &&
2199                                'lazy' === $filtered_loading_attr
2200                        ) {
2201                                _doing_it_wrong(
2202                                        __FUNCTION__,
2203                                        __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ),
2204                                        '6.3.0'
2205                                );
2206                        }
2207
2208                        // The filtered value will still be respected.
2209                        $optimization_attrs['loading'] = $filtered_loading_attr;
2210                }
2211
2212                if ( ! empty( $optimization_attrs['loading'] ) ) {
2213                        $image = str_replace( '<img', '<img loading="' . esc_attr( $optimization_attrs['loading'] ) . '"', $image );
2214                }
2215        }
2216
2217        if ( empty( $fetchpriority_val ) && ! empty( $optimization_attrs['fetchpriority'] ) ) {
2218                $image = str_replace( '<img', '<img fetchpriority="' . esc_attr( $optimization_attrs['fetchpriority'] ) . '"', $image );
2219        }
2220
2221        return $image;
2222}
2223
2224/**
2225 * Adds `width` and `height` attributes to an `img` HTML tag.
2226 *
2227 * @since 5.5.0
2228 *
2229 * @param string $image         The HTML `img` tag where the attribute should be added.
2230 * @param string $context       Additional context to pass to the filters.
2231 * @param int    $attachment_id Image attachment ID.
2232 * @return string Converted 'img' element with 'width' and 'height' attributes added.
2233 */
2234function wp_img_tag_add_width_and_height_attr( $image, $context, $attachment_id ) {
2235        $image_src         = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
2236        list( $image_src ) = explode( '?', $image_src );
2237
2238        // Return early if we couldn't get the image source.
2239        if ( ! $image_src ) {
2240                return $image;
2241        }
2242
2243        /**
2244         * Filters whether to add the missing `width` and `height` HTML attributes to the img tag. Default `true`.
2245         *
2246         * Returning anything else than `true` will not add the attributes.
2247         *
2248         * @since 5.5.0
2249         *
2250         * @param bool   $value         The filtered value, defaults to `true`.
2251         * @param string $image         The HTML `img` tag where the attribute should be added.
2252         * @param string $context       Additional context about how the function was called or where the img tag is.
2253         * @param int    $attachment_id The image attachment ID.
2254         */
2255        $add = apply_filters( 'wp_img_tag_add_width_and_height_attr', true, $image, $context, $attachment_id );
2256
2257        if ( true === $add ) {
2258                $image_meta = wp_get_attachment_metadata( $attachment_id );
2259                $size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id );
2260
2261                if ( $size_array && $size_array[0] && $size_array[1] ) {
2262                        // If the width is enforced through style (e.g. in an inline image), calculate the dimension attributes.
2263                        $style_width = preg_match( '/style="width:\s*(\d+)px;"/', $image, $match_width ) ? (int) $match_width[1] : 0;
2264                        if ( $style_width ) {
2265                                $size_array[1] = (int) round( $size_array[1] * $style_width / $size_array[0] );
2266                                $size_array[0] = $style_width;
2267                        }
2268
2269                        $hw = trim( image_hwstring( $size_array[0], $size_array[1] ) );
2270                        return str_replace( '<img', "<img {$hw}", $image );
2271                }
2272        }
2273
2274        return $image;
2275}
2276
2277/**
2278 * Adds `srcset` and `sizes` attributes to an existing `img` HTML tag.
2279 *
2280 * @since 5.5.0
2281 *
2282 * @param string $image         The HTML `img` tag where the attribute should be added.
2283 * @param string $context       Additional context to pass to the filters.
2284 * @param int    $attachment_id Image attachment ID.
2285 * @return string Converted 'img' element with 'loading' attribute added.
2286 */
2287function wp_img_tag_add_srcset_and_sizes_attr( $image, $context, $attachment_id ) {
2288        /**
2289         * Filters whether to add the `srcset` and `sizes` HTML attributes to the img tag. Default `true`.
2290         *
2291         * Returning anything else than `true` will not add the attributes.
2292         *
2293         * @since 5.5.0
2294         *
2295         * @param bool   $value         The filtered value, defaults to `true`.
2296         * @param string $image         The HTML `img` tag where the attribute should be added.
2297         * @param string $context       Additional context about how the function was called or where the img tag is.
2298         * @param int    $attachment_id The image attachment ID.
2299         */
2300        $add = apply_filters( 'wp_img_tag_add_srcset_and_sizes_attr', true, $image, $context, $attachment_id );
2301
2302        if ( true === $add ) {
2303                $image_meta = wp_get_attachment_metadata( $attachment_id );
2304                return wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id );
2305        }
2306
2307        return $image;
2308}
2309
2310/**
2311 * Adds `loading` attribute to an `iframe` HTML tag.
2312 *
2313 * @since 5.7.0
2314 *
2315 * @param string $iframe  The HTML `iframe` tag where the attribute should be added.
2316 * @param string $context Additional context to pass to the filters.
2317 * @return string Converted `iframe` tag with `loading` attribute added.
2318 */
2319function wp_iframe_tag_add_loading_attr( $iframe, $context ) {
2320        /*
2321         * Get loading attribute value to use. This must occur before the conditional check below so that even iframes that
2322         * are ineligible for being lazy-loaded are considered.
2323         */
2324        $optimization_attrs = wp_get_loading_optimization_attributes(
2325                'iframe',
2326                array(
2327                        /*
2328                         * The concrete values for width and height are not important here for now
2329                         * since fetchpriority is not yet supported for iframes.
2330                         * TODO: Use WP_HTML_Tag_Processor to extract actual values once support is
2331                         * added.
2332                         */
2333                        'width'   => str_contains( $iframe, ' width="' ) ? 100 : null,
2334                        'height'  => str_contains( $iframe, ' height="' ) ? 100 : null,
2335                        // This function is never called when a 'loading' attribute is already present.
2336                        'loading' => null,
2337                ),
2338                $context
2339        );
2340
2341        // Iframes should have source and dimension attributes for the `loading` attribute to be added.
2342        if ( ! str_contains( $iframe, ' src="' ) || ! str_contains( $iframe, ' width="' ) || ! str_contains( $iframe, ' height="' ) ) {
2343                return $iframe;
2344        }
2345
2346        $value = isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false;
2347
2348        /**
2349         * Filters the `loading` attribute value to add to an iframe. Default `lazy`.
2350         *
2351         * Returning `false` or an empty string will not add the attribute.
2352         * Returning `true` will add the default value.
2353         *
2354         * @since 5.7.0
2355         *
2356         * @param string|bool $value   The `loading` attribute value. Returning a falsey value will result in
2357         *                             the attribute being omitted for the iframe.
2358         * @param string      $iframe  The HTML `iframe` tag to be filtered.
2359         * @param string      $context Additional context about how the function was called or where the iframe tag is.
2360         */
2361        $value = apply_filters( 'wp_iframe_tag_add_loading_attr', $value, $iframe, $context );
2362
2363        if ( $value ) {
2364                if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
2365                        $value = 'lazy';
2366                }
2367
2368                return str_replace( '<iframe', '<iframe loading="' . esc_attr( $value ) . '"', $iframe );
2369        }
2370
2371        return $iframe;
2372}
2373
2374/**
2375 * Adds a 'wp-post-image' class to post thumbnails. Internal use only.
2376 *
2377 * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'}
2378 * action hooks to dynamically add/remove itself so as to only filter post thumbnails.
2379 *
2380 * @ignore
2381 * @since 2.9.0
2382 *
2383 * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
2384 * @return string[] Modified array of attributes including the new 'wp-post-image' class.
2385 */
2386function _wp_post_thumbnail_class_filter( $attr ) {
2387        $attr['class'] .= ' wp-post-image';
2388        return $attr;
2389}
2390
2391/**
2392 * Adds '_wp_post_thumbnail_class_filter' callback to the 'wp_get_attachment_image_attributes'
2393 * filter hook. Internal use only.
2394 *
2395 * @ignore
2396 * @since 2.9.0
2397 *
2398 * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
2399 */
2400function _wp_post_thumbnail_class_filter_add( $attr ) {
2401        add_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
2402}
2403
2404/**
2405 * Removes the '_wp_post_thumbnail_class_filter' callback from the 'wp_get_attachment_image_attributes'
2406 * filter hook. Internal use only.
2407 *
2408 * @ignore
2409 * @since 2.9.0
2410 *
2411 * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
2412 */
2413function _wp_post_thumbnail_class_filter_remove( $attr ) {
2414        remove_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
2415}
2416
2417/**
2418 * Overrides the context used in {@see wp_get_attachment_image()}. Internal use only.
2419 *
2420 * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'}
2421 * action hooks to dynamically add/remove itself so as to only filter post thumbnails.
2422 *
2423 * @ignore
2424 * @since 6.3.0
2425 * @access private
2426 *
2427 * @param string $context The context for rendering an attachment image.
2428 * @return string Modified context set to 'the_post_thumbnail'.
2429 */
2430function _wp_post_thumbnail_context_filter( $context ) {
2431        return 'the_post_thumbnail';
2432}
2433
2434/**
2435 * Adds the '_wp_post_thumbnail_context_filter' callback to the 'wp_get_attachment_image_context'
2436 * filter hook. Internal use only.
2437 *
2438 * @ignore
2439 * @since 6.3.0
2440 * @access private
2441 */
2442function _wp_post_thumbnail_context_filter_add() {
2443        add_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' );
2444}
2445
2446/**
2447 * Removes the '_wp_post_thumbnail_context_filter' callback from the 'wp_get_attachment_image_context'
2448 * filter hook. Internal use only.
2449 *
2450 * @ignore
2451 * @since 6.3.0
2452 * @access private
2453 */
2454function _wp_post_thumbnail_context_filter_remove() {
2455        remove_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' );
2456}
2457
2458add_shortcode( 'wp_caption', 'img_caption_shortcode' );
2459add_shortcode( 'caption', 'img_caption_shortcode' );
2460
2461/**
2462 * Builds the Caption shortcode output.
2463 *
2464 * Allows a plugin to replace the content that would otherwise be returned. The
2465 * filter is {@see 'img_caption_shortcode'} and passes an empty string, the attr
2466 * parameter and the content parameter values.
2467 *
2468 * The supported attributes for the shortcode are 'id', 'caption_id', 'align',
2469 * 'width', 'caption', and 'class'.
2470 *
2471 * @since 2.6.0
2472 * @since 3.9.0 The `class` attribute was added.
2473 * @since 5.1.0 The `caption_id` attribute was added.
2474 * @since 5.9.0 The `$content` parameter default value changed from `null` to `''`.
2475 *
2476 * @param array  $attr {
2477 *     Attributes of the caption shortcode.
2478 *
2479 *     @type string $id         ID of the image and caption container element, i.e. `<figure>` or `<div>`.
2480 *     @type string $caption_id ID of the caption element, i.e. `<figcaption>` or `<p>`.
2481 *     @type string $align      Class name that aligns the caption. Default 'alignnone'. Accepts 'alignleft',
2482 *                              'aligncenter', alignright', 'alignnone'.
2483 *     @type int    $width      The width of the caption, in pixels.
2484 *     @type string $caption    The caption text.
2485 *     @type string $class      Additional class name(s) added to the caption container.
2486 * }
2487 * @param string $content Optional. Shortcode content. Default empty string.
2488 * @return string HTML content to display the caption.
2489 */
2490function img_caption_shortcode( $attr, $content = '' ) {
2491        // New-style shortcode with the caption inside the shortcode with the link and image tags.
2492        if ( ! isset( $attr['caption'] ) ) {
2493                if ( preg_match( '#((?:<a [^>]+>\s*)?<img [^>]+>(?:\s*</a>)?)(.*)#is', $content, $matches ) ) {
2494                        $content         = $matches[1];
2495                        $attr['caption'] = trim( $matches[2] );
2496                }
2497        } elseif ( str_contains( $attr['caption'], '<' ) ) {
2498                $attr['caption'] = wp_kses( $attr['caption'], 'post' );
2499        }
2500
2501        /**
2502         * Filters the default caption shortcode output.
2503         *
2504         * If the filtered output isn't empty, it will be used instead of generating
2505         * the default caption template.
2506         *
2507         * @since 2.6.0
2508         *
2509         * @see img_caption_shortcode()
2510         *
2511         * @param string $output  The caption output. Default empty.
2512         * @param array  $attr    Attributes of the caption shortcode.
2513         * @param string $content The image element, possibly wrapped in a hyperlink.
2514         */
2515        $output = apply_filters( 'img_caption_shortcode', '', $attr, $content );
2516
2517        if ( ! empty( $output ) ) {
2518                return $output;
2519        }
2520
2521        $atts = shortcode_atts(
2522                array(
2523                        'id'         => '',
2524                        'caption_id' => '',
2525                        'align'      => 'alignnone',
2526                        'width'      => '',
2527                        'caption'    => '',
2528                        'class'      => '',
2529                ),
2530                $attr,
2531                'caption'
2532        );
2533
2534        $atts['width'] = (int) $atts['width'];
2535
2536        if ( $atts['width'] < 1 || empty( $atts['caption'] ) ) {
2537                return $content;
2538        }
2539
2540        $id          = '';
2541        $caption_id  = '';
2542        $describedby = '';
2543
2544        if ( $atts['id'] ) {
2545                $atts['id'] = sanitize_html_class( $atts['id'] );
2546                $id         = 'id="' . esc_attr( $atts['id'] ) . '" ';
2547        }
2548
2549        if ( $atts['caption_id'] ) {
2550                $atts['caption_id'] = sanitize_html_class( $atts['caption_id'] );
2551        } elseif ( $atts['id'] ) {
2552                $atts['caption_id'] = 'caption-' . str_replace( '_', '-', $atts['id'] );
2553        }
2554
2555        if ( $atts['caption_id'] ) {
2556                $caption_id  = 'id="' . esc_attr( $atts['caption_id'] ) . '" ';
2557                $describedby = 'aria-describedby="' . esc_attr( $atts['caption_id'] ) . '" ';
2558        }
2559
2560        $class = trim( 'wp-caption ' . $atts['align'] . ' ' . $atts['class'] );
2561
2562        $html5 = current_theme_supports( 'html5', 'caption' );
2563        // HTML5 captions never added the extra 10px to the image width.
2564        $width = $html5 ? $atts['width'] : ( 10 + $atts['width'] );
2565
2566        /**
2567         * Filters the width of an image's caption.
2568         *
2569         * By default, the caption is 10 pixels greater than the width of the image,
2570         * to prevent post content from running up against a floated image.
2571         *
2572         * @since 3.7.0
2573         *
2574         * @see img_caption_shortcode()
2575         *
2576         * @param int    $width    Width of the caption in pixels. To remove this inline style,
2577         *                         return zero.
2578         * @param array  $atts     Attributes of the caption shortcode.
2579         * @param string $content  The image element, possibly wrapped in a hyperlink.
2580         */
2581        $caption_width = apply_filters( 'img_caption_shortcode_width', $width, $atts, $content );
2582
2583        $style = '';
2584
2585        if ( $caption_width ) {
2586                $style = 'style="width: ' . (int) $caption_width . 'px" ';
2587        }
2588
2589        if ( $html5 ) {
2590                $html = sprintf(
2591                        '<figure %s%s%sclass="%s">%s%s</figure>',
2592                        $id,
2593                        $describedby,
2594                        $style,
2595                        esc_attr( $class ),
2596                        do_shortcode( $content ),
2597                        sprintf(
2598                                '<figcaption %sclass="wp-caption-text">%s</figcaption>',
2599                                $caption_id,
2600                                $atts['caption']
2601                        )
2602                );
2603        } else {
2604                $html = sprintf(
2605                        '<div %s%sclass="%s">%s%s</div>',
2606                        $id,
2607                        $style,
2608                        esc_attr( $class ),
2609                        str_replace( '<img ', '<img ' . $describedby, do_shortcode( $content ) ),
2610                        sprintf(
2611                                '<p %sclass="wp-caption-text">%s</p>',
2612                                $caption_id,
2613                                $atts['caption']
2614                        )
2615                );
2616        }
2617
2618        return $html;
2619}
2620
2621add_shortcode( 'gallery', 'gallery_shortcode' );
2622
2623/**
2624 * Builds the Gallery shortcode output.
2625 *
2626 * This implements the functionality of the Gallery Shortcode for displaying
2627 * WordPress images on a post.
2628 *
2629 * @since 2.5.0
2630 * @since 2.8.0 Added the `$attr` parameter to set the shortcode output. New attributes included
2631 *              such as `size`, `itemtag`, `icontag`, `captiontag`, and columns. Changed markup from
2632 *              `div` tags to `dl`, `dt` and `dd` tags. Support more than one gallery on the
2633 *              same page.
2634 * @since 2.9.0 Added support for `include` and `exclude` to shortcode.
2635 * @since 3.5.0 Use get_post() instead of global `$post`. Handle mapping of `ids` to `include`
2636 *              and `orderby`.
2637 * @since 3.6.0 Added validation for tags used in gallery shortcode. Add orientation information to items.
2638 * @since 3.7.0 Introduced the `link` attribute.
2639 * @since 3.9.0 `html5` gallery support, accepting 'itemtag', 'icontag', and 'captiontag' attributes.
2640 * @since 4.0.0 Removed use of `extract()`.
2641 * @since 4.1.0 Added attribute to `wp_get_attachment_link()` to output `aria-describedby`.
2642 * @since 4.2.0 Passed the shortcode instance ID to `post_gallery` and `post_playlist` filters.
2643 * @since 4.6.0 Standardized filter docs to match documentation standards for PHP.
2644 * @since 5.1.0 Code cleanup for WPCS 1.0.0 coding standards.
2645 * @since 5.3.0 Saved progress of intermediate image creation after upload.
2646 * @since 5.5.0 Ensured that galleries can be output as a list of links in feeds.
2647 * @since 5.6.0 Replaced order-style PHP type conversion functions with typecasts. Fix logic for
2648 *              an array of image dimensions.
2649 *
2650 * @param array $attr {
2651 *     Attributes of the gallery shortcode.
2652 *
2653 *     @type string       $order      Order of the images in the gallery. Default 'ASC'. Accepts 'ASC', 'DESC'.
2654 *     @type string       $orderby    The field to use when ordering the images. Default 'menu_order ID'.
2655 *                                    Accepts any valid SQL ORDERBY statement.
2656 *     @type int          $id         Post ID.
2657 *     @type string       $itemtag    HTML tag to use for each image in the gallery.
2658 *                                    Default 'dl', or 'figure' when the theme registers HTML5 gallery support.
2659 *     @type string       $icontag    HTML tag to use for each image's icon.
2660 *                                    Default 'dt', or 'div' when the theme registers HTML5 gallery support.
2661 *     @type string       $captiontag HTML tag to use for each image's caption.
2662 *                                    Default 'dd', or 'figcaption' when the theme registers HTML5 gallery support.
2663 *     @type int          $columns    Number of columns of images to display. Default 3.
2664 *     @type string|int[] $size       Size of the images to display. Accepts any registered image size name, or an array
2665 *                                    of width and height values in pixels (in that order). Default 'thumbnail'.
2666 *     @type string       $ids        A comma-separated list of IDs of attachments to display. Default empty.
2667 *     @type string       $include    A comma-separated list of IDs of attachments to include. Default empty.
2668 *     @type string       $exclude    A comma-separated list of IDs of attachments to exclude. Default empty.
2669 *     @type string       $link       What to link each image to. Default empty (links to the attachment page).
2670 *                                    Accepts 'file', 'none'.
2671 * }
2672 * @return string HTML content to display gallery.
2673 */
2674function gallery_shortcode( $attr ) {
2675        $post = get_post();
2676
2677        static $instance = 0;
2678        ++$instance;
2679
2680        if ( ! empty( $attr['ids'] ) ) {
2681                // 'ids' is explicitly ordered, unless you specify otherwise.
2682                if ( empty( $attr['orderby'] ) ) {
2683                        $attr['orderby'] = 'post__in';
2684                }
2685                $attr['include'] = $attr['ids'];
2686        }
2687
2688        /**
2689         * Filters the default gallery shortcode output.
2690         *
2691         * If the filtered output isn't empty, it will be used instead of generating
2692         * the default gallery template.
2693         *
2694         * @since 2.5.0
2695         * @since 4.2.0 The `$instance` parameter was added.
2696         *
2697         * @see gallery_shortcode()
2698         *
2699         * @param string $output   The gallery output. Default empty.
2700         * @param array  $attr     Attributes of the gallery shortcode.
2701         * @param int    $instance Unique numeric ID of this gallery shortcode instance.
2702         */
2703        $output = apply_filters( 'post_gallery', '', $attr, $instance );
2704
2705        if ( ! empty( $output ) ) {
2706                return $output;
2707        }
2708
2709        $html5 = current_theme_supports( 'html5', 'gallery' );
2710        $atts  = shortcode_atts(
2711                array(
2712                        'order'      => 'ASC',
2713                        'orderby'    => 'menu_order ID',
2714                        'id'         => $post ? $post->ID : 0,
2715                        'itemtag'    => $html5 ? 'figure' : 'dl',
2716                        'icontag'    => $html5 ? 'div' : 'dt',
2717                        'captiontag' => $html5 ? 'figcaption' : 'dd',
2718                        'columns'    => 3,
2719                        'size'       => 'thumbnail',
2720                        'include'    => '',
2721                        'exclude'    => '',
2722                        'link'       => '',
2723                ),
2724                $attr,
2725                'gallery'
2726        );
2727
2728        $id = (int) $atts['id'];
2729
2730        if ( ! empty( $atts['include'] ) ) {
2731                $_attachments = get_posts(
2732                        array(
2733                                'include'        => $atts['include'],
2734                                'post_status'    => 'inherit',
2735                                'post_type'      => 'attachment',
2736                                'post_mime_type' => 'image',
2737                                'order'          => $atts['order'],
2738                                'orderby'        => $atts['orderby'],
2739                        )
2740                );
2741
2742                $attachments = array();
2743                foreach ( $_attachments as $key => $val ) {
2744                        $attachments[ $val->ID ] = $_attachments[ $key ];
2745                }
2746        } elseif ( ! empty( $atts['exclude'] ) ) {
2747                $post_parent_id = $id;
2748                $attachments    = get_children(
2749                        array(
2750                                'post_parent'    => $id,
2751                                'exclude'        => $atts['exclude'],
2752                                'post_status'    => 'inherit',
2753                                'post_type'      => 'attachment',
2754                                'post_mime_type' => 'image',
2755                                'order'          => $atts['order'],
2756                                'orderby'        => $atts['orderby'],
2757                        )
2758                );
2759        } else {
2760                $post_parent_id = $id;
2761                $attachments    = get_children(
2762                        array(
2763                                'post_parent'    => $id,
2764                                'post_status'    => 'inherit',
2765                                'post_type'      => 'attachment',
2766                                'post_mime_type' => 'image',
2767                                'order'          => $atts['order'],
2768                                'orderby'        => $atts['orderby'],
2769                        )
2770                );
2771        }
2772
2773        if ( ! empty( $post_parent_id ) ) {
2774                $post_parent = get_post( $post_parent_id );
2775
2776                // Terminate the shortcode execution if the user cannot read the post or it is password-protected.
2777                if ( ! is_post_publicly_viewable( $post_parent->ID ) && ! current_user_can( 'read_post', $post_parent->ID )
2778                        || post_password_required( $post_parent )
2779                ) {
2780                        return '';
2781                }
2782        }
2783
2784        if ( empty( $attachments ) ) {
2785                return '';
2786        }
2787
2788        if ( is_feed() ) {
2789                $output = "\n";
2790                foreach ( $attachments as $att_id => $attachment ) {
2791                        if ( ! empty( $atts['link'] ) ) {
2792                                if ( 'none' === $atts['link'] ) {
2793                                        $output .= wp_get_attachment_image( $att_id, $atts['size'], false, $attr );
2794                                } else {
2795                                        $output .= wp_get_attachment_link( $att_id, $atts['size'], false );
2796                                }
2797                        } else {
2798                                $output .= wp_get_attachment_link( $att_id, $atts['size'], true );
2799                        }
2800                        $output .= "\n";
2801                }
2802                return $output;
2803        }
2804
2805        $itemtag    = tag_escape( $atts['itemtag'] );
2806        $captiontag = tag_escape( $atts['captiontag'] );
2807        $icontag    = tag_escape( $atts['icontag'] );
2808        $valid_tags = wp_kses_allowed_html( 'post' );
2809        if ( ! isset( $valid_tags[ $itemtag ] ) ) {
2810                $itemtag = 'dl';
2811        }
2812        if ( ! isset( $valid_tags[ $captiontag ] ) ) {
2813                $captiontag = 'dd';
2814        }
2815        if ( ! isset( $valid_tags[ $icontag ] ) ) {
2816                $icontag = 'dt';
2817        }
2818
2819        $columns   = (int) $atts['columns'];
2820        $itemwidth = $columns > 0 ? floor( 100 / $columns ) : 100;
2821        $float     = is_rtl() ? 'right' : 'left';
2822
2823        $selector = "gallery-{$instance}";
2824
2825        $gallery_style = '';
2826
2827        /**
2828         * Filters whether to print default gallery styles.
2829         *
2830         * @since 3.1.0
2831         *
2832         * @param bool $print Whether to print default gallery styles.
2833         *                    Defaults to false if the theme supports HTML5 galleries.
2834         *                    Otherwise, defaults to true.
2835         */
2836        if ( apply_filters( 'use_default_gallery_style', ! $html5 ) ) {
2837                $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
2838
2839                $gallery_style = "
2840                <style{$type_attr}>
2841                        #{$selector} {
2842                                margin: auto;
2843                        }
2844                        #{$selector} .gallery-item {
2845                                float: {$float};
2846                                margin-top: 10px;
2847                                text-align: center;
2848                                width: {$itemwidth}%;
2849                        }
2850                        #{$selector} img {
2851                                border: 2px solid #cfcfcf;
2852                        }
2853                        #{$selector} .gallery-caption {
2854                                margin-left: 0;
2855                        }
2856                        /* see gallery_shortcode() in wp-includes/media.php */
2857                </style>\n\t\t";
2858        }
2859
2860        $size_class  = sanitize_html_class( is_array( $atts['size'] ) ? implode( 'x', $atts['size'] ) : $atts['size'] );
2861        $gallery_div = "<div id='$selector' class='gallery galleryid-{$id} gallery-columns-{$columns} gallery-size-{$size_class}'>";
2862
2863        /**
2864         * Filters the default gallery shortcode CSS styles.
2865         *
2866         * @since 2.5.0
2867         *
2868         * @param string $gallery_style Default CSS styles and opening HTML div container
2869         *                              for the gallery shortcode output.
2870         */
2871        $output = apply_filters( 'gallery_style', $gallery_style . $gallery_div );
2872
2873        $i = 0;
2874
2875        foreach ( $attachments as $id => $attachment ) {
2876
2877                $attr = ( trim( $attachment->post_excerpt ) ) ? array( 'aria-describedby' => "$selector-$id" ) : '';
2878
2879                if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) {
2880                        $image_output = wp_get_attachment_link( $id, $atts['size'], false, false, false, $attr );
2881                } elseif ( ! empty( $atts['link'] ) && 'none' === $atts['link'] ) {
2882                        $image_output = wp_get_attachment_image( $id, $atts['size'], false, $attr );
2883                } else {
2884                        $image_output = wp_get_attachment_link( $id, $atts['size'], true, false, false, $attr );
2885                }
2886
2887                $image_meta = wp_get_attachment_metadata( $id );
2888
2889                $orientation = '';
2890
2891                if ( isset( $image_meta['height'], $image_meta['width'] ) ) {
2892                        $orientation = ( $image_meta['height'] > $image_meta['width'] ) ? 'portrait' : 'landscape';
2893                }
2894
2895                $output .= "<{$itemtag} class='gallery-item'>";
2896                $output .= "
2897                        <{$icontag} class='gallery-icon {$orientation}'>
2898                                $image_output
2899                        </{$icontag}>";
2900
2901                if ( $captiontag && trim( $attachment->post_excerpt ) ) {
2902                        $output .= "
2903                                <{$captiontag} class='wp-caption-text gallery-caption' id='$selector-$id'>
2904                                " . wptexturize( $attachment->post_excerpt ) . "
2905                                </{$captiontag}>";
2906                }
2907
2908                $output .= "</{$itemtag}>";
2909
2910                if ( ! $html5 && $columns > 0 && 0 === ++$i % $columns ) {
2911                        $output .= '<br style="clear: both" />';
2912                }
2913        }
2914
2915        if ( ! $html5 && $columns > 0 && 0 !== $i % $columns ) {
2916                $output .= "
2917                        <br style='clear: both' />";
2918        }
2919
2920        $output .= "
2921                </div>\n";
2922
2923        return $output;
2924}
2925
2926/**
2927 * Outputs the templates used by playlists.
2928 *
2929 * @since 3.9.0
2930 */
2931function wp_underscore_playlist_templates() {
2932        ?>
2933<script type="text/html" id="tmpl-wp-playlist-current-item">
2934        <# if ( data.thumb && data.thumb.src ) { #>
2935                <img src="{{ data.thumb.src }}" alt="" />
2936        <# } #>
2937        <div class="wp-playlist-caption">
2938                <span class="wp-playlist-item-meta wp-playlist-item-title">
2939                        <# if ( data.meta.album || data.meta.artist ) { #>
2940                                <?php
2941                                /* translators: %s: Playlist item title. */
2942                                printf( _x( '&#8220;%s&#8221;', 'playlist item title' ), '{{ data.title }}' );
2943                                ?>
2944                        <# } else { #>
2945                                {{ data.title }}
2946                        <# } #>
2947                </span>
2948                <# if ( data.meta.album ) { #><span class="wp-playlist-item-meta wp-playlist-item-album">{{ data.meta.album }}</span><# } #>
2949                <# if ( data.meta.artist ) { #><span class="wp-playlist-item-meta wp-playlist-item-artist">{{ data.meta.artist }}</span><# } #>
2950        </div>
2951</script>
2952<script type="text/html" id="tmpl-wp-playlist-item">
2953        <div class="wp-playlist-item">
2954                <a class="wp-playlist-caption" href="{{ data.src }}">
2955                        {{ data.index ? ( data.index + '. ' ) : '' }}
2956                        <# if ( data.caption ) { #>
2957                                {{ data.caption }}
2958                        <# } else { #>
2959                                <# if ( data.artists && data.meta.artist ) { #>
2960                                        <span class="wp-playlist-item-title">
2961                                                <?php
2962                                                /* translators: %s: Playlist item title. */
2963                                                printf( _x( '&#8220;%s&#8221;', 'playlist item title' ), '{{{ data.title }}}' );
2964                                                ?>
2965                                        </span>
2966                                        <span class="wp-playlist-item-artist"> &mdash; {{ data.meta.artist }}</span>
2967                                <# } else { #>
2968                                        <span class="wp-playlist-item-title">{{{ data.title }}}</span>
2969                                <# } #>
2970                        <# } #>
2971                </a>
2972                <# if ( data.meta.length_formatted ) { #>
2973                <div class="wp-playlist-item-length">{{ data.meta.length_formatted }}</div>
2974                <# } #>
2975        </div>
2976</script>
2977        <?php
2978}
2979
2980/**
2981 * Outputs and enqueues default scripts and styles for playlists.
2982 *
2983 * @since 3.9.0
2984 *
2985 * @param string $type Type of playlist. Accepts 'audio' or 'video'.
2986 */
2987function wp_playlist_scripts( $type ) {
2988        wp_enqueue_style( 'wp-mediaelement' );
2989        wp_enqueue_script( 'wp-playlist' );
2990        ?>
2991<!--[if lt IE 9]><script>document.createElement('<?php echo esc_js( $type ); ?>');</script><![endif]-->
2992        <?php
2993        add_action( 'wp_footer', 'wp_underscore_playlist_templates', 0 );
2994        add_action( 'admin_footer', 'wp_underscore_playlist_templates', 0 );
2995}
2996
2997/**
2998 * Builds the Playlist shortcode output.
2999 *
3000 * This implements the functionality of the playlist shortcode for displaying
3001 * a collection of WordPress audio or video files in a post.
3002 *
3003 * @since 3.9.0
3004 *
3005 * @global int $content_width
3006 *
3007 * @param array $attr {
3008 *     Array of default playlist attributes.
3009 *
3010 *     @type string  $type         Type of playlist to display. Accepts 'audio' or 'video'. Default 'audio'.
3011 *     @type string  $order        Designates ascending or descending order of items in the playlist.
3012 *                                 Accepts 'ASC', 'DESC'. Default 'ASC'.
3013 *     @type string  $orderby      Any column, or columns, to sort the playlist. If $ids are
3014 *                                 passed, this defaults to the order of the $ids array ('post__in').
3015 *                                 Otherwise default is 'menu_order ID'.
3016 *     @type int     $id           If an explicit $ids array is not present, this parameter
3017 *                                 will determine which attachments are used for the playlist.
3018 *                                 Default is the current post ID.
3019 *     @type array   $ids          Create a playlist out of these explicit attachment IDs. If empty,
3020 *                                 a playlist will be created from all $type attachments of $id.
3021 *                                 Default empty.
3022 *     @type array   $exclude      List of specific attachment IDs to exclude from the playlist. Default empty.
3023 *     @type string  $style        Playlist style to use. Accepts 'light' or 'dark'. Default 'light'.
3024 *     @type bool    $tracklist    Whether to show or hide the playlist. Default true.
3025 *     @type bool    $tracknumbers Whether to show or hide the numbers next to entries in the playlist. Default true.
3026 *     @type bool    $images       Show or hide the video or audio thumbnail (Featured Image/post
3027 *                                 thumbnail). Default true.
3028 *     @type bool    $artists      Whether to show or hide artist name in the playlist. Default true.
3029 * }
3030 *
3031 * @return string Playlist output. Empty string if the passed type is unsupported.
3032 */
3033function wp_playlist_shortcode( $attr ) {
3034        global $content_width;
3035        $post = get_post();
3036
3037        static $instance = 0;
3038        ++$instance;
3039
3040        if ( ! empty( $attr['ids'] ) ) {
3041                // 'ids' is explicitly ordered, unless you specify otherwise.
3042                if ( empty( $attr['orderby'] ) ) {
3043                        $attr['orderby'] = 'post__in';
3044                }
3045                $attr['include'] = $attr['ids'];
3046        }
3047
3048        /**
3049         * Filters the playlist output.
3050         *
3051         * Returning a non-empty value from the filter will short-circuit generation
3052         * of the default playlist output, returning the passed value instead.
3053         *
3054         * @since 3.9.0
3055         * @since 4.2.0 The `$instance` parameter was added.
3056         *
3057         * @param string $output   Playlist output. Default empty.
3058         * @param array  $attr     An array of shortcode attributes.
3059         * @param int    $instance Unique numeric ID of this playlist shortcode instance.
3060         */
3061        $output = apply_filters( 'post_playlist', '', $attr, $instance );
3062
3063        if ( ! empty( $output ) ) {
3064                return $output;
3065        }
3066
3067        $atts = shortcode_atts(
3068                array(
3069                        'type'         => 'audio',
3070                        'order'        => 'ASC',
3071                        'orderby'      => 'menu_order ID',
3072                        'id'           => $post ? $post->ID : 0,
3073                        'include'      => '',
3074                        'exclude'      => '',
3075                        'style'        => 'light',
3076                        'tracklist'    => true,
3077                        'tracknumbers' => true,
3078                        'images'       => true,
3079                        'artists'      => true,
3080                ),
3081                $attr,
3082                'playlist'
3083        );
3084
3085        $id = (int) $atts['id'];
3086
3087        if ( 'audio' !== $atts['type'] ) {
3088                $atts['type'] = 'video';
3089        }
3090
3091        $args = array(
3092                'post_status'    => 'inherit',
3093                'post_type'      => 'attachment',
3094                'post_mime_type' => $atts['type'],
3095                'order'          => $atts['order'],
3096                'orderby'        => $atts['orderby'],
3097        );
3098
3099        if ( ! empty( $atts['include'] ) ) {
3100                $args['include'] = $atts['include'];
3101                $_attachments    = get_posts( $args );
3102
3103                $attachments = array();
3104                foreach ( $_attachments as $key => $val ) {
3105                        $attachments[ $val->ID ] = $_attachments[ $key ];
3106                }
3107        } elseif ( ! empty( $atts['exclude'] ) ) {
3108                $args['post_parent'] = $id;
3109                $args['exclude']     = $atts['exclude'];
3110                $attachments         = get_children( $args );
3111        } else {
3112                $args['post_parent'] = $id;
3113                $attachments         = get_children( $args );
3114        }
3115
3116        if ( ! empty( $args['post_parent'] ) ) {
3117                $post_parent = get_post( $id );
3118
3119                // Terminate the shortcode execution if the user cannot read the post or it is password-protected.
3120                if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) {
3121                        return '';
3122                }
3123        }
3124
3125        if ( empty( $attachments ) ) {
3126                return '';
3127        }
3128
3129        if ( is_feed() ) {
3130                $output = "\n";
3131                foreach ( $attachments as $att_id => $attachment ) {
3132                        $output .= wp_get_attachment_link( $att_id ) . "\n";
3133                }
3134                return $output;
3135        }
3136
3137        $outer = 22; // Default padding and border of wrapper.
3138
3139        $default_width  = 640;
3140        $default_height = 360;
3141
3142        $theme_width  = empty( $content_width ) ? $default_width : ( $content_width - $outer );
3143        $theme_height = empty( $content_width ) ? $default_height : round( ( $default_height * $theme_width ) / $default_width );
3144
3145        $data = array(
3146                'type'         => $atts['type'],
3147                // Don't pass strings to JSON, will be truthy in JS.
3148                'tracklist'    => wp_validate_boolean( $atts['tracklist'] ),
3149                'tracknumbers' => wp_validate_boolean( $atts['tracknumbers'] ),
3150                'images'       => wp_validate_boolean( $atts['images'] ),
3151                'artists'      => wp_validate_boolean( $atts['artists'] ),
3152        );
3153
3154        $tracks = array();
3155        foreach ( $attachments as $attachment ) {
3156                $url   = wp_get_attachment_url( $attachment->ID );
3157                $ftype = wp_check_filetype( $url, wp_get_mime_types() );
3158                $track = array(
3159                        'src'         => $url,
3160                        'type'        => $ftype['type'],
3161                        'title'       => $attachment->post_title,
3162                        'caption'     => $attachment->post_excerpt,
3163                        'description' => $attachment->post_content,
3164                );
3165
3166                $track['meta'] = array();
3167                $meta          = wp_get_attachment_metadata( $attachment->ID );
3168                if ( ! empty( $meta ) ) {
3169
3170                        foreach ( wp_get_attachment_id3_keys( $attachment ) as $key => $label ) {
3171                                if ( ! empty( $meta[ $key ] ) ) {
3172                                        $track['meta'][ $key ] = $meta[ $key ];
3173                                }
3174                        }
3175
3176                        if ( 'video' === $atts['type'] ) {
3177                                if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) {
3178                                        $width        = $meta['width'];
3179                                        $height       = $meta['height'];
3180                                        $theme_height = round( ( $height * $theme_width ) / $width );
3181                                } else {
3182                                        $width  = $default_width;
3183                                        $height = $default_height;
3184                                }
3185
3186                                $track['dimensions'] = array(
3187                                        'original' => compact( 'width', 'height' ),
3188                                        'resized'  => array(
3189                                                'width'  => $theme_width,
3190                                                'height' => $theme_height,
3191                                        ),
3192                                );
3193                        }
3194                }
3195
3196                if ( $atts['images'] ) {
3197                        $thumb_id = get_post_thumbnail_id( $attachment->ID );
3198                        if ( ! empty( $thumb_id ) ) {
3199                                list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'full' );
3200                                $track['image']               = compact( 'src', 'width', 'height' );
3201                                list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'thumbnail' );
3202                                $track['thumb']               = compact( 'src', 'width', 'height' );
3203                        } else {
3204                                $src            = wp_mime_type_icon( $attachment->ID, '.svg' );
3205                                $width          = 48;
3206                                $height         = 64;
3207                                $track['image'] = compact( 'src', 'width', 'height' );
3208                                $track['thumb'] = compact( 'src', 'width', 'height' );
3209                        }
3210                }
3211
3212                $tracks[] = $track;
3213        }
3214        $data['tracks'] = $tracks;
3215
3216        $safe_type  = esc_attr( $atts['type'] );
3217        $safe_style = esc_attr( $atts['style'] );
3218
3219        ob_start();
3220
3221        if ( 1 === $instance ) {
3222                /**
3223                 * Prints and enqueues playlist scripts, styles, and JavaScript templates.
3224                 *
3225                 * @since 3.9.0
3226                 *
3227                 * @param string $type  Type of playlist. Possible values are 'audio' or 'video'.
3228                 * @param string $style The 'theme' for the playlist. Core provides 'light' and 'dark'.
3229                 */
3230                do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] );
3231        }
3232        ?>
3233<div class="wp-playlist wp-<?php echo $safe_type; ?>-playlist wp-playlist-<?php echo $safe_style; ?>">
3234        <?php if ( 'audio' === $atts['type'] ) : ?>
3235                <div class="wp-playlist-current-item"></div>
3236        <?php endif; ?>
3237        <<?php echo $safe_type; ?> controls="controls" preload="none" width="<?php echo (int) $theme_width; ?>"
3238                <?php
3239                if ( 'video' === $safe_type ) {
3240                        echo ' height="', (int) $theme_height, '"';
3241                }
3242                ?>
3243        ></<?php echo $safe_type; ?>>
3244        <div class="wp-playlist-next"></div>
3245        <div class="wp-playlist-prev"></div>
3246        <noscript>
3247        <ol>
3248                <?php
3249                foreach ( $attachments as $att_id => $attachment ) {
3250                        printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) );
3251                }
3252                ?>
3253        </ol>
3254        </noscript>
3255        <script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data ); ?></script>
3256</div>
3257        <?php
3258        return ob_get_clean();
3259}
3260add_shortcode( 'playlist', 'wp_playlist_shortcode' );
3261
3262/**
3263 * Provides a No-JS Flash fallback as a last resort for audio / video.
3264 *
3265 * @since 3.6.0
3266 *
3267 * @param string $url The media element URL.
3268 * @return string Fallback HTML.
3269 */
3270function wp_mediaelement_fallback( $url ) {
3271        /**
3272         * Filters the MediaElement fallback output for no-JS.
3273         *
3274         * @since 3.6.0
3275         *
3276         * @param string $output Fallback output for no-JS.
3277         * @param string $url    Media file URL.
3278         */
3279        return apply_filters( 'wp_mediaelement_fallback', sprintf( '<a href="%1$s">%1$s</a>', esc_url( $url ) ), $url );
3280}
3281
3282/**
3283 * Returns a filtered list of supported audio formats.
3284 *
3285 * @since 3.6.0
3286 *
3287 * @return string[] Supported audio formats.
3288 */
3289function wp_get_audio_extensions() {
3290        /**
3291         * Filters the list of supported audio formats.
3292         *
3293         * @since 3.6.0
3294         *
3295         * @param string[] $extensions An array of supported audio formats. Defaults are
3296         *                            'mp3', 'ogg', 'flac', 'm4a', 'wav'.
3297         */
3298        return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'flac', 'm4a', 'wav' ) );
3299}
3300
3301/**
3302 * Returns useful keys to use to lookup data from an attachment's stored metadata.
3303 *
3304 * @since 3.9.0
3305 *
3306 * @param WP_Post $attachment The current attachment, provided for context.
3307 * @param string  $context    Optional. The context. Accepts 'edit', 'display'. Default 'display'.
3308 * @return string[] Key/value pairs of field keys to labels.
3309 */
3310function wp_get_attachment_id3_keys( $attachment, $context = 'display' ) {
3311        $fields = array(
3312                'artist' => __( 'Artist' ),
3313                'album'  => __( 'Album' ),
3314        );
3315
3316        if ( 'display' === $context ) {
3317                $fields['genre']            = __( 'Genre' );
3318                $fields['year']             = __( 'Year' );
3319                $fields['length_formatted'] = _x( 'Length', 'video or audio' );
3320        } elseif ( 'js' === $context ) {
3321                $fields['bitrate']      = __( 'Bitrate' );
3322                $fields['bitrate_mode'] = __( 'Bitrate Mode' );
3323        }
3324
3325        /**
3326         * Filters the editable list of keys to look up data from an attachment's metadata.
3327         *
3328         * @since 3.9.0
3329         *
3330         * @param array   $fields     Key/value pairs of field keys to labels.
3331         * @param WP_Post $attachment Attachment object.
3332         * @param string  $context    The context. Accepts 'edit', 'display'. Default 'display'.
3333         */
3334        return apply_filters( 'wp_get_attachment_id3_keys', $fields, $attachment, $context );
3335}
3336/**
3337 * Builds the Audio shortcode output.
3338 *
3339 * This implements the functionality of the Audio Shortcode for displaying
3340 * WordPress mp3s in a post.
3341 *
3342 * @since 3.6.0
3343 *
3344 * @param array  $attr {
3345 *     Attributes of the audio shortcode.
3346 *
3347 *     @type string $src      URL to the source of the audio file. Default empty.
3348 *     @type string $loop     The 'loop' attribute for the `<audio>` element. Default empty.
3349 *     @type string $autoplay The 'autoplay' attribute for the `<audio>` element. Default empty.
3350 *     @type string $preload  The 'preload' attribute for the `<audio>` element. Default 'none'.
3351 *     @type string $class    The 'class' attribute for the `<audio>` element. Default 'wp-audio-shortcode'.
3352 *     @type string $style    The 'style' attribute for the `<audio>` element. Default 'width: 100%;'.
3353 * }
3354 * @param string $content Shortcode content.
3355 * @return string|void HTML content to display audio.
3356 */
3357function wp_audio_shortcode( $attr, $content = '' ) {
3358        $post_id = get_post() ? get_the_ID() : 0;
3359
3360        static $instance = 0;
3361        ++$instance;
3362
3363        /**
3364         * Filters the default audio shortcode output.
3365         *
3366         * If the filtered output isn't empty, it will be used instead of generating the default audio template.
3367         *
3368         * @since 3.6.0
3369         *
3370         * @param string $html     Empty variable to be replaced with shortcode markup.
3371         * @param array  $attr     Attributes of the shortcode. See {@see wp_audio_shortcode()}.
3372         * @param string $content  Shortcode content.
3373         * @param int    $instance Unique numeric ID of this audio shortcode instance.
3374         */
3375        $override = apply_filters( 'wp_audio_shortcode_override', '', $attr, $content, $instance );
3376
3377        if ( '' !== $override ) {
3378                return $override;
3379        }
3380
3381        $audio = null;
3382
3383        $default_types = wp_get_audio_extensions();
3384        $defaults_atts = array(
3385                'src'      => '',
3386                'loop'     => '',
3387                'autoplay' => '',
3388                'preload'  => 'none',
3389                'class'    => 'wp-audio-shortcode',
3390                'style'    => 'width: 100%;',
3391        );
3392        foreach ( $default_types as $type ) {
3393                $defaults_atts[ $type ] = '';
3394        }
3395
3396        $atts = shortcode_atts( $defaults_atts, $attr, 'audio' );
3397
3398        $primary = false;
3399        if ( ! empty( $atts['src'] ) ) {
3400                $type = wp_check_filetype( $atts['src'], wp_get_mime_types() );
3401
3402                if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) {
3403                        return sprintf( '<a class="wp-embedded-audio" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
3404                }
3405
3406                $primary = true;
3407                array_unshift( $default_types, 'src' );
3408        } else {
3409                foreach ( $default_types as $ext ) {
3410                        if ( ! empty( $atts[ $ext ] ) ) {
3411                                $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );
3412
3413                                if ( strtolower( $type['ext'] ) === $ext ) {
3414                                        $primary = true;
3415                                }
3416                        }
3417                }
3418        }
3419
3420        if ( ! $primary ) {
3421                $audios = get_attached_media( 'audio', $post_id );
3422
3423                if ( empty( $audios ) ) {
3424                        return;
3425                }
3426
3427                $audio       = reset( $audios );
3428                $atts['src'] = wp_get_attachment_url( $audio->ID );
3429
3430                if ( empty( $atts['src'] ) ) {
3431                        return;
3432                }
3433
3434                array_unshift( $default_types, 'src' );
3435        }
3436
3437        /**
3438         * Filters the media library used for the audio shortcode.
3439         *
3440         * @since 3.6.0
3441         *
3442         * @param string $library Media library used for the audio shortcode.
3443         */
3444        $library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' );
3445
3446        if ( 'mediaelement' === $library && did_action( 'init' ) ) {
3447                wp_enqueue_style( 'wp-mediaelement' );
3448                wp_enqueue_script( 'wp-mediaelement' );
3449        }
3450
3451        /**
3452         * Filters the class attribute for the audio shortcode output container.
3453         *
3454         * @since 3.6.0
3455         * @since 4.9.0 The `$atts` parameter was added.
3456         *
3457         * @param string $class CSS class or list of space-separated classes.
3458         * @param array  $atts  Array of audio shortcode attributes.
3459         */
3460        $atts['class'] = apply_filters( 'wp_audio_shortcode_class', $atts['class'], $atts );
3461
3462        $html_atts = array(
3463                'class'    => $atts['class'],
3464                'id'       => sprintf( 'audio-%d-%d', $post_id, $instance ),
3465                'loop'     => wp_validate_boolean( $atts['loop'] ),
3466                'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
3467                'preload'  => $atts['preload'],
3468                'style'    => $atts['style'],
3469        );
3470
3471        // These ones should just be omitted altogether if they are blank.
3472        foreach ( array( 'loop', 'autoplay', 'preload' ) as $a ) {
3473                if ( empty( $html_atts[ $a ] ) ) {
3474                        unset( $html_atts[ $a ] );
3475                }
3476        }
3477
3478        $attr_strings = array();
3479
3480        foreach ( $html_atts as $k => $v ) {
3481                $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
3482        }
3483
3484        $html = '';
3485
3486        if ( 'mediaelement' === $library && 1 === $instance ) {
3487                $html .= "<!--[if lt IE 9]><script>document.createElement('audio');</script><![endif]-->\n";
3488        }
3489
3490        $html .= sprintf( '<audio %s controls="controls">', implode( ' ', $attr_strings ) );
3491
3492        $fileurl = '';
3493        $source  = '<source type="%s" src="%s" />';
3494
3495        foreach ( $default_types as $fallback ) {
3496                if ( ! empty( $atts[ $fallback ] ) ) {
3497                        if ( empty( $fileurl ) ) {
3498                                $fileurl = $atts[ $fallback ];
3499                        }
3500
3501                        $type  = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
3502                        $url   = add_query_arg( '_', $instance, $atts[ $fallback ] );
3503                        $html .= sprintf( $source, $type['type'], esc_url( $url ) );
3504                }
3505        }
3506
3507        if ( 'mediaelement' === $library ) {
3508                $html .= wp_mediaelement_fallback( $fileurl );
3509        }
3510
3511        $html .= '</audio>';
3512
3513        /**
3514         * Filters the audio shortcode output.
3515         *
3516         * @since 3.6.0
3517         *
3518         * @param string $html    Audio shortcode HTML output.
3519         * @param array  $atts    Array of audio shortcode attributes.
3520         * @param string $audio   Audio file.
3521         * @param int    $post_id Post ID.
3522         * @param string $library Media library used for the audio shortcode.
3523         */
3524        return apply_filters( 'wp_audio_shortcode', $html, $atts, $audio, $post_id, $library );
3525}
3526add_shortcode( 'audio', 'wp_audio_shortcode' );
3527
3528/**
3529 * Returns a filtered list of supported video formats.
3530 *
3531 * @since 3.6.0
3532 *
3533 * @return string[] List of supported video formats.
3534 */
3535function wp_get_video_extensions() {
3536        /**
3537         * Filters the list of supported video formats.
3538         *
3539         * @since 3.6.0
3540         *
3541         * @param string[] $extensions An array of supported video formats. Defaults are
3542         *                             'mp4', 'm4v', 'webm', 'ogv', 'flv'.
3543         */
3544        return apply_filters( 'wp_video_extensions', array( 'mp4', 'm4v', 'webm', 'ogv', 'flv' ) );
3545}
3546
3547/**
3548 * Builds the Video shortcode output.
3549 *
3550 * This implements the functionality of the Video Shortcode for displaying
3551 * WordPress mp4s in a post.
3552 *
3553 * @since 3.6.0
3554 *
3555 * @global int $content_width
3556 *
3557 * @param array  $attr {
3558 *     Attributes of the shortcode.
3559 *
3560 *     @type string $src      URL to the source of the video file. Default empty.
3561 *     @type int    $height   Height of the video embed in pixels. Default 360.
3562 *     @type int    $width    Width of the video embed in pixels. Default $content_width or 640.
3563 *     @type string $poster   The 'poster' attribute for the `<video>` element. Default empty.
3564 *     @type string $loop     The 'loop' attribute for the `<video>` element. Default empty.
3565 *     @type string $autoplay The 'autoplay' attribute for the `<video>` element. Default empty.
3566 *     @type string $muted    The 'muted' attribute for the `<video>` element. Default false.
3567 *     @type string $preload  The 'preload' attribute for the `<video>` element.
3568 *                            Default 'metadata'.
3569 *     @type string $class    The 'class' attribute for the `<video>` element.
3570 *                            Default 'wp-video-shortcode'.
3571 * }
3572 * @param string $content Shortcode content.
3573 * @return string|void HTML content to display video.
3574 */
3575function wp_video_shortcode( $attr, $content = '' ) {
3576        global $content_width;
3577        $post_id = get_post() ? get_the_ID() : 0;
3578
3579        static $instance = 0;
3580        ++$instance;
3581
3582        /**
3583         * Filters the default video shortcode output.
3584         *
3585         * If the filtered output isn't empty, it will be used instead of generating
3586         * the default video template.
3587         *
3588         * @since 3.6.0
3589         *
3590         * @see wp_video_shortcode()
3591         *
3592         * @param string $html     Empty variable to be replaced with shortcode markup.
3593         * @param array  $attr     Attributes of the shortcode. See {@see wp_video_shortcode()}.
3594         * @param string $content  Video shortcode content.
3595         * @param int    $instance Unique numeric ID of this video shortcode instance.
3596         */
3597        $override = apply_filters( 'wp_video_shortcode_override', '', $attr, $content, $instance );
3598
3599        if ( '' !== $override ) {
3600                return $override;
3601        }
3602
3603        $video = null;
3604
3605        $default_types = wp_get_video_extensions();
3606        $defaults_atts = array(
3607                'src'      => '',
3608                'poster'   => '',
3609                'loop'     => '',
3610                'autoplay' => '',
3611                'muted'    => 'false',
3612                'preload'  => 'metadata',
3613                'width'    => 640,
3614                'height'   => 360,
3615                'class'    => 'wp-video-shortcode',
3616        );
3617
3618        foreach ( $default_types as $type ) {
3619                $defaults_atts[ $type ] = '';
3620        }
3621
3622        $atts = shortcode_atts( $defaults_atts, $attr, 'video' );
3623
3624        if ( is_admin() ) {
3625                // Shrink the video so it isn't huge in the admin.
3626                if ( $atts['width'] > $defaults_atts['width'] ) {
3627                        $atts['height'] = round( ( $atts['height'] * $defaults_atts['width'] ) / $atts['width'] );
3628                        $atts['width']  = $defaults_atts['width'];
3629                }
3630        } else {
3631                // If the video is bigger than the theme.
3632                if ( ! empty( $content_width ) && $atts['width'] > $content_width ) {
3633                        $atts['height'] = round( ( $atts['height'] * $content_width ) / $atts['width'] );
3634                        $atts['width']  = $content_width;
3635                }
3636        }
3637
3638        $is_vimeo      = false;
3639        $is_youtube    = false;
3640        $yt_pattern    = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#';
3641        $vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#';
3642
3643        $primary = false;
3644        if ( ! empty( $atts['src'] ) ) {
3645                $is_vimeo   = ( preg_match( $vimeo_pattern, $atts['src'] ) );
3646                $is_youtube = ( preg_match( $yt_pattern, $atts['src'] ) );
3647
3648                if ( ! $is_youtube && ! $is_vimeo ) {
3649                        $type = wp_check_filetype( $atts['src'], wp_get_mime_types() );
3650
3651                        if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) {
3652                                return sprintf( '<a class="wp-embedded-video" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
3653                        }
3654                }
3655
3656                if ( $is_vimeo ) {
3657                        wp_enqueue_script( 'mediaelement-vimeo' );
3658                }
3659
3660                $primary = true;
3661                array_unshift( $default_types, 'src' );
3662        } else {
3663                foreach ( $default_types as $ext ) {
3664                        if ( ! empty( $atts[ $ext ] ) ) {
3665                                $type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );
3666                                if ( strtolower( $type['ext'] ) === $ext ) {
3667                                        $primary = true;
3668                                }
3669                        }
3670                }
3671        }
3672
3673        if ( ! $primary ) {
3674                $videos = get_attached_media( 'video', $post_id );
3675                if ( empty( $videos ) ) {
3676                        return;
3677                }
3678
3679                $video       = reset( $videos );
3680                $atts['src'] = wp_get_attachment_url( $video->ID );
3681                if ( empty( $atts['src'] ) ) {
3682                        return;
3683                }
3684
3685                array_unshift( $default_types, 'src' );
3686        }
3687
3688        /**
3689         * Filters the media library used for the video shortcode.
3690         *
3691         * @since 3.6.0
3692         *
3693         * @param string $library Media library used for the video shortcode.
3694         */
3695        $library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' );
3696        if ( 'mediaelement' === $library && did_action( 'init' ) ) {
3697                wp_enqueue_style( 'wp-mediaelement' );
3698                wp_enqueue_script( 'wp-mediaelement' );
3699                wp_enqueue_script( 'mediaelement-vimeo' );
3700        }
3701
3702        /*
3703         * MediaElement.js has issues with some URL formats for Vimeo and YouTube,
3704         * so update the URL to prevent the ME.js player from breaking.
3705         */
3706        if ( 'mediaelement' === $library ) {
3707                if ( $is_youtube ) {
3708                        // Remove `feature` query arg and force SSL - see #40866.
3709                        $atts['src'] = remove_query_arg( 'feature', $atts['src'] );
3710                        $atts['src'] = set_url_scheme( $atts['src'], 'https' );
3711                } elseif ( $is_vimeo ) {
3712                        // Remove all query arguments and force SSL - see #40866.
3713                        $parsed_vimeo_url = wp_parse_url( $atts['src'] );
3714                        $vimeo_src        = 'https://' . $parsed_vimeo_url['host'] . $parsed_vimeo_url['path'];
3715
3716                        // Add loop param for mejs bug - see #40977, not needed after #39686.
3717                        $loop        = $atts['loop'] ? '1' : '0';
3718                        $atts['src'] = add_query_arg( 'loop', $loop, $vimeo_src );
3719                }
3720        }
3721
3722        /**
3723         * Filters the class attribute for the video shortcode output container.
3724         *
3725         * @since 3.6.0
3726         * @since 4.9.0 The `$atts` parameter was added.
3727         *
3728         * @param string $class CSS class or list of space-separated classes.
3729         * @param array  $atts  Array of video shortcode attributes.
3730         */
3731        $atts['class'] = apply_filters( 'wp_video_shortcode_class', $atts['class'], $atts );
3732
3733        $html_atts = array(
3734                'class'    => $atts['class'],
3735                'id'       => sprintf( 'video-%d-%d', $post_id, $instance ),
3736                'width'    => absint( $atts['width'] ),
3737                'height'   => absint( $atts['height'] ),
3738                'poster'   => esc_url( $atts['poster'] ),
3739                'loop'     => wp_validate_boolean( $atts['loop'] ),
3740                'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
3741                'muted'    => wp_validate_boolean( $atts['muted'] ),
3742                'preload'  => $atts['preload'],
3743        );
3744
3745        // These ones should just be omitted altogether if they are blank.
3746        foreach ( array( 'poster', 'loop', 'autoplay', 'preload', 'muted' ) as $a ) {
3747                if ( empty( $html_atts[ $a ] ) ) {
3748                        unset( $html_atts[ $a ] );
3749                }
3750        }
3751
3752        $attr_strings = array();
3753        foreach ( $html_atts as $k => $v ) {
3754                $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
3755        }
3756
3757        $html = '';
3758
3759        if ( 'mediaelement' === $library && 1 === $instance ) {
3760                $html .= "<!--[if lt IE 9]><script>document.createElement('video');</script><![endif]-->\n";
3761        }
3762
3763        $html .= sprintf( '<video %s controls="controls">', implode( ' ', $attr_strings ) );
3764
3765        $fileurl = '';
3766        $source  = '<source type="%s" src="%s" />';
3767
3768        foreach ( $default_types as $fallback ) {
3769                if ( ! empty( $atts[ $fallback ] ) ) {
3770                        if ( empty( $fileurl ) ) {
3771                                $fileurl = $atts[ $fallback ];
3772                        }
3773                        if ( 'src' === $fallback && $is_youtube ) {
3774                                $type = array( 'type' => 'video/youtube' );
3775                        } elseif ( 'src' === $fallback && $is_vimeo ) {
3776                                $type = array( 'type' => 'video/vimeo' );
3777                        } else {
3778                                $type = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
3779                        }
3780                        $url   = add_query_arg( '_', $instance, $atts[ $fallback ] );
3781                        $html .= sprintf( $source, $type['type'], esc_url( $url ) );
3782                }
3783        }
3784
3785        if ( ! empty( $content ) ) {
3786                if ( str_contains( $content, "\n" ) ) {
3787                        $content = str_replace( array( "\r\n", "\n", "\t" ), '', $content );
3788                }
3789                $html .= trim( $content );
3790        }
3791
3792        if ( 'mediaelement' === $library ) {
3793                $html .= wp_mediaelement_fallback( $fileurl );
3794        }
3795        $html .= '</video>';
3796
3797        $width_rule = '';
3798        if ( ! empty( $atts['width'] ) ) {
3799                $width_rule = sprintf( 'width: %dpx;', $atts['width'] );
3800        }
3801        $output = sprintf( '<div style="%s" class="wp-video">%s</div>', $width_rule, $html );
3802
3803        /**
3804         * Filters the output of the video shortcode.
3805         *
3806         * @since 3.6.0
3807         *
3808         * @param string $output  Video shortcode HTML output.
3809         * @param array  $atts    Array of video shortcode attributes.
3810         * @param string $video   Video file.
3811         * @param int    $post_id Post ID.
3812         * @param string $library Media library used for the video shortcode.
3813         */
3814        return apply_filters( 'wp_video_shortcode', $output, $atts, $video, $post_id, $library );
3815}
3816add_shortcode( 'video', 'wp_video_shortcode' );
3817
3818/**
3819 * Gets the previous image link that has the same post parent.
3820 *
3821 * @since 5.8.0
3822 *
3823 * @see get_adjacent_image_link()
3824 *
3825 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
3826 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
3827 * @param string|false $text Optional. Link text. Default false.
3828 * @return string Markup for previous image link.
3829 */
3830function get_previous_image_link( $size = 'thumbnail', $text = false ) {
3831        return get_adjacent_image_link( true, $size, $text );
3832}
3833
3834/**
3835 * Displays previous image link that has the same post parent.
3836 *
3837 * @since 2.5.0
3838 *
3839 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
3840 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
3841 * @param string|false $text Optional. Link text. Default false.
3842 */
3843function previous_image_link( $size = 'thumbnail', $text = false ) {
3844        echo get_previous_image_link( $size, $text );
3845}
3846
3847/**
3848 * Gets the next image link that has the same post parent.
3849 *
3850 * @since 5.8.0
3851 *
3852 * @see get_adjacent_image_link()
3853 *
3854 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
3855 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
3856 * @param string|false $text Optional. Link text. Default false.
3857 * @return string Markup for next image link.
3858 */
3859function get_next_image_link( $size = 'thumbnail', $text = false ) {
3860        return get_adjacent_image_link( false, $size, $text );
3861}
3862
3863/**
3864 * Displays next image link that has the same post parent.
3865 *
3866 * @since 2.5.0
3867 *
3868 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
3869 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
3870 * @param string|false $text Optional. Link text. Default false.
3871 */
3872function next_image_link( $size = 'thumbnail', $text = false ) {
3873        echo get_next_image_link( $size, $text );
3874}
3875
3876/**
3877 * Gets the next or previous image link that has the same post parent.
3878 *
3879 * Retrieves the current attachment object from the $post global.
3880 *
3881 * @since 5.8.0
3882 *
3883 * @param bool         $prev Optional. Whether to display the next (false) or previous (true) link. Default true.
3884 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
3885 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
3886 * @param bool         $text Optional. Link text. Default false.
3887 * @return string Markup for image link.
3888 */
3889function get_adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) {
3890        $post        = get_post();
3891        $attachments = array_values(
3892                get_children(
3893                        array(
3894                                'post_parent'    => $post->post_parent,
3895                                'post_status'    => 'inherit',
3896                                'post_type'      => 'attachment',
3897                                'post_mime_type' => 'image',
3898                                'order'          => 'ASC',
3899                                'orderby'        => 'menu_order ID',
3900                        )
3901                )
3902        );
3903
3904        foreach ( $attachments as $k => $attachment ) {
3905                if ( (int) $attachment->ID === (int) $post->ID ) {
3906                        break;
3907                }
3908        }
3909
3910        $output        = '';
3911        $attachment_id = 0;
3912
3913        if ( $attachments ) {
3914                $k = $prev ? $k - 1 : $k + 1;
3915
3916                if ( isset( $attachments[ $k ] ) ) {
3917                        $attachment_id = $attachments[ $k ]->ID;
3918                        $attr          = array( 'alt' => get_the_title( $attachment_id ) );
3919                        $output        = wp_get_attachment_link( $attachment_id, $size, true, false, $text, $attr );
3920                }
3921        }
3922
3923        $adjacent = $prev ? 'previous' : 'next';
3924
3925        /**
3926         * Filters the adjacent image link.
3927         *
3928         * The dynamic portion of the hook name, `$adjacent`, refers to the type of adjacency,
3929         * either 'next', or 'previous'.
3930         *
3931         * Possible hook names include:
3932         *
3933         *  - `next_image_link`
3934         *  - `previous_image_link`
3935         *
3936         * @since 3.5.0
3937         *
3938         * @param string $output        Adjacent image HTML markup.
3939         * @param int    $attachment_id Attachment ID
3940         * @param string|int[] $size    Requested image size. Can be any registered image size name, or
3941         *                              an array of width and height values in pixels (in that order).
3942         * @param string $text          Link text.
3943         */
3944        return apply_filters( "{$adjacent}_image_link", $output, $attachment_id, $size, $text );
3945}
3946
3947/**
3948 * Displays next or previous image link that has the same post parent.
3949 *
3950 * Retrieves the current attachment object from the $post global.
3951 *
3952 * @since 2.5.0
3953 *
3954 * @param bool         $prev Optional. Whether to display the next (false) or previous (true) link. Default true.
3955 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
3956 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
3957 * @param bool         $text Optional. Link text. Default false.
3958 */
3959function adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) {
3960        echo get_adjacent_image_link( $prev, $size, $text );
3961}
3962
3963/**
3964 * Retrieves taxonomies attached to given the attachment.
3965 *
3966 * @since 2.5.0
3967 * @since 4.7.0 Introduced the `$output` parameter.
3968 *
3969 * @param int|array|object $attachment Attachment ID, data array, or data object.
3970 * @param string           $output     Output type. 'names' to return an array of taxonomy names,
3971 *                                     or 'objects' to return an array of taxonomy objects.
3972 *                                     Default is 'names'.
3973 * @return string[]|WP_Taxonomy[] List of taxonomies or taxonomy names. Empty array on failure.
3974 */
3975function get_attachment_taxonomies( $attachment, $output = 'names' ) {
3976        if ( is_int( $attachment ) ) {
3977                $attachment = get_post( $attachment );
3978        } elseif ( is_array( $attachment ) ) {
3979                $attachment = (object) $attachment;
3980        }
3981
3982        if ( ! is_object( $attachment ) ) {
3983                return array();
3984        }
3985
3986        $file     = get_attached_file( $attachment->ID );
3987        $filename = wp_basename( $file );
3988
3989        $objects = array( 'attachment' );
3990
3991        if ( str_contains( $filename, '.' ) ) {
3992                $objects[] = 'attachment:' . substr( $filename, strrpos( $filename, '.' ) + 1 );
3993        }
3994
3995        if ( ! empty( $attachment->post_mime_type ) ) {
3996                $objects[] = 'attachment:' . $attachment->post_mime_type;
3997
3998                if ( str_contains( $attachment->post_mime_type, '/' ) ) {
3999                        foreach ( explode( '/', $attachment->post_mime_type ) as $token ) {
4000                                if ( ! empty( $token ) ) {
4001                                        $objects[] = "attachment:$token";
4002                                }
4003                        }
4004                }
4005        }
4006
4007        $taxonomies = array();
4008
4009        foreach ( $objects as $object ) {
4010                $taxes = get_object_taxonomies( $object, $output );
4011
4012                if ( $taxes ) {
4013                        $taxonomies = array_merge( $taxonomies, $taxes );
4014                }
4015        }
4016
4017        if ( 'names' === $output ) {
4018                $taxonomies = array_unique( $taxonomies );
4019        }
4020
4021        return $taxonomies;
4022}
4023
4024/**
4025 * Retrieves all of the taxonomies that are registered for attachments.
4026 *
4027 * Handles mime-type-specific taxonomies such as attachment:image and attachment:video.
4028 *
4029 * @since 3.5.0
4030 *
4031 * @see get_taxonomies()
4032 *
4033 * @param string $output Optional. The type of taxonomy output to return. Accepts 'names' or 'objects'.
4034 *                       Default 'names'.
4035 * @return string[]|WP_Taxonomy[] Array of names or objects of registered taxonomies for attachments.
4036 */
4037function get_taxonomies_for_attachments( $output = 'names' ) {
4038        $taxonomies = array();
4039
4040        foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy ) {
4041                foreach ( $taxonomy->object_type as $object_type ) {
4042                        if ( 'attachment' === $object_type || str_starts_with( $object_type, 'attachment:' ) ) {
4043                                if ( 'names' === $output ) {
4044                                        $taxonomies[] = $taxonomy->name;
4045                                } else {
4046                                        $taxonomies[ $taxonomy->name ] = $taxonomy;
4047                                }
4048                                break;
4049                        }
4050                }
4051        }
4052
4053        return $taxonomies;
4054}
4055
4056/**
4057 * Determines whether the value is an acceptable type for GD image functions.
4058 *
4059 * In PHP 8.0, the GD extension uses GdImage objects for its data structures.
4060 * This function checks if the passed value is either a GdImage object instance
4061 * or a resource of type `gd`. Any other type will return false.
4062 *
4063 * @since 5.6.0
4064 *
4065 * @param resource|GdImage|false $image A value to check the type for.
4066 * @return bool True if `$image` is either a GD image resource or a GdImage instance,
4067 *              false otherwise.
4068 */
4069function is_gd_image( $image ) {
4070        if ( $image instanceof GdImage
4071                || is_resource( $image ) && 'gd' === get_resource_type( $image )
4072        ) {
4073                return true;
4074        }
4075
4076        return false;
4077}
4078
4079/**
4080 * Creates a new GD image resource with transparency support.
4081 *
4082 * @todo Deprecate if possible.
4083 *
4084 * @since 2.9.0
4085 *
4086 * @param int $width  Image width in pixels.
4087 * @param int $height Image height in pixels.
4088 * @return resource|GdImage|false The GD image resource or GdImage instance on success.
4089 *                                False on failure.
4090 */
4091function wp_imagecreatetruecolor( $width, $height ) {
4092        $img = imagecreatetruecolor( $width, $height );
4093
4094        if ( is_gd_image( $img )
4095                && function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' )
4096        ) {
4097                imagealphablending( $img, false );
4098                imagesavealpha( $img, true );
4099        }
4100
4101        return $img;
4102}
4103
4104/**
4105 * Based on a supplied width/height example, returns the biggest possible dimensions based on the max width/height.
4106 *
4107 * @since 2.9.0
4108 *
4109 * @see wp_constrain_dimensions()
4110 *
4111 * @param int $example_width  The width of an example embed.
4112 * @param int $example_height The height of an example embed.
4113 * @param int $max_width      The maximum allowed width.
4114 * @param int $max_height     The maximum allowed height.
4115 * @return int[] {
4116 *     An array of maximum width and height values.
4117 *
4118 *     @type int $0 The maximum width in pixels.
4119 *     @type int $1 The maximum height in pixels.
4120 * }
4121 */
4122function wp_expand_dimensions( $example_width, $example_height, $max_width, $max_height ) {
4123        $example_width  = (int) $example_width;
4124        $example_height = (int) $example_height;
4125        $max_width      = (int) $max_width;
4126        $max_height     = (int) $max_height;
4127
4128        return wp_constrain_dimensions( $example_width * 1000000, $example_height * 1000000, $max_width, $max_height );
4129}
4130
4131/**
4132 * Determines the maximum upload size allowed in php.ini.
4133 *
4134 * @since 2.5.0
4135 *
4136 * @return int Allowed upload size.
4137 */
4138function wp_max_upload_size() {
4139        $u_bytes = wp_convert_hr_to_bytes( ini_get( 'upload_max_filesize' ) );
4140        $p_bytes = wp_convert_hr_to_bytes( ini_get( 'post_max_size' ) );
4141
4142        /**
4143         * Filters the maximum upload size allowed in php.ini.
4144         *
4145         * @since 2.5.0
4146         *
4147         * @param int $size    Max upload size limit in bytes.
4148         * @param int $u_bytes Maximum upload filesize in bytes.
4149         * @param int $p_bytes Maximum size of POST data in bytes.
4150         */
4151        return apply_filters( 'upload_size_limit', min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes );
4152}
4153
4154/**
4155 * Returns a WP_Image_Editor instance and loads file into it.
4156 *
4157 * @since 3.5.0
4158 *
4159 * @param string $path Path to the file to load.
4160 * @param array  $args Optional. Additional arguments for retrieving the image editor.
4161 *                     Default empty array.
4162 * @return WP_Image_Editor|WP_Error The WP_Image_Editor object on success,
4163 *                                  a WP_Error object otherwise.
4164 */
4165function wp_get_image_editor( $path, $args = array() ) {
4166        $args['path'] = $path;
4167
4168        // If the mime type is not set in args, try to extract and set it from the file.
4169        if ( ! isset( $args['mime_type'] ) ) {
4170                $file_info = wp_check_filetype( $args['path'] );
4171
4172                /*
4173                 * If $file_info['type'] is false, then we let the editor attempt to
4174                 * figure out the file type, rather than forcing a failure based on extension.
4175                 */
4176                if ( isset( $file_info ) && $file_info['type'] ) {
4177                        $args['mime_type'] = $file_info['type'];
4178                }
4179        }
4180
4181        // Check and set the output mime type mapped to the input type.
4182        if ( isset( $args['mime_type'] ) ) {
4183                $output_format = wp_get_image_editor_output_format( $path, $args['mime_type'] );
4184                if ( isset( $output_format[ $args['mime_type'] ] ) ) {
4185                        $args['output_mime_type'] = $output_format[ $args['mime_type'] ];
4186                }
4187        }
4188
4189        $implementation = _wp_image_editor_choose( $args );
4190
4191        if ( $implementation ) {
4192                $editor = new $implementation( $path );
4193                $loaded = $editor->load();
4194
4195                if ( is_wp_error( $loaded ) ) {
4196                        return $loaded;
4197                }
4198
4199                return $editor;
4200        }
4201
4202        return new WP_Error( 'image_no_editor', __( 'No editor could be selected.' ) );
4203}
4204
4205/**
4206 * Tests whether there is an editor that supports a given mime type or methods.
4207 *
4208 * @since 3.5.0
4209 *
4210 * @param string|array $args Optional. Array of arguments to retrieve the image editor supports.
4211 *                           Default empty array.
4212 * @return bool True if an eligible editor is found; false otherwise.
4213 */
4214function wp_image_editor_supports( $args = array() ) {
4215        return (bool) _wp_image_editor_choose( $args );
4216}
4217
4218/**
4219 * Tests which editors are capable of supporting the request.
4220 *
4221 * @ignore
4222 * @since 3.5.0
4223 *
4224 * @param array $args Optional. Array of arguments for choosing a capable editor. Default empty array.
4225 * @return string|false Class name for the first editor that claims to support the request.
4226 *                      False if no editor claims to support the request.
4227 */
4228function _wp_image_editor_choose( $args = array() ) {
4229        require_once ABSPATH . WPINC . '/class-wp-image-editor.php';
4230        require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php';
4231        require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php';
4232        require_once ABSPATH . WPINC . '/class-avif-info.php';
4233        /**
4234         * Filters the list of image editing library classes.
4235         *
4236         * @since 3.5.0
4237         *
4238         * @param string[] $image_editors Array of available image editor class names. Defaults are
4239         *                                'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'.
4240         */
4241        $implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) );
4242
4243        $editors = wp_cache_get( 'wp_image_editor_choose', 'image_editor' );
4244
4245        if ( ! is_array( $editors ) ) {
4246                $editors = array();
4247        }
4248
4249        // Cache the chosen editor implementation based on specific args and available implementations.
4250        $cache_key = md5( serialize( array( $args, $implementations ) ) );
4251
4252        if ( isset( $editors[ $cache_key ] ) ) {
4253                return $editors[ $cache_key ];
4254        }
4255
4256        // Assume no support until a capable implementation is identified.
4257        $editor = false;
4258
4259        foreach ( $implementations as $implementation ) {
4260                if ( ! call_user_func( array( $implementation, 'test' ), $args ) ) {
4261                        continue;
4262                }
4263
4264                // Implementation should support the passed mime type.
4265                if ( isset( $args['mime_type'] ) &&
4266                        ! call_user_func(
4267                                array( $implementation, 'supports_mime_type' ),
4268                                $args['mime_type']
4269                        ) ) {
4270                        continue;
4271                }
4272
4273                // Implementation should support requested methods.
4274                if ( isset( $args['methods'] ) &&
4275                        array_diff( $args['methods'], get_class_methods( $implementation ) ) ) {
4276
4277                        continue;
4278                }
4279
4280                // Implementation should ideally support the output mime type as well if set and different than the passed type.
4281                if (
4282                        isset( $args['mime_type'] ) &&
4283                        isset( $args['output_mime_type'] ) &&
4284                        $args['mime_type'] !== $args['output_mime_type'] &&
4285                        ! call_user_func( array( $implementation, 'supports_mime_type' ), $args['output_mime_type'] )
4286                ) {
4287                        /*
4288                         * This implementation supports the input type but not the output type.
4289                         * Keep looking to see if we can find an implementation that supports both.
4290                         */
4291                        $editor = $implementation;
4292                        continue;
4293                }
4294
4295                // Favor the implementation that supports both input and output mime types.
4296                $editor = $implementation;
4297                break;
4298        }
4299
4300        $editors[ $cache_key ] = $editor;
4301
4302        wp_cache_set( 'wp_image_editor_choose', $editors, 'image_editor', DAY_IN_SECONDS );
4303
4304        return $editor;
4305}
4306
4307/**
4308 * Prints default Plupload arguments.
4309 *
4310 * @since 3.4.0
4311 */
4312function wp_plupload_default_settings() {
4313        $wp_scripts = wp_scripts();
4314
4315        $data = $wp_scripts->get_data( 'wp-plupload', 'data' );
4316        if ( $data && str_contains( $data, '_wpPluploadSettings' ) ) {
4317                return;
4318        }
4319
4320        $max_upload_size    = wp_max_upload_size();
4321        $allowed_extensions = array_keys( get_allowed_mime_types() );
4322        $extensions         = array();
4323        foreach ( $allowed_extensions as $extension ) {
4324                $extensions = array_merge( $extensions, explode( '|', $extension ) );
4325        }
4326
4327        /*
4328         * Since 4.9 the `runtimes` setting is hardcoded in our version of Plupload to `html5,html4`,
4329         * and the `flash_swf_url` and `silverlight_xap_url` are not used.
4330         */
4331        $defaults = array(
4332                'file_data_name' => 'async-upload', // Key passed to $_FILE.
4333                'url'            => admin_url( 'async-upload.php', 'relative' ),
4334                'filters'        => array(
4335                        'max_file_size' => $max_upload_size . 'b',
4336                        'mime_types'    => array( array( 'extensions' => implode( ',', $extensions ) ) ),
4337                ),
4338        );
4339
4340        /*
4341         * Currently only iOS Safari supports multiple files uploading,
4342         * but iOS 7.x has a bug that prevents uploading of videos when enabled.
4343         * See #29602.
4344         */
4345        if ( wp_is_mobile()
4346                && str_contains( $_SERVER['HTTP_USER_AGENT'], 'OS 7_' )
4347                && str_contains( $_SERVER['HTTP_USER_AGENT'], 'like Mac OS X' )
4348        ) {
4349                $defaults['multi_selection'] = false;
4350        }
4351
4352        // Check if WebP images can be edited.
4353        if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
4354                $defaults['webp_upload_error'] = true;
4355        }
4356
4357        // Check if AVIF images can be edited.
4358        if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
4359                $defaults['avif_upload_error'] = true;
4360        }
4361
4362        // Check if HEIC images can be edited.
4363        if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
4364                $defaults['heic_upload_error'] = true;
4365        }
4366
4367        /**
4368         * Filters the Plupload default settings.
4369         *
4370         * @since 3.4.0
4371         *
4372         * @param array $defaults Default Plupload settings array.
4373         */
4374        $defaults = apply_filters( 'plupload_default_settings', $defaults );
4375
4376        $params = array(
4377                'action' => 'upload-attachment',
4378        );
4379
4380        /**
4381         * Filters the Plupload default parameters.
4382         *
4383         * @since 3.4.0
4384         *
4385         * @param array $params Default Plupload parameters array.
4386         */
4387        $params = apply_filters( 'plupload_default_params', $params );
4388
4389        $params['_wpnonce'] = wp_create_nonce( 'media-form' );
4390
4391        $defaults['multipart_params'] = $params;
4392
4393        $settings = array(
4394                'defaults'      => $defaults,
4395                'browser'       => array(
4396                        'mobile'    => wp_is_mobile(),
4397                        'supported' => _device_can_upload(),
4398                ),
4399                'limitExceeded' => is_multisite() && ! is_upload_space_available(),
4400        );
4401
4402        $script = 'var _wpPluploadSettings = ' . wp_json_encode( $settings ) . ';';
4403
4404        if ( $data ) {
4405                $script = "$data\n$script";
4406        }
4407
4408        $wp_scripts->add_data( 'wp-plupload', 'data', $script );
4409}
4410
4411/**
4412 * Prepares an attachment post object for JS, where it is expected
4413 * to be JSON-encoded and fit into an Attachment model.
4414 *
4415 * @since 3.5.0
4416 *
4417 * @param int|WP_Post $attachment Attachment ID or object.
4418 * @return array|void {
4419 *     Array of attachment details, or void if the parameter does not correspond to an attachment.
4420 *
4421 *     @type string $alt                   Alt text of the attachment.
4422 *     @type string $author                ID of the attachment author, as a string.
4423 *     @type string $authorName            Name of the attachment author.
4424 *     @type string $caption               Caption for the attachment.
4425 *     @type array  $compat                Containing item and meta.
4426 *     @type string $context               Context, whether it's used as the site icon for example.
4427 *     @type int    $date                  Uploaded date, timestamp in milliseconds.
4428 *     @type string $dateFormatted         Formatted date (e.g. June 29, 2018).
4429 *     @type string $description           Description of the attachment.
4430 *     @type string $editLink              URL to the edit page for the attachment.
4431 *     @type string $filename              File name of the attachment.
4432 *     @type string $filesizeHumanReadable Filesize of the attachment in human readable format (e.g. 1 MB).
4433 *     @type int    $filesizeInBytes       Filesize of the attachment in bytes.
4434 *     @type int    $height                If the attachment is an image, represents the height of the image in pixels.
4435 *     @type string $icon                  Icon URL of the attachment (e.g. /wp-includes/images/media/archive.png).
4436 *     @type int    $id                    ID of the attachment.
4437 *     @type string $link                  URL to the attachment.
4438 *     @type int    $menuOrder             Menu order of the attachment post.
4439 *     @type array  $meta                  Meta data for the attachment.
4440 *     @type string $mime                  Mime type of the attachment (e.g. image/jpeg or application/zip).
4441 *     @type int    $modified              Last modified, timestamp in milliseconds.
4442 *     @type string $name                  Name, same as title of the attachment.
4443 *     @type array  $nonces                Nonces for update, delete and edit.
4444 *     @type string $orientation           If the attachment is an image, represents the image orientation
4445 *                                         (landscape or portrait).
4446 *     @type array  $sizes                 If the attachment is an image, contains an array of arrays
4447 *                                         for the images sizes: thumbnail, medium, large, and full.
4448 *     @type string $status                Post status of the attachment (usually 'inherit').
4449 *     @type string $subtype               Mime subtype of the attachment (usually the last part, e.g. jpeg or zip).
4450 *     @type string $title                 Title of the attachment (usually slugified file name without the extension).
4451 *     @type string $type                  Type of the attachment (usually first part of the mime type, e.g. image).
4452 *     @type int    $uploadedTo            Parent post to which the attachment was uploaded.
4453 *     @type string $uploadedToLink        URL to the edit page of the parent post of the attachment.
4454 *     @type string $uploadedToTitle       Post title of the parent of the attachment.
4455 *     @type string $url                   Direct URL to the attachment file (from wp-content).
4456 *     @type int    $width                 If the attachment is an image, represents the width of the image in pixels.
4457 * }
4458 *
4459 */
4460function wp_prepare_attachment_for_js( $attachment ) {
4461        $attachment = get_post( $attachment );
4462
4463        if ( ! $attachment ) {
4464                return;
4465        }
4466
4467        if ( 'attachment' !== $attachment->post_type ) {
4468                return;
4469        }
4470
4471        $meta = wp_get_attachment_metadata( $attachment->ID );
4472        if ( str_contains( $attachment->post_mime_type, '/' ) ) {
4473                list( $type, $subtype ) = explode( '/', $attachment->post_mime_type );
4474        } else {
4475                list( $type, $subtype ) = array( $attachment->post_mime_type, '' );
4476        }
4477
4478        $attachment_url = wp_get_attachment_url( $attachment->ID );
4479        $base_url       = str_replace( wp_basename( $attachment_url ), '', $attachment_url );
4480
4481        $response = array(
4482                'id'            => $attachment->ID,
4483                'title'         => $attachment->post_title,
4484                'filename'      => wp_basename( get_attached_file( $attachment->ID ) ),
4485                'url'           => $attachment_url,
4486                'link'          => get_attachment_link( $attachment->ID ),
4487                'alt'           => get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ),
4488                'author'        => $attachment->post_author,
4489                'description'   => $attachment->post_content,
4490                'caption'       => $attachment->post_excerpt,
4491                'name'          => $attachment->post_name,
4492                'status'        => $attachment->post_status,
4493                'uploadedTo'    => $attachment->post_parent,
4494                'date'          => strtotime( $attachment->post_date_gmt ) * 1000,
4495                'modified'      => strtotime( $attachment->post_modified_gmt ) * 1000,
4496                'menuOrder'     => $attachment->menu_order,
4497                'mime'          => $attachment->post_mime_type,
4498                'type'          => $type,
4499                'subtype'       => $subtype,
4500                'icon'          => wp_mime_type_icon( $attachment->ID, '.svg' ),
4501                'dateFormatted' => mysql2date( __( 'F j, Y' ), $attachment->post_date ),
4502                'nonces'        => array(
4503                        'update' => false,
4504                        'delete' => false,
4505                        'edit'   => false,
4506                ),
4507                'editLink'      => false,
4508                'meta'          => false,
4509        );
4510
4511        $author = new WP_User( $attachment->post_author );
4512
4513        if ( $author->exists() ) {
4514                $author_name            = $author->display_name ? $author->display_name : $author->nickname;
4515                $response['authorName'] = html_entity_decode( $author_name, ENT_QUOTES, get_bloginfo( 'charset' ) );
4516                $response['authorLink'] = get_edit_user_link( $author->ID );
4517        } else {
4518                $response['authorName'] = __( '(no author)' );
4519        }
4520
4521        if ( $attachment->post_parent ) {
4522                $post_parent = get_post( $attachment->post_parent );
4523                if ( $post_parent ) {
4524                        $response['uploadedToTitle'] = $post_parent->post_title ? $post_parent->post_title : __( '(no title)' );
4525                        $response['uploadedToLink']  = get_edit_post_link( $attachment->post_parent, 'raw' );
4526                }
4527        }
4528
4529        $attached_file = get_attached_file( $attachment->ID );
4530
4531        if ( isset( $meta['filesize'] ) ) {
4532                $bytes = $meta['filesize'];
4533        } elseif ( file_exists( $attached_file ) ) {
4534                $bytes = wp_filesize( $attached_file );
4535        } else {
4536                $bytes = '';
4537        }
4538
4539        if ( $bytes ) {
4540                $response['filesizeInBytes']       = $bytes;
4541                $response['filesizeHumanReadable'] = size_format( $bytes );
4542        }
4543
4544        $context             = get_post_meta( $attachment->ID, '_wp_attachment_context', true );
4545        $response['context'] = ( $context ) ? $context : '';
4546
4547        if ( current_user_can( 'edit_post', $attachment->ID ) ) {
4548                $response['nonces']['update'] = wp_create_nonce( 'update-post_' . $attachment->ID );
4549                $response['nonces']['edit']   = wp_create_nonce( 'image_editor-' . $attachment->ID );
4550                $response['editLink']         = get_edit_post_link( $attachment->ID, 'raw' );
4551        }
4552
4553        if ( current_user_can( 'delete_post', $attachment->ID ) ) {
4554                $response['nonces']['delete'] = wp_create_nonce( 'delete-post_' . $attachment->ID );
4555        }
4556
4557        if ( $meta && ( 'image' === $type || ! empty( $meta['sizes'] ) ) ) {
4558                $sizes = array();
4559
4560                /** This filter is documented in wp-admin/includes/media.php */
4561                $possible_sizes = apply_filters(
4562                        'image_size_names_choose',
4563                        array(
4564                                'thumbnail' => __( 'Thumbnail' ),
4565                                'medium'    => __( 'Medium' ),
4566                                'large'     => __( 'Large' ),
4567                                'full'      => __( 'Full Size' ),
4568                        )
4569                );
4570                unset( $possible_sizes['full'] );
4571
4572                /*
4573                 * Loop through all potential sizes that may be chosen. Try to do this with some efficiency.
4574                 * First: run the image_downsize filter. If it returns something, we can use its data.
4575                 * If the filter does not return something, then image_downsize() is just an expensive way
4576                 * to check the image metadata, which we do second.
4577                 */
4578                foreach ( $possible_sizes as $size => $label ) {
4579
4580                        /** This filter is documented in wp-includes/media.php */
4581                        $downsize = apply_filters( 'image_downsize', false, $attachment->ID, $size );
4582
4583                        if ( $downsize ) {
4584                                if ( empty( $downsize[3] ) ) {
4585                                        continue;
4586                                }
4587
4588                                $sizes[ $size ] = array(
4589                                        'height'      => $downsize[2],
4590                                        'width'       => $downsize[1],
4591                                        'url'         => $downsize[0],
4592                                        'orientation' => $downsize[2] > $downsize[1] ? 'portrait' : 'landscape',
4593                                );
4594                        } elseif ( isset( $meta['sizes'][ $size ] ) ) {
4595                                // Nothing from the filter, so consult image metadata if we have it.
4596                                $size_meta = $meta['sizes'][ $size ];
4597
4598                                /*
4599                                 * We have the actual image size, but might need to further constrain it if content_width is narrower.
4600                                 * Thumbnail, medium, and full sizes are also checked against the site's height/width options.
4601                                 */
4602                                list( $width, $height ) = image_constrain_size_for_editor( $size_meta['width'], $size_meta['height'], $size, 'edit' );
4603
4604                                $sizes[ $size ] = array(
4605                                        'height'      => $height,
4606                                        'width'       => $width,
4607                                        'url'         => $base_url . $size_meta['file'],
4608                                        'orientation' => $height > $width ? 'portrait' : 'landscape',
4609                                );
4610                        }
4611                }
4612
4613                if ( 'image' === $type ) {
4614                        if ( ! empty( $meta['original_image'] ) ) {
4615                                $response['originalImageURL']  = wp_get_original_image_url( $attachment->ID );
4616                                $response['originalImageName'] = wp_basename( wp_get_original_image_path( $attachment->ID ) );
4617                        }
4618
4619                        $sizes['full'] = array( 'url' => $attachment_url );
4620
4621                        if ( isset( $meta['height'], $meta['width'] ) ) {
4622                                $sizes['full']['height']      = $meta['height'];
4623                                $sizes['full']['width']       = $meta['width'];
4624                                $sizes['full']['orientation'] = $meta['height'] > $meta['width'] ? 'portrait' : 'landscape';
4625                        }
4626
4627                        $response = array_merge( $response, $sizes['full'] );
4628                } elseif ( $meta['sizes']['full']['file'] ) {
4629                        $sizes['full'] = array(
4630                                'url'         => $base_url . $meta['sizes']['full']['file'],
4631                                'height'      => $meta['sizes']['full']['height'],
4632                                'width'       => $meta['sizes']['full']['width'],
4633                                'orientation' => $meta['sizes']['full']['height'] > $meta['sizes']['full']['width'] ? 'portrait' : 'landscape',
4634                        );
4635                }
4636
4637                $response = array_merge( $response, array( 'sizes' => $sizes ) );
4638        }
4639
4640        if ( $meta && 'video' === $type ) {
4641                if ( isset( $meta['width'] ) ) {
4642                        $response['width'] = (int) $meta['width'];
4643                }
4644                if ( isset( $meta['height'] ) ) {
4645                        $response['height'] = (int) $meta['height'];
4646                }
4647        }
4648
4649        if ( $meta && ( 'audio' === $type || 'video' === $type ) ) {
4650                if ( isset( $meta['length_formatted'] ) ) {
4651                        $response['fileLength']              = $meta['length_formatted'];
4652                        $response['fileLengthHumanReadable'] = human_readable_duration( $meta['length_formatted'] );
4653                }
4654
4655                $response['meta'] = array();
4656                foreach ( wp_get_attachment_id3_keys( $attachment, 'js' ) as $key => $label ) {
4657                        $response['meta'][ $key ] = false;
4658
4659                        if ( ! empty( $meta[ $key ] ) ) {
4660                                $response['meta'][ $key ] = $meta[ $key ];
4661                        }
4662                }
4663
4664                $id = get_post_thumbnail_id( $attachment->ID );
4665                if ( ! empty( $id ) ) {
4666                        list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'full' );
4667                        $response['image']            = compact( 'src', 'width', 'height' );
4668                        list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'thumbnail' );
4669                        $response['thumb']            = compact( 'src', 'width', 'height' );
4670                } else {
4671                        $src               = wp_mime_type_icon( $attachment->ID, '.svg' );
4672                        $width             = 48;
4673                        $height            = 64;
4674                        $response['image'] = compact( 'src', 'width', 'height' );
4675                        $response['thumb'] = compact( 'src', 'width', 'height' );
4676                }
4677        }
4678
4679        if ( function_exists( 'get_compat_media_markup' ) ) {
4680                $response['compat'] = get_compat_media_markup( $attachment->ID, array( 'in_modal' => true ) );
4681        }
4682
4683        if ( function_exists( 'get_media_states' ) ) {
4684                $media_states = get_media_states( $attachment );
4685                if ( ! empty( $media_states ) ) {
4686                        $response['mediaStates'] = implode( ', ', $media_states );
4687                }
4688        }
4689
4690        /**
4691         * Filters the attachment data prepared for JavaScript.
4692         *
4693         * @since 3.5.0
4694         *
4695         * @param array       $response   Array of prepared attachment data. See {@see wp_prepare_attachment_for_js()}.
4696         * @param WP_Post     $attachment Attachment object.
4697         * @param array|false $meta       Array of attachment meta data, or false if there is none.
4698         */
4699        return apply_filters( 'wp_prepare_attachment_for_js', $response, $attachment, $meta );
4700}
4701
4702/**
4703 * Enqueues all scripts, styles, settings, and templates necessary to use
4704 * all media JS APIs.
4705 *
4706 * @since 3.5.0
4707 *
4708 * @global int       $content_width
4709 * @global wpdb      $wpdb          WordPress database abstraction object.
4710 * @global WP_Locale $wp_locale     WordPress date and time locale object.
4711 *
4712 * @param array $args {
4713 *     Arguments for enqueuing media scripts.
4714 *
4715 *     @type int|WP_Post $post Post ID or post object.
4716 * }
4717 */
4718function wp_enqueue_media( $args = array() ) {
4719        // Enqueue me just once per page, please.
4720        if ( did_action( 'wp_enqueue_media' ) ) {
4721                return;
4722        }
4723
4724        global $content_width, $wpdb, $wp_locale;
4725
4726        $defaults = array(
4727                'post' => null,
4728        );
4729        $args     = wp_parse_args( $args, $defaults );
4730
4731        /*
4732         * We're going to pass the old thickbox media tabs to `media_upload_tabs`
4733         * to ensure plugins will work. We will then unset those tabs.
4734         */
4735        $tabs = array(
4736                // handler action suffix => tab label
4737                'type'     => '',
4738                'type_url' => '',
4739                'gallery'  => '',
4740                'library'  => '',
4741        );
4742
4743        /** This filter is documented in wp-admin/includes/media.php */
4744        $tabs = apply_filters( 'media_upload_tabs', $tabs );
4745        unset( $tabs['type'], $tabs['type_url'], $tabs['gallery'], $tabs['library'] );
4746
4747        $props = array(
4748                'link'  => get_option( 'image_default_link_type' ), // DB default is 'file'.
4749                'align' => get_option( 'image_default_align' ),     // Empty default.
4750                'size'  => get_option( 'image_default_size' ),      // Empty default.
4751        );
4752
4753        $exts      = array_merge( wp_get_audio_extensions(), wp_get_video_extensions() );
4754        $mimes     = get_allowed_mime_types();
4755        $ext_mimes = array();
4756        foreach ( $exts as $ext ) {
4757                foreach ( $mimes as $ext_preg => $mime_match ) {
4758                        if ( preg_match( '#' . $ext . '#i', $ext_preg ) ) {
4759                                $ext_mimes[ $ext ] = $mime_match;
4760                                break;
4761                        }
4762                }
4763        }
4764
4765        /**
4766         * Allows showing or hiding the "Create Audio Playlist" button in the media library.
4767         *
4768         * By default, the "Create Audio Playlist" button will always be shown in
4769         * the media library.  If this filter returns `null`, a query will be run
4770         * to determine whether the media library contains any audio items.  This
4771         * was the default behavior prior to version 4.8.0, but this query is
4772         * expensive for large media libraries.
4773         *
4774         * @since 4.7.4
4775         * @since 4.8.0 The filter's default value is `true` rather than `null`.
4776         *
4777         * @link https://core.trac.wordpress.org/ticket/31071
4778         *
4779         * @param bool|null $show Whether to show the button, or `null` to decide based
4780         *                        on whether any audio files exist in the media library.
4781         */
4782        $show_audio_playlist = apply_filters( 'media_library_show_audio_playlist', true );
4783        if ( null === $show_audio_playlist ) {
4784                $show_audio_playlist = $wpdb->get_var(
4785                        "SELECT ID
4786                        FROM $wpdb->posts
4787                        WHERE post_type = 'attachment'
4788                        AND post_mime_type LIKE 'audio%'
4789                        LIMIT 1"
4790                );
4791        }
4792
4793        /**
4794         * Allows showing or hiding the "Create Video Playlist" button in the media library.
4795         *
4796         * By default, the "Create Video Playlist" button will always be shown in
4797         * the media library.  If this filter returns `null`, a query will be run
4798         * to determine whether the media library contains any video items.  This
4799         * was the default behavior prior to version 4.8.0, but this query is
4800         * expensive for large media libraries.
4801         *
4802         * @since 4.7.4
4803         * @since 4.8.0 The filter's default value is `true` rather than `null`.
4804         *
4805         * @link https://core.trac.wordpress.org/ticket/31071
4806         *
4807         * @param bool|null $show Whether to show the button, or `null` to decide based
4808         *                        on whether any video files exist in the media library.
4809         */
4810        $show_video_playlist = apply_filters( 'media_library_show_video_playlist', true );
4811        if ( null === $show_video_playlist ) {
4812                $show_video_playlist = $wpdb->get_var(
4813                        "SELECT ID
4814                        FROM $wpdb->posts
4815                        WHERE post_type = 'attachment'
4816                        AND post_mime_type LIKE 'video%'
4817                        LIMIT 1"
4818                );
4819        }
4820
4821        /**
4822         * Allows overriding the list of months displayed in the media library.
4823         *
4824         * By default (if this filter does not return an array), a query will be
4825         * run to determine the months that have media items.  This query can be
4826         * expensive for large media libraries, so it may be desirable for sites to
4827         * override this behavior.
4828         *
4829         * @since 4.7.4
4830         *
4831         * @link https://core.trac.wordpress.org/ticket/31071
4832         *
4833         * @param stdClass[]|null $months An array of objects with `month` and `year`
4834         *                                properties, or `null` for default behavior.
4835         */
4836        $months = apply_filters( 'media_library_months_with_files', null );
4837        if ( ! is_array( $months ) ) {
4838                $months = $wpdb->get_results(
4839                        $wpdb->prepare(
4840                                "SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
4841                                FROM $wpdb->posts
4842                                WHERE post_type = %s
4843                                ORDER BY post_date DESC",
4844                                'attachment'
4845                        )
4846                );
4847        }
4848        foreach ( $months as $month_year ) {
4849                $month_year->text = sprintf(
4850                        /* translators: 1: Month, 2: Year. */
4851                        __( '%1$s %2$d' ),
4852                        $wp_locale->get_month( $month_year->month ),
4853                        $month_year->year
4854                );
4855        }
4856
4857        /**
4858         * Filters whether the Media Library grid has infinite scrolling. Default `false`.
4859         *
4860         * @since 5.8.0
4861         *
4862         * @param bool $infinite Whether the Media Library grid has infinite scrolling.
4863         */
4864        $infinite_scrolling = apply_filters( 'media_library_infinite_scrolling', false );
4865
4866        $settings = array(
4867                'tabs'              => $tabs,
4868                'tabUrl'            => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ),
4869                'mimeTypes'         => wp_list_pluck( get_post_mime_types(), 0 ),
4870                /** This filter is documented in wp-admin/includes/media.php */
4871                'captions'          => ! apply_filters( 'disable_captions', '' ),
4872                'nonce'             => array(
4873                        'sendToEditor'           => wp_create_nonce( 'media-send-to-editor' ),
4874                        'setAttachmentThumbnail' => wp_create_nonce( 'set-attachment-thumbnail' ),
4875                ),
4876                'post'              => array(
4877                        'id' => 0,
4878                ),
4879                'defaultProps'      => $props,
4880                'attachmentCounts'  => array(
4881                        'audio' => ( $show_audio_playlist ) ? 1 : 0,
4882                        'video' => ( $show_video_playlist ) ? 1 : 0,
4883                ),
4884                'oEmbedProxyUrl'    => rest_url( 'oembed/1.0/proxy' ),
4885                'embedExts'         => $exts,
4886                'embedMimes'        => $ext_mimes,
4887                'contentWidth'      => $content_width,
4888                'months'            => $months,
4889                'mediaTrash'        => MEDIA_TRASH ? 1 : 0,
4890                'infiniteScrolling' => ( $infinite_scrolling ) ? 1 : 0,
4891        );
4892
4893        $post = null;
4894        if ( isset( $args['post'] ) ) {
4895                $post             = get_post( $args['post'] );
4896                $settings['post'] = array(
4897                        'id'    => $post->ID,
4898                        'nonce' => wp_create_nonce( 'update-post_' . $post->ID ),
4899                );
4900
4901                $thumbnail_support = current_theme_supports( 'post-thumbnails', $post->post_type ) && post_type_supports( $post->post_type, 'thumbnail' );
4902                if ( ! $thumbnail_support && 'attachment' === $post->post_type && $post->post_mime_type ) {
4903                        if ( wp_attachment_is( 'audio', $post ) ) {
4904                                $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
4905                        } elseif ( wp_attachment_is( 'video', $post ) ) {
4906                                $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
4907                        }
4908                }
4909
4910                if ( $thumbnail_support ) {
4911                        $featured_image_id                   = get_post_meta( $post->ID, '_thumbnail_id', true );
4912                        $settings['post']['featuredImageId'] = $featured_image_id ? $featured_image_id : -1;
4913                }
4914        }
4915
4916        if ( $post ) {
4917                $post_type_object = get_post_type_object( $post->post_type );
4918        } else {
4919                $post_type_object = get_post_type_object( 'post' );
4920        }
4921
4922        $strings = array(
4923                // Generic.
4924                'mediaFrameDefaultTitle'      => __( 'Media' ),
4925                'url'                         => __( 'URL' ),
4926                'addMedia'                    => __( 'Add media' ),
4927                'search'                      => __( 'Search' ),
4928                'select'                      => __( 'Select' ),
4929                'cancel'                      => __( 'Cancel' ),
4930                'update'                      => __( 'Update' ),
4931                'replace'                     => __( 'Replace' ),
4932                'remove'                      => __( 'Remove' ),
4933                'back'                        => __( 'Back' ),
4934                /*
4935                 * translators: This is a would-be plural string used in the media manager.
4936                 * If there is not a word you can use in your language to avoid issues with the
4937                 * lack of plural support here, turn it into "selected: %d" then translate it.
4938                 */
4939                'selected'                    => __( '%d selected' ),
4940                'dragInfo'                    => __( 'Drag and drop to reorder media files.' ),
4941
4942                // Upload.
4943                'uploadFilesTitle'            => __( 'Upload files' ),
4944                'uploadImagesTitle'           => __( 'Upload images' ),
4945
4946                // Library.
4947                'mediaLibraryTitle'           => __( 'Media Library' ),
4948                'insertMediaTitle'            => __( 'Add media' ),
4949                'createNewGallery'            => __( 'Create a new gallery' ),
4950                'createNewPlaylist'           => __( 'Create a new playlist' ),
4951                'createNewVideoPlaylist'      => __( 'Create a new video playlist' ),
4952                'returnToLibrary'             => __( '&#8592; Go to library' ),
4953                'allMediaItems'               => __( 'All media items' ),
4954                'allDates'                    => __( 'All dates' ),
4955                'noItemsFound'                => __( 'No items found.' ),
4956                'insertIntoPost'              => $post_type_object->labels->insert_into_item,
4957                'unattached'                  => _x( 'Unattached', 'media items' ),
4958                'mine'                        => _x( 'Mine', 'media items' ),
4959                'trash'                       => _x( 'Trash', 'noun' ),
4960                'uploadedToThisPost'          => $post_type_object->labels->uploaded_to_this_item,
4961                'warnDelete'                  => __( "You are about to permanently delete this item from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ),
4962                'warnBulkDelete'              => __( "You are about to permanently delete these items from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ),
4963                'warnBulkTrash'               => __( "You are about to trash these items.\n  'Cancel' to stop, 'OK' to delete." ),
4964                'bulkSelect'                  => __( 'Bulk select' ),
4965                'trashSelected'               => __( 'Move to Trash' ),
4966                'restoreSelected'             => __( 'Restore from Trash' ),
4967                'deletePermanently'           => __( 'Delete permanently' ),
4968                'errorDeleting'               => __( 'Error in deleting the attachment.' ),
4969                'apply'                       => __( 'Apply' ),
4970                'filterByDate'                => __( 'Filter by date' ),
4971                'filterByType'                => __( 'Filter by type' ),
4972                'searchLabel'                 => __( 'Search media' ),
4973                'searchMediaLabel'            => __( 'Search media' ),          // Backward compatibility pre-5.3.
4974                'searchMediaPlaceholder'      => __( 'Search media items...' ), // Placeholder (no ellipsis), backward compatibility pre-5.3.
4975                /* translators: %d: Number of attachments found in a search. */
4976                'mediaFound'                  => __( 'Number of media items found: %d' ),
4977                'noMedia'                     => __( 'No media items found.' ),
4978                'noMediaTryNewSearch'         => __( 'No media items found. Try a different search.' ),
4979
4980                // Library Details.
4981                'attachmentDetails'           => __( 'Attachment details' ),
4982
4983                // From URL.
4984                'insertFromUrlTitle'          => __( 'Insert from URL' ),
4985
4986                // Featured Images.
4987                'setFeaturedImageTitle'       => $post_type_object->labels->featured_image,
4988                'setFeaturedImage'            => $post_type_object->labels->set_featured_image,
4989
4990                // Gallery.
4991                'createGalleryTitle'          => __( 'Create gallery' ),
4992                'editGalleryTitle'            => __( 'Edit gallery' ),
4993                'cancelGalleryTitle'          => __( '&#8592; Cancel gallery' ),
4994                'insertGallery'               => __( 'Insert gallery' ),
4995                'updateGallery'               => __( 'Update gallery' ),
4996                'addToGallery'                => __( 'Add to gallery' ),
4997                'addToGalleryTitle'           => __( 'Add to gallery' ),
4998                'reverseOrder'                => __( 'Reverse order' ),
4999
5000                // Edit Image.
5001                'imageDetailsTitle'           => __( 'Image details' ),
5002                'imageReplaceTitle'           => __( 'Replace image' ),
5003                'imageDetailsCancel'          => __( 'Cancel edit' ),
5004                'editImage'                   => __( 'Edit image' ),
5005
5006                // Crop Image.
5007                'chooseImage'                 => __( 'Choose image' ),
5008                'selectAndCrop'               => __( 'Select and crop' ),
5009                'skipCropping'                => __( 'Skip cropping' ),
5010                'cropImage'                   => __( 'Crop image' ),
5011                'cropYourImage'               => __( 'Crop your image' ),
5012                'cropping'                    => __( 'Cropping&hellip;' ),
5013                /* translators: 1: Suggested width number, 2: Suggested height number. */
5014                'suggestedDimensions'         => __( 'Suggested image dimensions: %1$s by %2$s pixels.' ),
5015                'cropError'                   => __( 'There has been an error cropping your image.' ),
5016
5017                // Edit Audio.
5018                'audioDetailsTitle'           => __( 'Audio details' ),
5019                'audioReplaceTitle'           => __( 'Replace audio' ),
5020                'audioAddSourceTitle'         => __( 'Add audio source' ),
5021                'audioDetailsCancel'          => __( 'Cancel edit' ),
5022
5023                // Edit Video.
5024                'videoDetailsTitle'           => __( 'Video details' ),
5025                'videoReplaceTitle'           => __( 'Replace video' ),
5026                'videoAddSourceTitle'         => __( 'Add video source' ),
5027                'videoDetailsCancel'          => __( 'Cancel edit' ),
5028                'videoSelectPosterImageTitle' => __( 'Select poster image' ),
5029                'videoAddTrackTitle'          => __( 'Add subtitles' ),
5030
5031                // Playlist.
5032                'playlistDragInfo'            => __( 'Drag and drop to reorder tracks.' ),
5033                'createPlaylistTitle'         => __( 'Create audio playlist' ),
5034                'editPlaylistTitle'           => __( 'Edit audio playlist' ),
5035                'cancelPlaylistTitle'         => __( '&#8592; Cancel audio playlist' ),
5036                'insertPlaylist'              => __( 'Insert audio playlist' ),
5037                'updatePlaylist'              => __( 'Update audio playlist' ),
5038                'addToPlaylist'               => __( 'Add to audio playlist' ),
5039                'addToPlaylistTitle'          => __( 'Add to Audio Playlist' ),
5040
5041                // Video Playlist.
5042                'videoPlaylistDragInfo'       => __( 'Drag and drop to reorder videos.' ),
5043                'createVideoPlaylistTitle'    => __( 'Create video playlist' ),
5044                'editVideoPlaylistTitle'      => __( 'Edit video playlist' ),
5045                'cancelVideoPlaylistTitle'    => __( '&#8592; Cancel video playlist' ),
5046                'insertVideoPlaylist'         => __( 'Insert video playlist' ),
5047                'updateVideoPlaylist'         => __( 'Update video playlist' ),
5048                'addToVideoPlaylist'          => __( 'Add to video playlist' ),
5049                'addToVideoPlaylistTitle'     => __( 'Add to video Playlist' ),
5050
5051                // Headings.
5052                'filterAttachments'           => __( 'Filter media' ),
5053                'attachmentsList'             => __( 'Media list' ),
5054        );
5055
5056        /**
5057         * Filters the media view settings.
5058         *
5059         * @since 3.5.0
5060         *
5061         * @param array   $settings List of media view settings.
5062         * @param WP_Post $post     Post object.
5063         */
5064        $settings = apply_filters( 'media_view_settings', $settings, $post );
5065
5066        /**
5067         * Filters the media view strings.
5068         *
5069         * @since 3.5.0
5070         *
5071         * @param string[] $strings Array of media view strings keyed by the name they'll be referenced by in JavaScript.
5072         * @param WP_Post  $post    Post object.
5073         */
5074        $strings = apply_filters( 'media_view_strings', $strings, $post );
5075
5076        $strings['settings'] = $settings;
5077
5078        /*
5079         * Ensure we enqueue media-editor first, that way media-views
5080         * is registered internally before we try to localize it. See #24724.
5081         */
5082        wp_enqueue_script( 'media-editor' );
5083        wp_localize_script( 'media-views', '_wpMediaViewsL10n', $strings );
5084
5085        wp_enqueue_script( 'media-audiovideo' );
5086        wp_enqueue_style( 'media-views' );
5087        if ( is_admin() ) {
5088                wp_enqueue_script( 'mce-view' );
5089                wp_enqueue_script( 'image-edit' );
5090        }
5091        wp_enqueue_style( 'imgareaselect' );
5092        wp_plupload_default_settings();
5093
5094        require_once ABSPATH . WPINC . '/media-template.php';
5095        add_action( 'admin_footer', 'wp_print_media_templates' );
5096        add_action( 'wp_footer', 'wp_print_media_templates' );
5097        add_action( 'customize_controls_print_footer_scripts', 'wp_print_media_templates' );
5098
5099        /**
5100         * Fires at the conclusion of wp_enqueue_media().
5101         *
5102         * @since 3.5.0
5103         */
5104        do_action( 'wp_enqueue_media' );
5105}
5106
5107/**
5108 * Retrieves media attached to the passed post.
5109 *
5110 * @since 3.6.0
5111 *
5112 * @param string      $type Mime type.
5113 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
5114 * @return WP_Post[] Array of media attached to the given post.
5115 */
5116function get_attached_media( $type, $post = 0 ) {
5117        $post = get_post( $post );
5118
5119        if ( ! $post ) {
5120                return array();
5121        }
5122
5123        $args = array(
5124                'post_parent'    => $post->ID,
5125                'post_type'      => 'attachment',
5126                'post_mime_type' => $type,
5127                'posts_per_page' => -1,
5128                'orderby'        => 'menu_order',
5129                'order'          => 'ASC',
5130        );
5131
5132        /**
5133         * Filters arguments used to retrieve media attached to the given post.
5134         *
5135         * @since 3.6.0
5136         *
5137         * @param array   $args Post query arguments.
5138         * @param string  $type Mime type of the desired media.
5139         * @param WP_Post $post Post object.
5140         */
5141        $args = apply_filters( 'get_attached_media_args', $args, $type, $post );
5142
5143        $children = get_children( $args );
5144
5145        /**
5146         * Filters the list of media attached to the given post.
5147         *
5148         * @since 3.6.0
5149         *
5150         * @param WP_Post[] $children Array of media attached to the given post.
5151         * @param string    $type     Mime type of the media desired.
5152         * @param WP_Post   $post     Post object.
5153         */
5154        return (array) apply_filters( 'get_attached_media', $children, $type, $post );
5155}
5156
5157/**
5158 * Checks the HTML content for an audio, video, object, embed, or iframe tags.
5159 *
5160 * @since 3.6.0
5161 *
5162 * @param string   $content A string of HTML which might contain media elements.
5163 * @param string[] $types   An array of media types: 'audio', 'video', 'object', 'embed', or 'iframe'.
5164 * @return string[] Array of found HTML media elements.
5165 */
5166function get_media_embedded_in_content( $content, $types = null ) {
5167        $html = array();
5168
5169        /**
5170         * Filters the embedded media types that are allowed to be returned from the content blob.
5171         *
5172         * @since 4.2.0
5173         *
5174         * @param string[] $allowed_media_types An array of allowed media types. Default media types are
5175         *                                      'audio', 'video', 'object', 'embed', and 'iframe'.
5176         */
5177        $allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe' ) );
5178
5179        if ( ! empty( $types ) ) {
5180                if ( ! is_array( $types ) ) {
5181                        $types = array( $types );
5182                }
5183
5184                $allowed_media_types = array_intersect( $allowed_media_types, $types );
5185        }
5186
5187        $tags = implode( '|', $allowed_media_types );
5188
5189        if ( preg_match_all( '#<(?P<tag>' . $tags . ')[^<]*?(?:>[\s\S]*?<\/(?P=tag)>|\s*\/>)#', $content, $matches ) ) {
5190                foreach ( $matches[0] as $match ) {
5191                        $html[] = $match;
5192                }
5193        }
5194
5195        return $html;
5196}
5197
5198/**
5199 * Retrieves galleries from the passed post's content.
5200 *
5201 * @since 3.6.0
5202 *
5203 * @param int|WP_Post $post Post ID or object.
5204 * @param bool        $html Optional. Whether to return HTML or data in the array. Default true.
5205 * @return array A list of arrays, each containing gallery data and srcs parsed
5206 *               from the expanded shortcode.
5207 */
5208function get_post_galleries( $post, $html = true ) {
5209        $post = get_post( $post );
5210
5211        if ( ! $post ) {
5212                return array();
5213        }
5214
5215        if ( ! has_shortcode( $post->post_content, 'gallery' ) && ! has_block( 'gallery', $post->post_content ) ) {
5216                return array();
5217        }
5218
5219        $galleries = array();
5220        if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $matches, PREG_SET_ORDER ) ) {
5221                foreach ( $matches as $shortcode ) {
5222                        if ( 'gallery' === $shortcode[2] ) {
5223                                $srcs = array();
5224
5225                                $shortcode_attrs = shortcode_parse_atts( $shortcode[3] );
5226
5227                                // Specify the post ID of the gallery we're viewing if the shortcode doesn't reference another post already.
5228                                if ( ! isset( $shortcode_attrs['id'] ) ) {
5229                                        $shortcode[3] .= ' id="' . (int) $post->ID . '"';
5230                                }
5231
5232                                $gallery = do_shortcode_tag( $shortcode );
5233                                if ( $html ) {
5234                                        $galleries[] = $gallery;
5235                                } else {
5236                                        preg_match_all( '#src=([\'"])(.+?)\1#is', $gallery, $src, PREG_SET_ORDER );
5237                                        if ( ! empty( $src ) ) {
5238                                                foreach ( $src as $s ) {
5239                                                        $srcs[] = $s[2];
5240                                                }
5241                                        }
5242
5243                                        $galleries[] = array_merge(
5244                                                $shortcode_attrs,
5245                                                array(
5246                                                        'src' => array_values( array_unique( $srcs ) ),
5247                                                )
5248                                        );
5249                                }
5250                        }
5251                }
5252        }
5253
5254        if ( has_block( 'gallery', $post->post_content ) ) {
5255                $post_blocks = parse_blocks( $post->post_content );
5256
5257                while ( $block = array_shift( $post_blocks ) ) {
5258                        $has_inner_blocks = ! empty( $block['innerBlocks'] );
5259
5260                        // Skip blocks with no blockName and no innerHTML.
5261                        if ( ! $block['blockName'] ) {
5262                                continue;
5263                        }
5264
5265                        // Skip non-Gallery blocks.
5266                        if ( 'core/gallery' !== $block['blockName'] ) {
5267                                // Move inner blocks into the root array before skipping.
5268                                if ( $has_inner_blocks ) {
5269                                        array_push( $post_blocks, ...$block['innerBlocks'] );
5270                                }
5271                                continue;
5272                        }
5273
5274                        // New Gallery block format as HTML.
5275                        if ( $has_inner_blocks && $html ) {
5276                                $block_html  = wp_list_pluck( $block['innerBlocks'], 'innerHTML' );
5277                                $galleries[] = '<figure>' . implode( ' ', $block_html ) . '</figure>';
5278                                continue;
5279                        }
5280
5281                        $srcs = array();
5282
5283                        // New Gallery block format as an array.
5284                        if ( $has_inner_blocks ) {
5285                                $attrs = wp_list_pluck( $block['innerBlocks'], 'attrs' );
5286                                $ids   = wp_list_pluck( $attrs, 'id' );
5287
5288                                foreach ( $ids as $id ) {
5289                                        $url = wp_get_attachment_url( $id );
5290
5291                                        if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) {
5292                                                $srcs[] = $url;
5293                                        }
5294                                }
5295
5296                                $galleries[] = array(
5297                                        'ids' => implode( ',', $ids ),
5298                                        'src' => $srcs,
5299                                );
5300
5301                                continue;
5302                        }
5303
5304                        // Old Gallery block format as HTML.
5305                        if ( $html ) {
5306                                $galleries[] = $block['innerHTML'];
5307                                continue;
5308                        }
5309
5310                        // Old Gallery block format as an array.
5311                        $ids = ! empty( $block['attrs']['ids'] ) ? $block['attrs']['ids'] : array();
5312
5313                        // If present, use the image IDs from the JSON blob as canonical.
5314                        if ( ! empty( $ids ) ) {
5315                                foreach ( $ids as $id ) {
5316                                        $url = wp_get_attachment_url( $id );
5317
5318                                        if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) {
5319                                                $srcs[] = $url;
5320                                        }
5321                                }
5322
5323                                $galleries[] = array(
5324                                        'ids' => implode( ',', $ids ),
5325                                        'src' => $srcs,
5326                                );
5327
5328                                continue;
5329                        }
5330
5331                        // Otherwise, extract srcs from the innerHTML.
5332                        preg_match_all( '#src=([\'"])(.+?)\1#is', $block['innerHTML'], $found_srcs, PREG_SET_ORDER );
5333
5334                        if ( ! empty( $found_srcs[0] ) ) {
5335                                foreach ( $found_srcs as $src ) {
5336                                        if ( isset( $src[2] ) && ! in_array( $src[2], $srcs, true ) ) {
5337                                                $srcs[] = $src[2];
5338                                        }
5339                                }
5340                        }
5341
5342                        $galleries[] = array( 'src' => $srcs );
5343                }
5344        }
5345
5346        /**
5347         * Filters the list of all found galleries in the given post.
5348         *
5349         * @since 3.6.0
5350         *
5351         * @param array   $galleries Associative array of all found post galleries.
5352         * @param WP_Post $post      Post object.
5353         */
5354        return apply_filters( 'get_post_galleries', $galleries, $post );
5355}
5356
5357/**
5358 * Checks a specified post's content for gallery and, if present, return the first
5359 *
5360 * @since 3.6.0
5361 *
5362 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
5363 * @param bool        $html Optional. Whether to return HTML or data. Default is true.
5364 * @return string|array Gallery data and srcs parsed from the expanded shortcode.
5365 */
5366function get_post_gallery( $post = 0, $html = true ) {
5367        $galleries = get_post_galleries( $post, $html );
5368        $gallery   = reset( $galleries );
5369
5370        /**
5371         * Filters the first-found post gallery.
5372         *
5373         * @since 3.6.0
5374         *
5375         * @param array       $gallery   The first-found post gallery.
5376         * @param int|WP_Post $post      Post ID or object.
5377         * @param array       $galleries Associative array of all found post galleries.
5378         */
5379        return apply_filters( 'get_post_gallery', $gallery, $post, $galleries );
5380}
5381
5382/**
5383 * Retrieves the image srcs from galleries from a post's content, if present.
5384 *
5385 * @since 3.6.0
5386 *
5387 * @see get_post_galleries()
5388 *
5389 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
5390 * @return array A list of lists, each containing image srcs parsed.
5391 *               from an expanded shortcode
5392 */
5393function get_post_galleries_images( $post = 0 ) {
5394        $galleries = get_post_galleries( $post, false );
5395        return wp_list_pluck( $galleries, 'src' );
5396}
5397
5398/**
5399 * Checks a post's content for galleries and return the image srcs for the first found gallery.
5400 *
5401 * @since 3.6.0
5402 *
5403 * @see get_post_gallery()
5404 *
5405 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
5406 * @return string[] A list of a gallery's image srcs in order.
5407 */
5408function get_post_gallery_images( $post = 0 ) {
5409        $gallery = get_post_gallery( $post, false );
5410        return empty( $gallery['src'] ) ? array() : $gallery['src'];
5411}
5412
5413/**
5414 * Maybe attempts to generate attachment metadata, if missing.
5415 *
5416 * @since 3.9.0
5417 *
5418 * @param WP_Post $attachment Attachment object.
5419 */
5420function wp_maybe_generate_attachment_metadata( $attachment ) {
5421        if ( empty( $attachment ) || empty( $attachment->ID ) ) {
5422                return;
5423        }
5424
5425        $attachment_id = (int) $attachment->ID;
5426        $file          = get_attached_file( $attachment_id );
5427        $meta          = wp_get_attachment_metadata( $attachment_id );
5428
5429        if ( empty( $meta ) && file_exists( $file ) ) {
5430                $_meta = get_post_meta( $attachment_id );
5431                $_lock = 'wp_generating_att_' . $attachment_id;
5432
5433                if ( ! array_key_exists( '_wp_attachment_metadata', $_meta ) && ! get_transient( $_lock ) ) {
5434                        set_transient( $_lock, $file );
5435                        wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
5436                        delete_transient( $_lock );
5437                }
5438        }
5439}
5440
5441/**
5442 * Tries to convert an attachment URL into a post ID.
5443 *
5444 * @since 4.0.0
5445 *
5446 * @global wpdb $wpdb WordPress database abstraction object.
5447 *
5448 * @param string $url The URL to resolve.
5449 * @return int The found post ID, or 0 on failure.
5450 */
5451function attachment_url_to_postid( $url ) {
5452        global $wpdb;
5453
5454        /**
5455         * Filters the attachment ID to allow short-circuit the function.
5456         *
5457         * Allows plugins to short-circuit attachment ID lookups. Plugins making
5458         * use of this function should return:
5459         *
5460         * - 0 (integer) to indicate the attachment is not found,
5461         * - attachment ID (integer) to indicate the attachment ID found,
5462         * - null to indicate WordPress should proceed with the lookup.
5463         *
5464         * Warning: The post ID may be null or zero, both of which cast to a
5465         * boolean false. For information about casting to booleans see the
5466         * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}.
5467         * Use the === operator for testing the post ID when developing filters using
5468         * this hook.
5469         *
5470         * @param int|null $post_id The result of the post ID lookup. Null to indicate
5471         *                          no lookup has been attempted. Default null.
5472         * @param string   $url     The URL being looked up.
5473         */
5474        $post_id = apply_filters( 'pre_attachment_url_to_postid', null, $url );
5475        if ( null !== $post_id ) {
5476                return (int) $post_id;
5477        }
5478
5479        $dir  = wp_get_upload_dir();
5480        $path = $url;
5481
5482        $site_url   = parse_url( $dir['url'] );
5483        $image_path = parse_url( $path );
5484
5485        // Force the protocols to match if needed.
5486        if ( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
5487                $path = str_replace( $image_path['scheme'], $site_url['scheme'], $path );
5488        }
5489
5490        if ( str_starts_with( $path, $dir['baseurl'] . '/' ) ) {
5491                $path = substr( $path, strlen( $dir['baseurl'] . '/' ) );
5492        }
5493
5494        $sql = $wpdb->prepare(
5495                "SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
5496                $path
5497        );
5498
5499        $results = $wpdb->get_results( $sql );
5500        $post_id = null;
5501
5502        if ( $results ) {
5503                // Use the first available result, but prefer a case-sensitive match, if exists.
5504                $post_id = reset( $results )->post_id;
5505
5506                if ( count( $results ) > 1 ) {
5507                        foreach ( $results as $result ) {
5508                                if ( $path === $result->meta_value ) {
5509                                        $post_id = $result->post_id;
5510                                        break;
5511                                }
5512                        }
5513                }
5514        }
5515
5516        /**
5517         * Filters an attachment ID found by URL.
5518         *
5519         * @since 4.2.0
5520         *
5521         * @param int|null $post_id The post_id (if any) found by the function.
5522         * @param string   $url     The URL being looked up.
5523         */
5524        return (int) apply_filters( 'attachment_url_to_postid', $post_id, $url );
5525}
5526
5527/**
5528 * Returns the URLs for CSS files used in an iframe-sandbox'd TinyMCE media view.
5529 *
5530 * @since 4.0.0
5531 *
5532 * @return string[] The relevant CSS file URLs.
5533 */
5534function wpview_media_sandbox_styles() {
5535        $version        = 'ver=' . get_bloginfo( 'version' );
5536        $mediaelement   = includes_url( "js/mediaelement/mediaelementplayer-legacy.min.css?$version" );
5537        $wpmediaelement = includes_url( "js/mediaelement/wp-mediaelement.css?$version" );
5538
5539        return array( $mediaelement, $wpmediaelement );
5540}
5541
5542/**
5543 * Registers the personal data exporter for media.
5544 *
5545 * @param array[] $exporters An array of personal data exporters, keyed by their ID.
5546 * @return array[] Updated array of personal data exporters.
5547 */
5548function wp_register_media_personal_data_exporter( $exporters ) {
5549        $exporters['wordpress-media'] = array(
5550                'exporter_friendly_name' => __( 'WordPress Media' ),
5551                'callback'               => 'wp_media_personal_data_exporter',
5552        );
5553
5554        return $exporters;
5555}
5556
5557/**
5558 * Finds and exports attachments associated with an email address.
5559 *
5560 * @since 4.9.6
5561 *
5562 * @param string $email_address The attachment owner email address.
5563 * @param int    $page          Attachment page number.
5564 * @return array {
5565 *     An array of personal data.
5566 *
5567 *     @type array[] $data An array of personal data arrays.
5568 *     @type bool    $done Whether the exporter is finished.
5569 * }
5570 */
5571function wp_media_personal_data_exporter( $email_address, $page = 1 ) {
5572        // Limit us to 50 attachments at a time to avoid timing out.
5573        $number = 50;
5574        $page   = (int) $page;
5575
5576        $data_to_export = array();
5577
5578        $user = get_user_by( 'email', $email_address );
5579        if ( false === $user ) {
5580                return array(
5581                        'data' => $data_to_export,
5582                        'done' => true,
5583                );
5584        }
5585
5586        $post_query = new WP_Query(
5587                array(
5588                        'author'         => $user->ID,
5589                        'posts_per_page' => $number,
5590                        'paged'          => $page,
5591                        'post_type'      => 'attachment',
5592                        'post_status'    => 'any',
5593                        'orderby'        => 'ID',
5594                        'order'          => 'ASC',
5595                )
5596        );
5597
5598        foreach ( (array) $post_query->posts as $post ) {
5599                $attachment_url = wp_get_attachment_url( $post->ID );
5600
5601                if ( $attachment_url ) {
5602                        $post_data_to_export = array(
5603                                array(
5604                                        'name'  => __( 'URL' ),
5605                                        'value' => $attachment_url,
5606                                ),
5607                        );
5608
5609                        $data_to_export[] = array(
5610                                'group_id'          => 'media',
5611                                'group_label'       => __( 'Media' ),
5612                                'group_description' => __( 'User&#8217;s media data.' ),
5613                                'item_id'           => "post-{$post->ID}",
5614                                'data'              => $post_data_to_export,
5615                        );
5616                }
5617        }
5618
5619        $done = $post_query->max_num_pages <= $page;
5620
5621        return array(
5622                'data' => $data_to_export,
5623                'done' => $done,
5624        );
5625}
5626
5627/**
5628 * Adds additional default image sub-sizes.
5629 *
5630 * These sizes are meant to enhance the way WordPress displays images on the front-end on larger,
5631 * high-density devices. They make it possible to generate more suitable `srcset` and `sizes` attributes
5632 * when the users upload large images.
5633 *
5634 * The sizes can be changed or removed by themes and plugins but that is not recommended.
5635 * The size "names" reflect the image dimensions, so changing the sizes would be quite misleading.
5636 *
5637 * @since 5.3.0
5638 * @access private
5639 */
5640function _wp_add_additional_image_sizes() {
5641        // 2x medium_large size.
5642        add_image_size( '1536x1536', 1536, 1536 );
5643        // 2x large size.
5644        add_image_size( '2048x2048', 2048, 2048 );
5645}
5646
5647/**
5648 * Callback to enable showing of the user error when uploading .heic images.
5649 *
5650 * @since 5.5.0
5651 * @since 6.7.0 The default behavior is to enable heic uploads as long as the server
5652 *              supports the format. The uploads are converted to JPEG's by default.
5653 *
5654 * @param array[] $plupload_settings The settings for Plupload.js.
5655 * @return array[] Modified settings for Plupload.js.
5656 */
5657function wp_show_heic_upload_error( $plupload_settings ) {
5658        // Check if HEIC images can be edited.
5659        if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
5660                $plupload_init['heic_upload_error'] = true;
5661        }
5662        return $plupload_settings;
5663}
5664
5665/**
5666 * Allows PHP's getimagesize() to be debuggable when necessary.
5667 *
5668 * @since 5.7.0
5669 * @since 5.8.0 Added support for WebP images.
5670 * @since 6.5.0 Added support for AVIF images.
5671 *
5672 * @param string $filename   The file path.
5673 * @param array  $image_info Optional. Extended image information (passed by reference).
5674 * @return array|false Array of image information or false on failure.
5675 */
5676function wp_getimagesize( $filename, ?array &$image_info = null ) {
5677        // Don't silence errors when in debug mode, unless running unit tests.
5678        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && ! defined( 'WP_RUN_CORE_TESTS' ) ) {
5679                if ( 2 === func_num_args() ) {
5680                        $info = getimagesize( $filename, $image_info );
5681                } else {
5682                        $info = getimagesize( $filename );
5683                }
5684        } else {
5685                /*
5686                 * Silencing notice and warning is intentional.
5687                 *
5688                 * getimagesize() has a tendency to generate errors, such as
5689                 * "corrupt JPEG data: 7191 extraneous bytes before marker",
5690                 * even when it's able to provide image size information.
5691                 *
5692                 * See https://core.trac.wordpress.org/ticket/42480
5693                 */
5694                if ( 2 === func_num_args() ) {
5695                        $info = @getimagesize( $filename, $image_info );
5696                } else {
5697                        $info = @getimagesize( $filename );
5698                }
5699        }
5700
5701        if (
5702                ! empty( $info ) &&
5703                // Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs.
5704                ! ( empty( $info[0] ) && empty( $info[1] ) )
5705        ) {
5706                return $info;
5707        }
5708
5709        $image_mime_type = wp_get_image_mime( $filename );
5710
5711        // Not an image?
5712        if ( false === $image_mime_type ) {
5713                return false;
5714        }
5715
5716        /*
5717         * For PHP versions that don't support WebP images,
5718         * extract the image size info from the file headers.
5719         */
5720        if ( 'image/webp' === $image_mime_type ) {
5721                $webp_info = wp_get_webp_info( $filename );
5722                $width     = $webp_info['width'];
5723                $height    = $webp_info['height'];
5724
5725                // Mimic the native return format.
5726                if ( $width && $height ) {
5727                        return array(
5728                                $width,
5729                                $height,
5730                                IMAGETYPE_WEBP,
5731                                sprintf(
5732                                        'width="%d" height="%d"',
5733                                        $width,
5734                                        $height
5735                                ),
5736                                'mime' => 'image/webp',
5737                        );
5738                }
5739        }
5740
5741        // For PHP versions that don't support AVIF images, extract the image size info from the file headers.
5742        if ( 'image/avif' === $image_mime_type ) {
5743                $avif_info = wp_get_avif_info( $filename );
5744
5745                $width  = $avif_info['width'];
5746                $height = $avif_info['height'];
5747
5748                // Mimic the native return format.
5749                if ( $width && $height ) {
5750                        return array(
5751                                $width,
5752                                $height,
5753                                IMAGETYPE_AVIF,
5754                                sprintf(
5755                                        'width="%d" height="%d"',
5756                                        $width,
5757                                        $height
5758                                ),
5759                                'mime' => 'image/avif',
5760                        );
5761                }
5762        }
5763
5764        // For PHP versions that don't support HEIC images, extract the size info using Imagick when available.
5765        if ( wp_is_heic_image_mime_type( $image_mime_type ) ) {
5766                $editor = wp_get_image_editor( $filename );
5767
5768                if ( is_wp_error( $editor ) ) {
5769                        return false;
5770                }
5771
5772                // If the editor for HEICs is Imagick, use it to get the image size.
5773                if ( $editor instanceof WP_Image_Editor_Imagick ) {
5774                        $size = $editor->get_size();
5775                        return array(
5776                                $size['width'],
5777                                $size['height'],
5778                                IMAGETYPE_HEIC,
5779                                sprintf(
5780                                        'width="%d" height="%d"',
5781                                        $size['width'],
5782                                        $size['height']
5783                                ),
5784                                'mime' => 'image/heic',
5785                        );
5786                }
5787        }
5788
5789        // The image could not be parsed.
5790        return false;
5791}
5792
5793/**
5794 * Extracts meta information about an AVIF file: width, height, bit depth, and number of channels.
5795 *
5796 * @since 6.5.0
5797 *
5798 * @param string $filename Path to an AVIF file.
5799 * @return array {
5800 *     An array of AVIF image information.
5801 *
5802 *     @type int|false $width        Image width on success, false on failure.
5803 *     @type int|false $height       Image height on success, false on failure.
5804 *     @type int|false $bit_depth    Image bit depth on success, false on failure.
5805 *     @type int|false $num_channels Image number of channels on success, false on failure.
5806 * }
5807 */
5808function wp_get_avif_info( $filename ) {
5809        $results = array(
5810                'width'        => false,
5811                'height'       => false,
5812                'bit_depth'    => false,
5813                'num_channels' => false,
5814        );
5815
5816        if ( 'image/avif' !== wp_get_image_mime( $filename ) ) {
5817                return $results;
5818        }
5819
5820        // Parse the file using libavifinfo's PHP implementation.
5821        require_once ABSPATH . WPINC . '/class-avif-info.php';
5822
5823        $handle = fopen( $filename, 'rb' );
5824        if ( $handle ) {
5825                $parser  = new Avifinfo\Parser( $handle );
5826                $success = $parser->parse_ftyp() && $parser->parse_file();
5827                fclose( $handle );
5828                if ( $success ) {
5829                        $results = $parser->features->primary_item_features;
5830                }
5831        }
5832        return $results;
5833}
5834
5835/**
5836 * Extracts meta information about a WebP file: width, height, and type.
5837 *
5838 * @since 5.8.0
5839 *
5840 * @param string $filename Path to a WebP file.
5841 * @return array {
5842 *     An array of WebP image information.
5843 *
5844 *     @type int|false    $width  Image width on success, false on failure.
5845 *     @type int|false    $height Image height on success, false on failure.
5846 *     @type string|false $type   The WebP type: one of 'lossy', 'lossless' or 'animated-alpha'.
5847 *                                False on failure.
5848 * }
5849 */
5850function wp_get_webp_info( $filename ) {
5851        $width  = false;
5852        $height = false;
5853        $type   = false;
5854
5855        if ( 'image/webp' !== wp_get_image_mime( $filename ) ) {
5856                return compact( 'width', 'height', 'type' );
5857        }
5858
5859        $magic = file_get_contents( $filename, false, null, 0, 40 );
5860
5861        if ( false === $magic ) {
5862                return compact( 'width', 'height', 'type' );
5863        }
5864
5865        // Make sure we got enough bytes.
5866        if ( strlen( $magic ) < 40 ) {
5867                return compact( 'width', 'height', 'type' );
5868        }
5869
5870        /*
5871         * The headers are a little different for each of the three formats.
5872         * Header values based on WebP docs, see https://developers.google.com/speed/webp/docs/riff_container.
5873         */
5874        switch ( substr( $magic, 12, 4 ) ) {
5875                // Lossy WebP.
5876                case 'VP8 ':
5877                        $parts  = unpack( 'v2', substr( $magic, 26, 4 ) );
5878                        $width  = (int) ( $parts[1] & 0x3FFF );
5879                        $height = (int) ( $parts[2] & 0x3FFF );
5880                        $type   = 'lossy';
5881                        break;
5882                // Lossless WebP.
5883                case 'VP8L':
5884                        $parts  = unpack( 'C4', substr( $magic, 21, 4 ) );
5885                        $width  = (int) ( $parts[1] | ( ( $parts[2] & 0x3F ) << 8 ) ) + 1;
5886                        $height = (int) ( ( ( $parts[2] & 0xC0 ) >> 6 ) | ( $parts[3] << 2 ) | ( ( $parts[4] & 0x03 ) << 10 ) ) + 1;
5887                        $type   = 'lossless';
5888                        break;
5889                // Animated/alpha WebP.
5890                case 'VP8X':
5891                        // Pad 24-bit int.
5892                        $width = unpack( 'V', substr( $magic, 24, 3 ) . "\x00" );
5893                        $width = (int) ( $width[1] & 0xFFFFFF ) + 1;
5894                        // Pad 24-bit int.
5895                        $height = unpack( 'V', substr( $magic, 27, 3 ) . "\x00" );
5896                        $height = (int) ( $height[1] & 0xFFFFFF ) + 1;
5897                        $type   = 'animated-alpha';
5898                        break;
5899        }
5900
5901        return compact( 'width', 'height', 'type' );
5902}
5903
5904/**
5905 * Gets loading optimization attributes.
5906 *
5907 * This function returns an array of attributes that should be merged into the given attributes array to optimize
5908 * loading performance. Potential attributes returned by this function are:
5909 * - `loading` attribute with a value of "lazy"
5910 * - `fetchpriority` attribute with a value of "high"
5911 * - `decoding` attribute with a value of "async"
5912 *
5913 * If any of these attributes are already present in the given attributes, they will not be modified. Note that no
5914 * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case
5915 * both attributes are present with those values.
5916 *
5917 * @since 6.3.0
5918 *
5919 * @global WP_Query $wp_query WordPress Query object.
5920 *
5921 * @param string $tag_name The tag name.
5922 * @param array  $attr     Array of the attributes for the tag.
5923 * @param string $context  Context for the element for which the loading optimization attribute is requested.
5924 * @return array Loading optimization attributes.
5925 */
5926function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
5927        global $wp_query;
5928
5929        /**
5930         * Filters whether to short-circuit loading optimization attributes.
5931         *
5932         * Returning an array from the filter will effectively short-circuit the loading of optimization attributes,
5933         * returning that value instead.
5934         *
5935         * @since 6.4.0
5936         *
5937         * @param array|false $loading_attrs False by default, or array of loading optimization attributes to short-circuit.
5938         * @param string      $tag_name      The tag name.
5939         * @param array       $attr          Array of the attributes for the tag.
5940         * @param string      $context       Context for the element for which the loading optimization attribute is requested.
5941         */
5942        $loading_attrs = apply_filters( 'pre_wp_get_loading_optimization_attributes', false, $tag_name, $attr, $context );
5943
5944        if ( is_array( $loading_attrs ) ) {
5945                return $loading_attrs;
5946        }
5947
5948        $loading_attrs = array();
5949
5950        /*
5951         * Skip lazy-loading for the overall block template, as it is handled more granularly.
5952         * The skip is also applicable for `fetchpriority`.
5953         */
5954        if ( 'template' === $context ) {
5955                /** This filter is documented in wp-includes/media.php */
5956                return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
5957        }
5958
5959        // For now this function only supports images and iframes.
5960        if ( 'img' !== $tag_name && 'iframe' !== $tag_name ) {
5961                /** This filter is documented in wp-includes/media.php */
5962                return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
5963        }
5964
5965        /*
5966         * Skip programmatically created images within content blobs as they need to be handled together with the other
5967         * images within the post content or widget content.
5968         * Without this clause, they would already be considered within their own context which skews the image count and
5969         * can result in the first post content image being lazy-loaded or an image further down the page being marked as a
5970         * high priority.
5971         */
5972        if (
5973                'the_content' !== $context && doing_filter( 'the_content' ) ||
5974                'widget_text_content' !== $context && doing_filter( 'widget_text_content' ) ||
5975                'widget_block_content' !== $context && doing_filter( 'widget_block_content' )
5976        ) {
5977                /** This filter is documented in wp-includes/media.php */
5978                return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
5979
5980        }
5981
5982        /*
5983         * Add `decoding` with a value of "async" for every image unless it has a
5984         * conflicting `decoding` attribute already present.
5985         */
5986        if ( 'img' === $tag_name ) {
5987                if ( isset( $attr['decoding'] ) ) {
5988                        $loading_attrs['decoding'] = $attr['decoding'];
5989                } else {
5990                        $loading_attrs['decoding'] = 'async';
5991                }
5992        }
5993
5994        // For any resources, width and height must be provided, to avoid layout shifts.
5995        if ( ! isset( $attr['width'], $attr['height'] ) ) {
5996                /** This filter is documented in wp-includes/media.php */
5997                return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
5998        }
5999
6000        /*
6001         * The key function logic starts here.
6002         */
6003        $maybe_in_viewport    = null;
6004        $increase_count       = false;
6005        $maybe_increase_count = false;
6006
6007        // Logic to handle a `loading` attribute that is already provided.
6008        if ( isset( $attr['loading'] ) ) {
6009                /*
6010                 * Interpret "lazy" as not in viewport. Any other value can be
6011                 * interpreted as in viewport (realistically only "eager" or `false`
6012                 * to force-omit the attribute are other potential values).
6013                 */
6014                if ( 'lazy' === $attr['loading'] ) {
6015                        $maybe_in_viewport = false;
6016                } else {
6017                        $maybe_in_viewport = true;
6018                }
6019        }
6020
6021        // Logic to handle a `fetchpriority` attribute that is already provided.
6022        if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) {
6023                /*
6024                 * If the image was already determined to not be in the viewport (e.g.
6025                 * from an already provided `loading` attribute), trigger a warning.
6026                 * Otherwise, the value can be interpreted as in viewport, since only
6027                 * the most important in-viewport image should have `fetchpriority` set
6028                 * to "high".
6029                 */
6030                if ( false === $maybe_in_viewport ) {
6031                        _doing_it_wrong(
6032                                __FUNCTION__,
6033                                __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ),
6034                                '6.3.0'
6035                        );
6036                        /*
6037                         * Set `fetchpriority` here for backward-compatibility as we should
6038                         * not override what a developer decided, even though it seems
6039                         * incorrect.
6040                         */
6041                        $loading_attrs['fetchpriority'] = 'high';
6042                } else {
6043                        $maybe_in_viewport = true;
6044                }
6045        }
6046
6047        if ( null === $maybe_in_viewport ) {
6048                $header_enforced_contexts = array(
6049                        'template_part_' . WP_TEMPLATE_PART_AREA_HEADER => true,
6050                        'get_header_image_tag' => true,
6051                );
6052
6053                /**
6054                 * Filters the header-specific contexts.
6055                 *
6056                 * @since 6.4.0
6057                 *
6058                 * @param array $default_header_enforced_contexts Map of contexts for which elements should be considered
6059                 *                                                in the header of the page, as $context => $enabled
6060                 *                                                pairs. The $enabled should always be true.
6061                 */
6062                $header_enforced_contexts = apply_filters( 'wp_loading_optimization_force_header_contexts', $header_enforced_contexts );
6063
6064                // Consider elements with these header-specific contexts to be in viewport.
6065                if ( isset( $header_enforced_contexts[ $context ] ) ) {
6066                        $maybe_in_viewport    = true;
6067                        $maybe_increase_count = true;
6068                } elseif ( ! is_admin() && in_the_loop() && is_main_query() ) {
6069                        /*
6070                         * Get the content media count, since this is a main query
6071                         * content element. This is accomplished by "increasing"
6072                         * the count by zero, as the only way to get the count is
6073                         * to call this function.
6074                         * The actual count increase happens further below, based
6075                         * on the `$increase_count` flag set here.
6076                         */
6077                        $content_media_count = wp_increase_content_media_count( 0 );
6078                        $increase_count      = true;
6079
6080                        // If the count so far is below the threshold, `loading` attribute is omitted.
6081                        if ( $content_media_count < wp_omit_loading_attr_threshold() ) {
6082                                $maybe_in_viewport = true;
6083                        } else {
6084                                $maybe_in_viewport = false;
6085                        }
6086                } elseif (
6087                        // Only apply for main query but before the loop.
6088                        $wp_query->before_loop && $wp_query->is_main_query()
6089                        /*
6090                         * Any image before the loop, but after the header has started should not be lazy-loaded,
6091                         * except when the footer has already started which can happen when the current template
6092                         * does not include any loop.
6093                         */
6094                        && did_action( 'get_header' ) && ! did_action( 'get_footer' )
6095                        ) {
6096                        $maybe_in_viewport    = true;
6097                        $maybe_increase_count = true;
6098                }
6099        }
6100
6101        /*
6102         * If the element is in the viewport (`true`), potentially add
6103         * `fetchpriority` with a value of "high". Otherwise, i.e. if the element
6104         * is not not in the viewport (`false`) or it is unknown (`null`), add
6105         * `loading` with a value of "lazy".
6106         */
6107        if ( $maybe_in_viewport ) {
6108                $loading_attrs = wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr );
6109        } else {
6110                // Only add `loading="lazy"` if the feature is enabled.
6111                if ( wp_lazy_loading_enabled( $tag_name, $context ) ) {
6112                        $loading_attrs['loading'] = 'lazy';
6113                }
6114        }
6115
6116        /*
6117         * If flag was set based on contextual logic above, increase the content
6118         * media count, either unconditionally, or based on whether the image size
6119         * is larger than the threshold.
6120         */
6121        if ( $increase_count ) {
6122                wp_increase_content_media_count();
6123        } elseif ( $maybe_increase_count ) {
6124                /** This filter is documented in wp-includes/media.php */
6125                $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );
6126
6127                if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
6128                        wp_increase_content_media_count();
6129                }
6130        }
6131
6132        /**
6133         * Filters the loading optimization attributes.
6134         *
6135         * @since 6.4.0
6136         *
6137         * @param array  $loading_attrs The loading optimization attributes.
6138         * @param string $tag_name      The tag name.
6139         * @param array  $attr          Array of the attributes for the tag.
6140         * @param string $context       Context for the element for which the loading optimization attribute is requested.
6141         */
6142        return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
6143}
6144
6145/**
6146 * Gets the threshold for how many of the first content media elements to not lazy-load.
6147 *
6148 * This function runs the {@see 'wp_omit_loading_attr_threshold'} filter, which uses a default threshold value of 3.
6149 * The filter is only run once per page load, unless the `$force` parameter is used.
6150 *
6151 * @since 5.9.0
6152 *
6153 * @param bool $force Optional. If set to true, the filter will be (re-)applied even if it already has been before.
6154 *                    Default false.
6155 * @return int The number of content media elements to not lazy-load.
6156 */
6157function wp_omit_loading_attr_threshold( $force = false ) {
6158        static $omit_threshold;
6159
6160        // This function may be called multiple times. Run the filter only once per page load.
6161        if ( ! isset( $omit_threshold ) || $force ) {
6162                /**
6163                 * Filters the threshold for how many of the first content media elements to not lazy-load.
6164                 *
6165                 * For these first content media elements, the `loading` attribute will be omitted. By default, this is the case
6166                 * for only the very first content media element.
6167                 *
6168                 * @since 5.9.0
6169                 * @since 6.3.0 The default threshold was changed from 1 to 3.
6170                 *
6171                 * @param int $omit_threshold The number of media elements where the `loading` attribute will not be added. Default 3.
6172                 */
6173                $omit_threshold = apply_filters( 'wp_omit_loading_attr_threshold', 3 );
6174        }
6175
6176        return $omit_threshold;
6177}
6178
6179/**
6180 * Increases an internal content media count variable.
6181 *
6182 * @since 5.9.0
6183 * @access private
6184 *
6185 * @param int $amount Optional. Amount to increase by. Default 1.
6186 * @return int The latest content media count, after the increase.
6187 */
6188function wp_increase_content_media_count( $amount = 1 ) {
6189        static $content_media_count = 0;
6190
6191        $content_media_count += $amount;
6192
6193        return $content_media_count;
6194}
6195
6196/**
6197 * Determines whether to add `fetchpriority='high'` to loading attributes.
6198 *
6199 * @since 6.3.0
6200 * @access private
6201 *
6202 * @param array  $loading_attrs Array of the loading optimization attributes for the element.
6203 * @param string $tag_name      The tag name.
6204 * @param array  $attr          Array of the attributes for the element.
6205 * @return array Updated loading optimization attributes for the element.
6206 */
6207function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ) {
6208        // For now, adding `fetchpriority="high"` is only supported for images.
6209        if ( 'img' !== $tag_name ) {
6210                return $loading_attrs;
6211        }
6212
6213        if ( isset( $attr['fetchpriority'] ) ) {
6214                /*
6215                 * While any `fetchpriority` value could be set in `$loading_attrs`,
6216                 * for consistency we only do it for `fetchpriority="high"` since that
6217                 * is the only possible value that WordPress core would apply on its
6218                 * own.
6219                 */
6220                if ( 'high' === $attr['fetchpriority'] ) {
6221                        $loading_attrs['fetchpriority'] = 'high';
6222                        wp_high_priority_element_flag( false );
6223                }
6224
6225                return $loading_attrs;
6226        }
6227
6228        // Lazy-loading and `fetchpriority="high"` are mutually exclusive.
6229        if ( isset( $loading_attrs['loading'] ) && 'lazy' === $loading_attrs['loading'] ) {
6230                return $loading_attrs;
6231        }
6232
6233        if ( ! wp_high_priority_element_flag() ) {
6234                return $loading_attrs;
6235        }
6236
6237        /**
6238         * Filters the minimum square-pixels threshold for an image to be eligible as the high-priority image.
6239         *
6240         * @since 6.3.0
6241         *
6242         * @param int $threshold Minimum square-pixels threshold. Default 50000.
6243         */
6244        $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );
6245
6246        if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
6247                $loading_attrs['fetchpriority'] = 'high';
6248                wp_high_priority_element_flag( false );
6249        }
6250
6251        return $loading_attrs;
6252}
6253
6254/**
6255 * Accesses a flag that indicates if an element is a possible candidate for `fetchpriority='high'`.
6256 *
6257 * @since 6.3.0
6258 * @access private
6259 *
6260 * @param bool $value Optional. Used to change the static variable. Default null.
6261 * @return bool Returns true if high-priority element was marked already, otherwise false.
6262 */
6263function wp_high_priority_element_flag( $value = null ) {
6264        static $high_priority_element = true;
6265
6266        if ( is_bool( $value ) ) {
6267                $high_priority_element = $value;
6268        }
6269
6270        return $high_priority_element;
6271}
6272
6273/**
6274 * Determines the output format for the image editor.
6275 *
6276 * @since 6.7.0
6277 * @access private
6278 *
6279 * @param string $filename  Path to the image.
6280 * @param string $mime_type The source image mime type.
6281 * @return string[] An array of mime type mappings.
6282 */
6283function wp_get_image_editor_output_format( $filename, $mime_type ) {
6284        $output_format = array(
6285                'image/heic'          => 'image/jpeg',
6286                'image/heif'          => 'image/jpeg',
6287                'image/heic-sequence' => 'image/jpeg',
6288                'image/heif-sequence' => 'image/jpeg',
6289        );
6290
6291        /**
6292         * Filters the image editor output format mapping.
6293         *
6294         * Enables filtering the mime type used to save images. By default HEIC/HEIF images
6295         * are converted to JPEGs.
6296         *
6297         * @see WP_Image_Editor::get_output_format()
6298         *
6299         * @since 5.8.0
6300         * @since 6.7.0 The default was changed from an empty array to an array
6301         *              containing the HEIC/HEIF images mime types.
6302         *
6303         * @param string[] $output_format {
6304         *     An array of mime type mappings. Maps a source mime type to a new
6305         *     destination mime type. By default maps HEIC/HEIF input to JPEG output.
6306         *
6307         *     @type string ...$0 The new mime type.
6308         * }
6309         * @param string $filename  Path to the image.
6310         * @param string $mime_type The source image mime type.
6311         */
6312        return apply_filters( 'image_editor_output_format', $output_format, $filename, $mime_type );
6313}
Note: See TracBrowser for help on using the repository browser.