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

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1