PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / classes / Optimization / Process / WP.php

WP.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at classes/Optimization/Process/WP.php

298 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\Optimization\Process;
3
4 use Imagify\Optimization\File;
5 use WP_Error;
6
7 /**
8 * Optimization class for the attachments in the WP library.
9 * This class constructor accepts:
10 * - A post ID (int).
11 * - A \WP_Post object.
12 * - A \Imagify\Media\MediaInterface object.
13 * - A \Imagify\Media\DataInterface object.
14 *
15 * @since 1.9
16 * @author Grégory Viguier
17 */
18 class WP extends AbstractProcess {
19
20 /** ----------------------------------------------------------------------------------------- */
21 /** RESIZING ================================================================================ */
22 /** ----------------------------------------------------------------------------------------- */
23
24 /**
25 * Tell if a size should be resized.
26 *
27 * When the browser handled the upload it also produced the scaled version, using the
28 * very threshold Imagify configured ({@see \Imagify\Context\WP::filter_big_image_size_threshold()}),
29 * so resizing here would only shrink the untouched original that WordPress keeps
30 * aside as `original_image`.
31 *
32 * @since 2.3.3
33 *
34 * @param string $size The size name.
35 * @param File $file A File instance.
36 * @return bool
37 */
38 protected function can_resize( $size, $file ) {
39 $media = $this->get_media();
40
41 if ( $media && \Imagify\Context\WP::is_client_side_scaled( $media->get_id() ) ) {
42 return false;
43 }
44
45 return parent::can_resize( $size, $file );
46 }
47
48 /** ----------------------------------------------------------------------------------------- */
49 /** MISSING THUMBNAILS ====================================================================== */
50 /** ----------------------------------------------------------------------------------------- */
51
52 /**
53 * Get the sizes for this media that have not get through optimization.
54 * No sizes are returned if the file is not optimized, has no backup, or is not an image.
55 * The 'full' size os never returned.
56 *
57 * @since 1.9
58 * @access public
59 * @author Grégory Viguier
60 *
61 * @return array|WP_Error {
62 * A WP_Error object on failure.
63 * An array of data for the thumbnail sizes on success.
64 * Size names are used as array keys.
65 *
66 * @type int $width The image width.
67 * @type int $height The image height.
68 * @type bool $crop True to crop, false to resize.
69 * @type string $name The size name.
70 * @type string $file The name the thumbnail "should" have.
71 * }
72 */
73 public function get_missing_sizes() {
74 // The media must have been optimized once and have a backup.
75 if ( ! $this->is_valid() ) {
76 return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
77 }
78
79 $media = $this->get_media();
80
81 if ( ! $media ) {
82 return new WP_Error( 'no_media', __( 'No media found.', 'imagify' ) );
83 }
84
85 if ( ! $media->is_supported() ) {
86 return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
87 }
88
89 $data = $this->get_data();
90
91 if ( ! $data->is_optimized() ) {
92 return new WP_Error( 'media_not_optimized', __( 'This media is not optimized yet.', 'imagify' ) );
93 }
94
95 if ( ! $media->has_backup() ) {
96 return new WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) );
97 }
98
99 if ( ! $media->is_image() ) {
100 return new WP_Error( 'media_not_an_image', __( 'This media is not an image.', 'imagify' ) );
101 }
102
103 // Compare registered sizes and optimized sizes.
104 $context_sizes = $media->get_context_instance()->get_thumbnail_sizes();
105 $optimized_sizes = $data->get_optimization_data();
106 $missing_sizes = array_diff_key( $context_sizes, $optimized_sizes['sizes'] );
107
108 if ( ! $missing_sizes ) {
109 // We have everything we need.
110 return [];
111 }
112
113 $media_sizes = $media->get_media_files();
114 $full_size = $media_sizes['full'];
115
116 if ( ! $full_size['path'] || ! $full_size['width'] || ! $full_size['height'] ) {
117 return [];
118 }
119
120 $file_name = $this->filesystem->path_info( $full_size['path'] );
121 $file_name = $file_name['file_base'] . '-{%suffix%}.' . $file_name['extension'];
122
123 // Test if the missing sizes are needed.
124 foreach ( $missing_sizes as $size_name => $size_data ) {
125 if ( $full_size['width'] === $size_data['width'] && $full_size['height'] === $size_data['height'] ) {
126 // Same dimensions as the full size.
127 unset( $missing_sizes[ $size_name ] );
128 continue;
129 }
130
131 if ( ! empty( $media_sizes[ $size_name ]['disabled'] ) ) {
132 // This size must not be optimized.
133 unset( $missing_sizes[ $size_name ] );
134 continue;
135 }
136
137 $resize_result = image_resize_dimensions( $full_size['width'], $full_size['height'], $size_data['width'], $size_data['height'], $size_data['crop'] );
138
139 if ( ! $resize_result ) {
140 // This thumbnail is not needed, it is smaller than this size.
141 unset( $missing_sizes[ $size_name ] );
142 continue;
143 }
144
145 // Provide what should be the file name.
146 list( , , , , $new_width, $new_height ) = $resize_result;
147 $missing_sizes[ $size_name ]['file'] = str_replace( '{%suffix%}', "{$new_width}x{$new_height}", $file_name );
148 }
149
150 return $missing_sizes;
151 }
152
153 /**
154 * Optimize missing thumbnail sizes.
155 *
156 * @since 1.9
157 * @access public
158 * @author Grégory Viguier
159 *
160 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
161 */
162 public function optimize_missing_thumbnails() {
163 if ( ! $this->is_valid() ) {
164 return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
165 }
166
167 $media = $this->get_media();
168
169 if ( ! $media ) {
170 return new WP_Error( 'no_media', __( 'No media found.', 'imagify' ) );
171 }
172
173 if ( ! $media->is_supported() ) {
174 return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
175 }
176
177 $missing_sizes = $this->get_missing_sizes();
178
179 if ( ! $missing_sizes ) {
180 return new WP_Error( 'no_sizes', __( 'No thumbnails seem to be missing.', 'imagify' ) );
181 }
182
183 if ( is_wp_error( $missing_sizes ) ) {
184 return $missing_sizes;
185 }
186
187 if ( $this->is_locked() ) {
188 return new WP_Error( 'media_locked', __( 'This media is already being processed.', 'imagify' ) );
189 }
190
191 $this->lock();
192
193 // Create the missing thumbnails.
194 $sizes = $this->create_missing_thumbnails( $missing_sizes );
195
196 if ( ! $sizes ) {
197 $this->unlock();
198 return new WP_Error( 'thumbnail_creation_failed', __( 'The thumbnails failed to be created.', 'imagify' ) );
199 }
200
201 $optimization_level = $this->get_data()->get_optimization_level();
202
203 if ( false === $optimization_level ) {
204 $this->unlock();
205 return new \WP_Error( 'optimization_level_not_set', __( 'The optimization level of this media seems to have disappear from the database. You should restore this media and then launch a new optimization.', 'imagify' ) );
206 }
207
208 $args = [
209 'hook_suffix' => 'optimize_missing_thumbnails',
210 'locked' => true,
211 ];
212
213 // Optimize.
214 return $this->optimize_sizes( array_keys( $sizes ), $optimization_level, $args );
215 }
216
217 /**
218 * Create all missing thumbnails if they don't exist and update the attachment metadata.
219 *
220 * @since 1.9
221 * @access protected
222 * @see $this->get_missing_sizes()
223 * @author Grégory Viguier
224 *
225 * @param array $missing_sizes array {
226 * An array of data for the thumbnail sizes on success.
227 * Size names are used as array keys.
228 *
229 * @type int $width The image width.
230 * @type int $height The image height.
231 * @type bool $crop True to crop, false to resize.
232 * @type string $name The size name.
233 * @type string $file The name the thumbnail "should" have.
234 * }
235 * @return array {
236 * An array of thumbnail data (those without errors):
237 *
238 * @type string $file File name.
239 * @type int $width The image width.
240 * @type int $height The image height.
241 * @type string $mime-type The mime type.
242 * }
243 */
244 protected function create_missing_thumbnails( $missing_sizes ) {
245 if ( ! $missing_sizes ) {
246 return [];
247 }
248
249 $media = $this->get_media();
250
251 if ( ! $media ) {
252 return [];
253 }
254
255 $media_id = $media->get_id();
256 $metadata = wp_get_attachment_metadata( $media_id );
257 $metadata['sizes'] = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : [];
258
259 $destination_dir = $this->filesystem->dir_path( $media->get_raw_fullsize_path() );
260 $backup_file = new File( $media->get_backup_path() );
261 $without_errors = [];
262 $has_new_data = false;
263
264 // Create the missing thumbnails.
265 foreach ( $missing_sizes as $size_name => $thumbnail_data ) {
266 // The path to the destination file.
267 $thumbnail_data['path'] = $destination_dir . $thumbnail_data['file'];
268
269 if ( ! $this->filesystem->exists( $thumbnail_data['path'] ) ) {
270 $result = $backup_file->create_thumbnail( $thumbnail_data );
271
272 if ( is_array( $result ) ) {
273 // New file.
274 $metadata['sizes'][ $size_name ] = $result;
275 $has_new_data = true;
276 }
277 } else {
278 $result = true;
279 }
280
281 if ( ! empty( $metadata['sizes'][ $size_name ] ) && ! is_wp_error( $result ) ) { // phpstan-ignore-line
282 // Not an error.
283 $without_errors[ $size_name ] = $metadata['sizes'][ $size_name ];
284 }
285 }
286
287 // Save the new data into the attachment metadata.
288 if ( $has_new_data ) {
289 /**
290 * Here we don't use wp_update_attachment_metadata() to prevent triggering unwanted hooks.
291 */
292 update_post_meta( $media_id, '_wp_attachment_metadata', $metadata );
293 }
294
295 return $without_errors;
296 }
297 }
298