PluginProbe ʕ •ᴥ•ʔ
Modern Image Formats / 1.1.1
Modern Image Formats v1.1.1
2.7.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 2.0.0 2.0.1 2.0.2 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.5.1 2.6.0 2.6.1
webp-uploads / helper.php
webp-uploads Last commit date
deprecated.php 2 years ago fallback.js 2 years ago helper.php 2 years ago hooks.php 2 years ago image-edit.php 2 years ago load.php 2 years ago readme.txt 2 years ago rest-api.php 2 years ago settings.php 2 years ago uninstall.php 2 years ago
helper.php
325 lines
1 <?php
2 /**
3 * Helper functions used for Modern Image Formats.
4 *
5 * @package webp-uploads
6 *
7 * @since 1.0.0
8 */
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 /**
15 * Returns an array with the list of valid mime types that a specific mime type can be converted into it,
16 * for example an image/jpeg can be converted into an image/webp.
17 *
18 * @since 1.0.0
19 *
20 * @return array<string, array<string>> An array of valid mime types, where the key is the mime type and the value is the extension type.
21 */
22 function webp_uploads_get_upload_image_mime_transforms(): array {
23 $default_transforms = array(
24 'image/jpeg' => array( 'image/webp' ),
25 'image/webp' => array( 'image/webp' ),
26 );
27
28 // Check setting for whether to generate both JPEG and WebP.
29 if ( true === (bool) get_option( 'perflab_generate_webp_and_jpeg' ) ) {
30 $default_transforms = array(
31 'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
32 'image/webp' => array( 'image/webp', 'image/jpeg' ),
33 );
34 }
35
36 /**
37 * Filter to allow the definition of a custom mime types, in which a defined mime type
38 * can be transformed and provide a wide range of mime types.
39 *
40 * The order of supported mime types matters. If the original mime type of the uploaded image
41 * is not needed, then the first mime type in the list supported by the image editor will be
42 * selected for the default subsizes.
43 *
44 * @since 1.0.0
45 *
46 * @param array $default_transforms A map with the valid mime transforms.
47 */
48 $transforms = apply_filters( 'webp_uploads_upload_image_mime_transforms', $default_transforms );
49
50 // Return the default mime transforms if a non-array result is returned from the filter.
51 if ( ! is_array( $transforms ) ) {
52 return $default_transforms;
53 }
54
55 // Ensure that all mime types have correct transforms. If a mime type has invalid transforms array,
56 // then fallback to the original mime type to make sure that the correct subsizes are created.
57 foreach ( $transforms as $mime_type => $transform_types ) {
58 if ( ! is_array( $transform_types ) || empty( $transform_types ) ) {
59 $transforms[ $mime_type ] = array( $mime_type );
60 }
61 }
62
63 return $transforms;
64 }
65
66 /**
67 * Creates a resized image with the provided dimensions out of an original attachment, the created image
68 * would be saved in the specified mime and stored in the destination file. If the image can't be saved correctly
69 * a WP_Error would be returned otherwise an array with the file and filesize properties.
70 *
71 * @since 1.0.0
72 * @access private
73 *
74 * @param int $attachment_id The ID of the attachment from where this image would be created.
75 * @param string $image_size The size name that would be used to create the image source, out of the registered subsizes.
76 * @param array{ width: int, height: int, crop: bool } $size_data An array with the dimensions of the image: height, width and crop.
77 * @param string $mime The target mime in which the image should be created.
78 * @param string|null $destination_file_name The path where the file would be stored, including the extension. If null, `generate_filename` is used to create the destination file name.
79 *
80 * @return array{ file: string, filesize: int }|WP_Error An array with the file and filesize if the image was created correctly, otherwise a WP_Error.
81 */
82 function webp_uploads_generate_additional_image_source( int $attachment_id, string $image_size, array $size_data, string $mime, ?string $destination_file_name = null ) {
83 /**
84 * Filter to allow the generation of additional image sources, in which a defined mime type
85 * can be transformed and create additional mime types for the file.
86 *
87 * Returning an image data array or WP_Error here effectively short-circuits the default logic to generate the image source.
88 *
89 * @since 1.1.0
90 *
91 * @param array{
92 * file: string,
93 * path?: string,
94 * filesize?: int
95 * }|null|WP_Error $image Image data, null, or WP_Error.
96 * @param int $attachment_id The ID of the attachment from where this image would be created.
97 * @param string $image_size The size name that would be used to create this image, out of the registered subsizes.
98 * @param array{
99 * width: int,
100 * height: int,
101 * crop: bool
102 * } $size_data An array with the dimensions of the image.
103 * @param string $mime The target mime in which the image should be created.
104 */
105 $image = apply_filters( 'webp_uploads_pre_generate_additional_image_source', null, $attachment_id, $image_size, $size_data, $mime );
106 if ( is_wp_error( $image ) ) {
107 return $image;
108 }
109 if ( is_array( $image ) && array_key_exists( 'file', $image ) && is_string( $image['file'] ) ) {
110 // The filtered image provided all we need to short-circuit here.
111 if ( array_key_exists( 'filesize', $image ) && is_int( $image['filesize'] ) && $image['filesize'] > 0 ) {
112 return $image;
113 }
114
115 // Supply the filesize based on the filter-provided path.
116 if ( array_key_exists( 'path', $image ) && is_int( $image['path'] ) ) {
117 $filesize = wp_filesize( $image['path'] );
118 if ( $filesize > 0 ) {
119 return array(
120 'file' => $image['file'],
121 'filesize' => $filesize,
122 );
123 }
124 }
125 }
126
127 $allowed_mimes = array_flip( wp_get_mime_types() );
128 if ( ! isset( $allowed_mimes[ $mime ] ) || ! is_string( $allowed_mimes[ $mime ] ) ) {
129 return new WP_Error( 'image_mime_type_invalid', __( 'The provided mime type is not allowed.', 'webp-uploads' ) );
130 }
131
132 if ( ! wp_image_editor_supports( array( 'mime_type' => $mime ) ) ) {
133 return new WP_Error( 'image_mime_type_not_supported', __( 'The provided mime type is not supported.', 'webp-uploads' ) );
134 }
135
136 $image_path = wp_get_original_image_path( $attachment_id );
137 if ( ! $image_path || ! file_exists( $image_path ) ) {
138 return new WP_Error( 'original_image_file_not_found', __( 'The original image file does not exists, subsizes are created out of the original image.', 'webp-uploads' ) );
139 }
140
141 $editor = wp_get_image_editor( $image_path, array( 'mime_type' => $mime ) );
142 if ( is_wp_error( $editor ) ) {
143 return $editor;
144 }
145
146 $height = isset( $size_data['height'] ) ? (int) $size_data['height'] : 0;
147 $width = isset( $size_data['width'] ) ? (int) $size_data['width'] : 0;
148 $crop = isset( $size_data['crop'] ) && $size_data['crop'];
149 if ( $width <= 0 && $height <= 0 ) {
150 return new WP_Error( 'image_wrong_dimensions', __( 'At least one of the dimensions must be a positive number.', 'webp-uploads' ) );
151 }
152
153 $image_meta = wp_get_attachment_metadata( $attachment_id );
154 // If stored EXIF data exists, rotate the source image before creating sub-sizes.
155 if ( ! empty( $image_meta['image_meta'] ) ) {
156 $editor->maybe_exif_rotate();
157 }
158
159 $editor->resize( $width, $height, $crop );
160
161 if ( null === $destination_file_name ) {
162 $ext = pathinfo( $image_path, PATHINFO_EXTENSION );
163 $suffix = $editor->get_suffix();
164 $suffix .= "-{$ext}";
165 $extension = explode( '|', $allowed_mimes[ $mime ] );
166 $destination_file_name = $editor->generate_filename( $suffix, null, $extension[0] );
167 }
168
169 remove_filter( 'image_editor_output_format', 'webp_uploads_filter_image_editor_output_format', 10 );
170 $image = $editor->save( $destination_file_name, $mime );
171 add_filter( 'image_editor_output_format', 'webp_uploads_filter_image_editor_output_format', 10, 3 );
172
173 if ( is_wp_error( $image ) ) {
174 return $image;
175 }
176
177 if ( empty( $image['file'] ) ) {
178 return new WP_Error( 'image_file_not_present', __( 'The file key is not present on the image data', 'webp-uploads' ) );
179 }
180
181 return array(
182 'file' => $image['file'],
183 'filesize' => isset( $image['path'] ) ? wp_filesize( $image['path'] ) : 0,
184 );
185 }
186
187 /**
188 * Creates a new image based of the specified attachment with a defined mime type
189 * this image would be stored in the same place as the provided size name inside the
190 * metadata of the attachment.
191 *
192 * @since 1.0.0
193 *
194 * @see wp_create_image_subsizes()
195 *
196 * @param int $attachment_id The ID of the attachment we are going to use as a reference to create the image.
197 * @param string $size The size name that would be used to create this image, out of the registered subsizes.
198 * @param string $mime A mime type we are looking to use to create this image.
199 *
200 * @return array{ file: string, filesize: int }|WP_Error
201 */
202 function webp_uploads_generate_image_size( int $attachment_id, string $size, string $mime ) {
203 $sizes = wp_get_registered_image_subsizes();
204 $metadata = wp_get_attachment_metadata( $attachment_id );
205
206 if (
207 ! isset( $metadata['sizes'][ $size ], $sizes[ $size ] )
208 || ! is_array( $metadata['sizes'][ $size ] )
209 || ! is_array( $sizes[ $size ] )
210 ) {
211 return new WP_Error( 'image_mime_type_invalid_metadata', __( 'The image does not have a valid metadata.', 'webp-uploads' ) );
212 }
213
214 $size_data = array(
215 'width' => 0,
216 'height' => 0,
217 'crop' => false,
218 );
219
220 if ( isset( $sizes[ $size ]['width'] ) ) {
221 $size_data['width'] = $sizes[ $size ]['width'];
222 } elseif ( isset( $metadata['sizes'][ $size ]['width'] ) ) {
223 $size_data['width'] = $metadata['sizes'][ $size ]['width'];
224 }
225
226 if ( isset( $sizes[ $size ]['height'] ) ) {
227 $size_data['height'] = $sizes[ $size ]['height'];
228 } elseif ( isset( $metadata['sizes'][ $size ]['height'] ) ) {
229 $size_data['height'] = $metadata['sizes'][ $size ]['height'];
230 }
231
232 if ( isset( $sizes[ $size ]['crop'] ) ) {
233 $size_data['crop'] = (bool) $sizes[ $size ]['crop'];
234 }
235
236 return webp_uploads_generate_additional_image_source( $attachment_id, $size, $size_data, $mime );
237 }
238
239 /**
240 * Returns mime types that should be used for an image in the specific context.
241 *
242 * @since 1.0.0
243 *
244 * @param int $attachment_id The attachment ID.
245 * @param string $context The current context.
246 * @return string[] Mime types to use for the image.
247 */
248 function webp_uploads_get_content_image_mimes( int $attachment_id, string $context ): array {
249 $target_mimes = array( 'image/webp', 'image/jpeg' );
250
251 /**
252 * Filters mime types that should be used to update all images in the content. The order of
253 * mime types matters. The first mime type in the list will be used if it is supported by an image.
254 *
255 * @since 1.0.0
256 *
257 * @param array $target_mimes The list of mime types that can be used to update images in the content.
258 * @param int $attachment_id The attachment ID.
259 * @param string $context The current context.
260 */
261 $target_mimes = apply_filters( 'webp_uploads_content_image_mimes', $target_mimes, $attachment_id, $context );
262 if ( ! is_array( $target_mimes ) ) {
263 $target_mimes = array();
264 }
265
266 return $target_mimes;
267 }
268
269 /**
270 * Verifies if the request is for a frontend context within the <body> tag.
271 *
272 * @since 1.0.0
273 *
274 * @global WP_Query $wp_query WordPress Query object.
275 *
276 * @return bool True if in the <body> within a frontend request, false otherwise.
277 */
278 function webp_uploads_in_frontend_body(): bool {
279 global $wp_query;
280
281 // Check if this request is generally outside (or before) any frontend context.
282 if ( ! isset( $wp_query ) || defined( 'REST_REQUEST' ) || defined( 'XMLRPC_REQUEST' ) || is_feed() ) {
283 return false;
284 }
285
286 // Check if we're anywhere before 'template_redirect' or within the 'wp_head' action.
287 if ( ! did_action( 'template_redirect' ) || doing_action( 'wp_head' ) ) {
288 return false;
289 }
290
291 return true;
292 }
293
294 /**
295 * Check whether the additional image is larger than the original image.
296 *
297 * @since 1.0.0
298 *
299 * @param array{ filesize?: int } $original An array with the metadata of the attachment.
300 * @param array{ filesize?: int } $additional An array containing the filename and file size for additional mime.
301 * @return bool True if the additional image is larger than the original image, otherwise false.
302 */
303 function webp_uploads_should_discard_additional_image_file( array $original, array $additional ): bool {
304 $original_image_filesize = isset( $original['filesize'] ) ? (int) $original['filesize'] : 0;
305 $additional_image_filesize = isset( $additional['filesize'] ) ? (int) $additional['filesize'] : 0;
306 if ( $original_image_filesize > 0 && $additional_image_filesize > 0 ) {
307 /**
308 * Filter whether WebP images that are larger than the matching JPEG should be discarded.
309 *
310 * By default the performance lab plugin will use the mime type with the smaller filesize
311 * rather than defaulting to `webp`.
312 *
313 * @since 1.0.0
314 *
315 * @param bool $preferred_filesize Prioritize file size over mime type. Default true.
316 */
317 $webp_discard_larger_images = apply_filters( 'webp_uploads_discard_larger_generated_images', true );
318
319 if ( $webp_discard_larger_images && $additional_image_filesize >= $original_image_filesize ) {
320 return true;
321 }
322 }
323 return false;
324 }
325