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 / inc / 3rd-party / amazon-s3-and-cloudfront / classes / CDN / WP / AS3.php

AS3.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php

514 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Imagify\ThirdParty\AS3CF\CDN\WP;
3
4 use Imagify\CDN\PushCDNInterface;
5 use Imagify\Context\ContextInterface;
6
7 defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
8
9 /**
10 * AS3 CDN for WP context.
11 *
12 * @since 1.9
13 * @author Grégory Viguier
14 */
15 class AS3 implements PushCDNInterface {
16
17 /**
18 * The media ID.
19 *
20 * @var int
21 * @since 1.9
22 * @access protected
23 * @author Grégory Viguier
24 */
25 protected $id;
26
27 /**
28 * Filesystem object.
29 *
30 * @var object Imagify_Filesystem
31 * @since 1.9
32 * @access protected
33 * @author Grégory Viguier
34 */
35 protected $filesystem;
36
37 /**
38 * Tell if we’re playing in WP 5.3’s garden.
39 *
40 * @var bool
41 * @since 1.9.8
42 * @access protected
43 * @author Grégory Viguier
44 */
45 protected $is_wp53;
46
47 /**
48 * Constructor.
49 *
50 * @since 1.9
51 * @access public
52 * @author Grégory Viguier
53 *
54 * @param int $media_id The media ID.
55 */
56 public function __construct( $media_id ) {
57 $this->id = (int) $media_id;
58 $this->filesystem = \Imagify_Filesystem::get_instance();
59 }
60
61 /**
62 * Tell if the CDN is ready (not necessarily reachable).
63 *
64 * @since 1.9
65 * @access public
66 * @author Grégory Viguier
67 *
68 * @return bool
69 */
70 public function is_ready() {
71 global $as3cf;
72 static $is;
73
74 if ( ! isset( $is ) ) {
75 $is = $as3cf && $as3cf->is_plugin_setup();
76 }
77
78 return $is;
79 }
80
81 /**
82 * Tell if the media is on the CDN.
83 *
84 * @since 1.9
85 * @access public
86 * @author Grégory Viguier
87 *
88 * @return bool
89 */
90 public function media_is_on_cdn() {
91 return (bool) $this->get_cdn_info();
92 }
93
94 /**
95 * Get files from the CDN.
96 *
97 * @since 1.9
98 * @access public
99 * @author Grégory Viguier
100 *
101 * @param array $file_paths A list of file paths.
102 * @return bool|\WP_Error True on success. A \WP_error object on failure.
103 */
104 public function get_files_from_cdn( $file_paths ) {
105 global $as3cf;
106
107 if ( ! $this->is_ready() ) {
108 return new \WP_Error( 'not_ready', __( 'CDN is not set up.', 'imagify' ) );
109 }
110
111 $cdn_info = $this->get_cdn_info();
112
113 if ( ! $cdn_info ) {
114 // The media is not on the CDN.
115 return new \WP_Error( 'not_on_cdn', __( 'This media could not be found on the CDN.', 'imagify' ) );
116 }
117
118 $directory = $this->filesystem->dir_path( $cdn_info['key'] );
119 $directory = $this->filesystem->is_root( $directory ) ? '' : $directory;
120 $new_method = method_exists( $as3cf->plugin_compat, 'copy_s3_file_to_server' );
121 $errors = [];
122
123 foreach ( $file_paths as $file_path ) {
124 $cdn_info['key'] = $directory . $this->filesystem->file_name( $file_path );
125
126 // Retrieve file from the CDN.
127 if ( $new_method ) {
128 $as3cf->plugin_compat->copy_s3_file_to_server( $cdn_info, $file_path );
129 } else {
130 $as3cf->plugin_compat->copy_provider_file_to_server( $cdn_info, $file_path );
131 }
132
133 if ( ! $this->filesystem->exists( $file_path ) ) {
134 $errors[] = $file_path;
135 }
136 }
137
138 if ( $errors ) {
139 $nbr_errors = count( $errors );
140 $errors_txt = array_map( [ $this->filesystem, 'make_path_relative' ], $errors );
141 $errors_txt = wp_sprintf_l( '%l', $errors_txt );
142
143 return new \WP_Error(
144 'not_retrieved',
145 sprintf(
146 /* translators: %s is a list of file paths. */
147 _n( 'The following file could not be retrieved from the CDN: %s.', 'The following files could not be retrieved from the CDN: %s.', $nbr_errors, 'imagify' ),
148 $errors_txt
149 ),
150 $errors
151 );
152 }
153
154 return true;
155 }
156
157 /**
158 * Remove files from the CDN.
159 * Don't use this to empty a folder.
160 *
161 * @since 1.9
162 * @access public
163 * @author Grégory Viguier
164 *
165 * @param array $file_paths A list of file paths. Those paths are not necessary absolute, and can be also file names.
166 * @return bool|\WP_Error True on success. A \WP_error object on failure.
167 */
168 public function remove_files_from_cdn( $file_paths ) {
169 global $as3cf;
170
171 if ( ! $this->is_ready() ) {
172 return new \WP_Error( 'not_ready', __( 'CDN is not set up.', 'imagify' ) );
173 }
174
175 $cdn_info = $this->get_cdn_info();
176
177 if ( ! $cdn_info ) {
178 // The media is not on the CDN.
179 return new \WP_Error( 'not_on_cdn', __( 'This media could not be found on the CDN.', 'imagify' ) );
180 }
181
182 $directory = $this->filesystem->dir_path( $cdn_info['key'] );
183 $directory = $this->filesystem->is_root( $directory ) ? '' : $directory;
184
185 if ( method_exists( $as3cf, 'get_s3object_region' ) ) {
186 $region = $as3cf->get_s3object_region( $cdn_info );
187 } else {
188 $region = $as3cf->get_provider_object_region( $cdn_info );
189 }
190
191 if ( is_wp_error( $region ) ) {
192 $region = '';
193 }
194
195 $to_remove = [];
196
197 foreach ( $file_paths as $file_path ) {
198 $to_remove[] = [
199 'Key' => $directory . $this->filesystem->file_name( $file_path ),
200 ];
201 }
202
203 if ( method_exists( $as3cf, 'delete_s3_objects' ) ) {
204 $result = $as3cf->delete_s3_objects( $region, $cdn_info['bucket'], $to_remove, false, false, false );
205 } else {
206 $result = $as3cf->delete_objects( $region, $cdn_info['bucket'], $to_remove, false, false, false );
207 }
208
209 if ( is_wp_error( $result ) ) {
210 return new \WP_Error( 'deletion_failed', __( 'File(s) could not be removed from the CDN.', 'imagify' ) );
211 }
212
213 return true;
214 }
215
216 /**
217 * Send all files from a media to the CDN.
218 *
219 * @since 1.9
220 * @access public
221 * @author Grégory Viguier
222 *
223 * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
224 * @return bool|\WP_Error True/False if sent or not. A \WP_error object on failure.
225 */
226 public function send_to_cdn( $is_new_upload ) {
227 global $as3cf;
228
229 if ( ! $this->is_ready() ) {
230 return new \WP_Error( 'not_ready', __( 'CDN is not set up.', 'imagify' ) );
231 }
232
233 if ( ! $this->can_send_to_cdn( $is_new_upload ) ) {
234 return false;
235 }
236
237 // Retrieve the missing files from the CDN: we must send all of them at once, even those that have not been modified.
238 $file_paths = \AS3CF_Utils::get_attachment_file_paths( $this->id, false );
239
240 if ( $file_paths ) {
241 foreach ( $file_paths as $size => $file_path ) {
242 if ( $this->filesystem->exists( $file_path ) ) {
243 // Keep only the files that don't exist.
244 unset( $file_paths[ $size ] );
245 }
246 }
247 }
248
249 if ( $file_paths ) {
250 $result = $this->get_files_from_cdn( $file_paths );
251
252 if ( is_wp_error( $result ) ) {
253 return $result;
254 }
255 }
256
257 $remove_local_files = $this->should_delete_files( $is_new_upload );
258
259 if ( ! $is_new_upload ) {
260 if ( $remove_local_files ) {
261 // Force files deletion when not a new media.
262 add_filter( 'as3cf_get_setting', [ $this, 'force_local_file_removal_setting' ], 100, 2 );
263 add_filter( 'as3cf_setting_remove-local-file', 'imagify_return_true', 100 );
264 } else {
265 // Force to keep the files when not a new media.
266 add_filter( 'as3cf_get_setting', [ $this, 'force_local_file_keep_setting' ], 100, 2 );
267 add_filter( 'as3cf_setting_remove-local-file', 'imagify_return_false', 100 );
268 }
269 }
270
271 if ( method_exists( $as3cf, 'upload_attachment_to_s3' ) ) {
272 $result = $as3cf->upload_attachment_to_s3( $this->id, null, null, false, $remove_local_files );
273 } else {
274 $result = $as3cf->upload_attachment( $this->id, null, null, false, $remove_local_files );
275 }
276
277 if ( ! $is_new_upload ) {
278 if ( $remove_local_files ) {
279 remove_filter( 'as3cf_get_setting', [ $this, 'force_local_file_removal_setting' ], 100 );
280 remove_filter( 'as3cf_setting_remove-local-file', 'imagify_return_true', 100 );
281 } else {
282 remove_filter( 'as3cf_get_setting', [ $this, 'force_local_file_keep_setting' ], 100 );
283 remove_filter( 'as3cf_setting_remove-local-file', 'imagify_return_false', 100 );
284 }
285 }
286
287 if ( is_wp_error( $result ) ) {
288 return $result;
289 }
290
291 return true;
292 }
293
294 /**
295 * Get a file URL.
296 *
297 * @since 1.9
298 * @access public
299 * @author Grégory Viguier
300 *
301 * @param string $file_name Name of the file. Leave empty for the full size file.
302 * @return string URL to the file.
303 */
304 public function get_file_url( $file_name = false ) {
305 $file_url = wp_get_attachment_url( $this->id );
306
307 if ( $file_name ) {
308 // It's not the full size.
309 $file_url = $this->filesystem->dir_path( $file_url ) . $file_name;
310 }
311
312 return $file_url;
313 }
314
315 /**
316 * Get a file path.
317 *
318 * @since 1.9
319 * @access public
320 * @author Grégory Viguier
321 *
322 * @param string $file_name Name of the file. Leave empty for the full size file. Use 'original' to get the path to the original file.
323 * @return string Path to the file.
324 */
325 public function get_file_path( $file_name = false ) {
326 if ( ! $file_name ) {
327 // Full size.
328 return get_attached_file( $this->id, true );
329 }
330
331 if ( 'original' === $file_name ) {
332 // Original file.
333 if ( $this->is_wp_53() ) {
334 // `wp_get_original_image_path()` may return false.
335 $file_path = wp_get_original_image_path( $this->id );
336 } else {
337 $file_path = false;
338 }
339
340 if ( ! $file_path ) {
341 $file_path = get_attached_file( $this->id, true );
342 }
343
344 return $file_path;
345 }
346
347 // Thumbnail.
348 $file_path = get_attached_file( $this->id, true );
349 $file_path = $this->filesystem->dir_path( $file_path ) . $file_name;
350
351 return $file_path;
352 }
353
354
355 /** ----------------------------------------------------------------------------------------- */
356 /** INTERNAL TOOLS ========================================================================== */
357 /** ----------------------------------------------------------------------------------------- */
358
359 /**
360 * Filter the CDN setting 'remove-local-file': this is used to force deletion when the media is not a new one.
361 * Caution to not use $this->should_delete_files() when this filter is used!
362 *
363 * @since 1.9
364 * @access public
365 * @author Grégory Viguier
366 *
367 * @param bool $setting The setting value.
368 * @param string $key The setting name.
369 * @return bool
370 */
371 public function force_local_file_removal_setting( $setting, $key ) {
372 if ( 'remove-local-file' === $key ) {
373 return true;
374 }
375
376 return $setting;
377 }
378
379 /**
380 * Filter the CDN setting 'remove-local-file': this is used to force not-deletion when the media is not a new one.
381 * Caution to not use $this->should_delete_files() when this filter is used!
382 *
383 * @since 1.9
384 * @access public
385 * @author Grégory Viguier
386 *
387 * @param bool $setting The setting value.
388 * @param string $key The setting name.
389 * @return bool
390 */
391 public function force_local_file_keep_setting( $setting, $key ) {
392 if ( 'remove-local-file' === $key ) {
393 return false;
394 }
395
396 return $setting;
397 }
398
399 /**
400 * Tell if a media is stored on the CDN.
401 *
402 * @since 1.9
403 * @access protected
404 * @author Grégory Viguier
405 *
406 * @return array|bool The CDN info on success. False if the media is not on the CDN.
407 */
408 protected function get_cdn_info() {
409 global $as3cf;
410
411 if ( ! $as3cf ) {
412 return false;
413 }
414
415 if ( method_exists( $as3cf, 'get_attachment_s3_info' ) ) {
416 return $as3cf->get_attachment_s3_info( $this->id );
417 }
418
419 return $as3cf->get_attachment_provider_info( $this->id );
420 }
421
422 /**
423 * Tell if a media can (and should) be sent to the CDN.
424 *
425 * @since 1.9
426 * @access protected
427 * @author Grégory Viguier
428 *
429 * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
430 * @return bool
431 */
432 protected function can_send_to_cdn( $is_new_upload ) {
433 global $as3cf;
434 static $can = [];
435 static $cdn_setting;
436
437 if ( isset( $can[ $this->id ] ) ) {
438 return $can[ $this->id ];
439 }
440
441 if ( ! $this->is_ready() ) {
442 $can[ $this->id ] = false;
443 return $can[ $this->id ];
444 }
445
446 if ( ! isset( $cdn_setting ) ) {
447 $cdn_setting = $as3cf && $as3cf->get_setting( 'copy-to-s3' );
448 }
449
450 // The CDN is set up: test if the media is on it.
451 $can[ $this->id ] = $this->media_is_on_cdn();
452
453 if ( $can[ $this->id ] && $is_new_upload ) {
454 // Use the CDN setting to tell if we're allowed to send the files (should be true since it's a new upload and it's already there).
455 $can[ $this->id ] = $cdn_setting;
456 }
457
458 /**
459 * Tell if a media can (and should) be sent to the CDN.
460 *
461 * @since 1.9
462 * @author Grégory Viguier
463 *
464 * @param bool $can True if the media can be sent. False otherwize.
465 * @param PushCDNInterface $cdn The CDN instance.
466 * @param bool $cdn_setting CDN setting that tells if a new media can be sent to the CDN.
467 * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
468 */
469 $can[ $this->id ] = (bool) apply_filters( 'imagify_can_send_to_cdn', $can[ $this->id ], $this, $cdn_setting, $is_new_upload );
470
471 return $can[ $this->id ];
472 }
473
474 /**
475 * Tell if the files should be deleted after optimization.
476 *
477 * @since 1.9
478 * @access protected
479 * @author Grégory Viguier
480 *
481 * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
482 * @return bool
483 */
484 protected function should_delete_files( $is_new_upload ) {
485 global $as3cf;
486
487 if ( $is_new_upload ) {
488 return (bool) $as3cf->get_setting( 'remove-local-file' );
489 }
490
491 // If the attachment has a 'filesize' metadata, that means the local files are meant to be deleted.
492 return (bool) get_post_meta( $this->id, 'wpos3_filesize_total', true );
493 }
494
495 /**
496 * Tell if we’re playing in WP 5.3’s garden.
497 *
498 * @since 1.9.8
499 * @access protected
500 * @author Grégory Viguier
501 *
502 * @return bool
503 */
504 protected function is_wp_53() {
505 if ( isset( $this->is_wp53 ) ) {
506 return $this->is_wp53;
507 }
508
509 $this->is_wp53 = function_exists( 'wp_get_original_image_path' );
510
511 return $this->is_wp53;
512 }
513 }
514