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