PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 3.0.0
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v3.0.0
4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 3.12.1 All 134 releases
optimole-wp / inc / media_offload.php

media_offload.php in Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization 3.0.0, at inc/media_offload.php

1,008 lines 33.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Optml_Media_Offload class.
4 *
5 * @package \Optimole\Inc
6 * @author Optimole <friends@optimole.com>
7 */
8
9 /**
10 * Class Optml_Admin
11 */
12 class Optml_Media_Offload extends Optml_App_Replacer {
13 use Optml_Normalizer;
14 /**
15 * Hold the settings object.
16 *
17 * @var Optml_Settings Settings object.
18 */
19 public $settings;
20
21 const KEYS = [
22 'uploaded_flag' => 'id:',
23 'not_processed_flag' => 'process:',
24 ];
25 /**
26 * Flag used inside wp_get_attachment url filter.
27 *
28 * @var bool Whether or not to return the original url of the image.
29 */
30 private static $return_original_url = false;
31 /**
32 * Enqueue script for generating cloud media tab.
33 */
34 public function add_cloud_script( $hook ) {
35 if ( $hook === 'post.php' || $hook === 'post-new.php' ) {
36 wp_enqueue_script( 'optimole_media', OPTML_URL . 'assets/js/optimole_media.js' );
37 }
38 }
39
40 /**
41 * Generate exactly the response format expected by wp media modal.
42 *
43 * @param string $url Original url to be optimized.
44 * @param string $resource_id Image id from cloud.
45 * @param int $width Image width.
46 * @param int $height Image height.
47 * @return array The format expected for a single image in the media modal.
48 */
49 private function media_attachment_template( $url, $resource_id, $width, $height, $last_attach ) {
50 $filename = pathinfo( $url, PATHINFO_FILENAME );
51 $optimized_url = $this->get_media_optimized_url( $url, $resource_id, $width, $height );
52 return
53 [
54 'id' => $last_attach + 1 + crc32( $filename ),
55 'title' => $filename,
56 'url' => $optimized_url,
57 'link' => $optimized_url,
58 'alt' => '',
59 'author' => 1,
60 'status' => 'inherit',
61 'menuOrder' => 0,
62 'mime' => 'image/jpeg',
63 'type' => 'image',
64 'subtype' => 'jpeg',
65 'icon' => $optimized_url,
66 'editLink' => $optimized_url,
67 'authorName' => 'Optimole',
68 'height' => $height,
69 'width' => $width,
70 'orientation' => 'landscape',
71 // just adding the thumbnail size for for smooth display inside the modal
72 // this sizes are ignored for everything else no point to define them
73 'sizes' =>
74 [
75 'thumbnail' =>
76 [
77 'height' => '150',
78 'width' => '150',
79 'url' => $this->get_media_optimized_url( $url, $resource_id, 150, 150, $this->to_optml_crop( true ) ),
80 'orientation' => 'landscape',
81 ],
82 ],
83 ];
84 }
85
86 /**
87 * Get count of all images from db.
88 *
89 * @return int Number of all images.
90 */
91 public static function number_of_all_images() {
92 $total_images_by_mime = wp_count_attachments( 'image' );
93 return array_sum( (array) $total_images_by_mime );
94 }
95 /**
96 * Parse images from api endpoint response images and send them to wp media modal.
97 */
98 public function pull_images() {
99 $images_on_page = 40;
100 $last_attach = self::number_of_all_images();
101 if ( ! current_user_can( 'upload_files' ) ) {
102 wp_send_json_error();
103 }
104 if ( isset( $_REQUEST['query'] ) && isset( $_REQUEST['query']['post_mime_type'] ) && $_REQUEST['query']['post_mime_type'][0] === 'optml_cloud' ) {
105 $search = '';
106 if ( isset( $_REQUEST['query']['s'] ) ) {
107 $search = $_REQUEST['query']['s'];
108 }
109 $page = 0;
110 if ( isset( $_REQUEST['query']['paged'] ) ) {
111 $page = $_REQUEST['query']['paged'] - 1;
112 }
113 $images = [];
114 $view_sites = [];
115 $all_sites = false;
116 $filter_sites = $this->settings->get( 'cloud_sites' );
117
118 if ( isset( $filter_sites['all'] ) && $filter_sites['all'] === 'true' ) {
119 $all_sites = true;
120 }
121 if ( ! $all_sites ) {
122 foreach ( $filter_sites as $site => $value ) {
123 if ( $value === 'true' ) {
124 $view_sites[] = $site;
125 }
126 }
127 }
128 $cloud_images = [];
129 $request = new Optml_Api();
130 $decoded_response = $request->get_cloud_images( $page, $view_sites, $search );
131
132 if ( isset( $decoded_response['images'] ) ) {
133 $cloud_images = $decoded_response['images'];
134 }
135
136 foreach ( $cloud_images as $index => $image ) {
137 $width = 'auto';
138 $height = 'auto';
139 if ( ! isset( $image['meta']['originURL'] ) || ! isset( $image['meta']['resourceS3'] ) ) {
140 continue;
141 }
142 if ( isset( $image['meta']['originalHeight'] ) ) {
143 $height = $image['meta']['originalHeight'];
144 }
145 if ( isset( $image['meta']['originalWidth'] ) ) {
146 $width = $image['meta']['originalWidth'];
147 }
148 $images[] = $this->media_attachment_template( $image['meta']['originURL'], $image['meta']['resourceS3'], $width, $height, $last_attach );
149 }
150 wp_send_json_success( $images );
151 }
152 }
153 /**
154 * Optml_Media_Offload constructor.
155 */
156 public function __construct() {
157 $this->settings = new Optml_Settings();
158 if ( $this->settings->is_connected() ) {
159 parent::init();
160 }
161 if ( $this->settings->get( 'cloud_images' ) === 'enabled' ) {
162 add_action( 'wp_ajax_query-attachments', [$this, 'pull_images'], -2 );
163 add_action( 'admin_enqueue_scripts', [$this, 'add_cloud_script'] );
164 }
165 if ( $this->settings->get( 'offload_media' ) === 'enabled' ) {
166 add_filter( 'image_downsize', [$this, 'generate_filter_downsize_urls'], 10, 3 );
167 add_filter( 'wp_generate_attachment_metadata', [$this, 'generate_image_meta'], 10, 2 );
168 add_filter( 'wp_get_attachment_url', [$this, 'get_image_attachment_url'], -999, 2 );
169 add_action( 'wp_insert_post_data', [$this, 'filter_uploaded_images'] );
170 add_action( 'delete_attachment', [$this, 'delete_attachment_hook'], 10 );
171 add_filter( 'handle_bulk_actions-upload', [$this, 'bulk_action_handler'], 10, 3 );
172 add_filter( 'bulk_actions-upload', [$this, 'register_bulk_media_actions'] );
173 add_action( 'admin_notices', [$this, 'bulk_action_notices'] );
174 add_filter( 'media_row_actions', [$this, 'add_inline_media_action'], 10, 2 );
175 add_filter( 'wp_calculate_image_srcset', [ $this, 'calculate_image_srcset' ], 1, 5 );
176 }
177 }
178 /**
179 * Get image size name from width and meta.
180 *
181 * @param array $sizes Image sizes .
182 * @param string $width Size width.
183 * @param string $filename Image filename.
184 *
185 * @return null|string
186 */
187 public static function get_image_name_from_width( $sizes, $width, $filename ) {
188 foreach ( $sizes as $name => $size ) {
189 if ( $width === absint( $size['width'] ) && $size['file'] === $filename ) {
190 return $name;
191 }
192 }
193
194 return null;
195 }
196 /**
197 * Replace image URLs in the srcset attributes.
198 *
199 * @param array $sources Array of image sources.
200 * @param array $size_array Array of width and height values in pixels (in that order).
201 * @param string $image_src The 'src' of the image.
202 * @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
203 * @param int $attachment_id Image attachment ID.
204 *
205 * @return array
206 */
207 public function calculate_image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) {
208
209 if ( ! is_array( $sources ) ) {
210 return $sources;
211 }
212
213 if ( ! Optml_Media_Offload::is_uploaded_image( $image_src ) || ! isset( $image_meta['file'] ) || ! Optml_Media_Offload::is_uploaded_image( $image_meta['file'] ) ) {
214 return $sources;
215 }
216 foreach ( $sources as $width => $source ) {
217 $filename = wp_basename( $image_meta['file'] );
218 $size = $this->get_image_name_from_width( $image_meta['sizes'], $width, $filename );
219 $optimized_url = wp_get_attachment_image_src( $attachment_id, $size );
220
221 if ( false === $optimized_url || ! isset( $optimized_url[0] ) ) {
222 continue;
223 }
224
225 $sources[ $width ]['url'] = $optimized_url[0];
226 }
227 return $sources;
228 }
229 /**
230 * Get the dimension from optimized url.
231 *
232 * @param string $url The image url.
233 * @return array Contains the width and height values in this order.
234 */
235 public static function parse_dimension_from_optimized_url( $url ) {
236 $catch = [];
237 $height = false;
238 $width = false;
239 preg_match( '/\/w:(.*)\/h:(.*)\/q:/', $url, $catch );
240 if ( isset( $catch[1] ) && isset( $catch[2] ) ) {
241 $width = $catch[1];
242 $height = $catch[2];
243 }
244 return [$width, $height];
245 }
246
247 /**
248 * Check if the image is stored on our servers or not.
249 *
250 * @param string $src Image src or url.
251 * @return bool Whether image is upload or not.
252 */
253 public static function is_not_processed_image( $src ) {
254 return strpos( $src, self::KEYS['not_processed_flag'] ) !== false;
255 }
256 /**
257 * Check if the image is stored on our servers or not.
258 *
259 * @param string $src Image src or url.
260 * @return bool Whether image is upload or not.
261 */
262 public static function is_uploaded_image( $src ) {
263 return strpos( $src, '/' . self::KEYS['uploaded_flag'] ) !== false;
264 }
265 /**
266 * Get the attachment ID from the image tag.
267 *
268 * @param string $image Image tag.
269 *
270 * @return int|false
271 */
272 public function get_id_from_tag( $image ) {
273 $attachment_id = false;
274 if ( preg_match( '#class=["|\']?[^"\']*(wp-image-|wp-video-)([\d]+)[^"\']*["|\']?#i', $image, $found ) ) {
275 $attachment_id = intval( $found[2] );
276 }
277
278 return $attachment_id;
279 }
280 /**
281 * Filter out the urls that are saved to our servers when saving to the DB.
282 *
283 * @param array $data The post data array to save.
284 *
285 * @return array
286 * @uses filter:wp_insert_post_data
287 */
288 public function filter_uploaded_images( $data ) {
289
290 $content = trim( wp_unslash( $data['post_content'] ) );
291 $images = Optml_Manager::parse_images_from_html( $content );
292 if ( ! isset( $images[0] ) ) {
293 return $data;
294 }
295 foreach ( $images[0] as $index => $tag ) {
296 $url = $images['img_url'][ $index ];
297 $attachment_id = $this->get_id_from_tag( $tag );
298 if ( false === $attachment_id || ! wp_attachment_is_image( $attachment_id ) ) {
299 continue;
300 }
301 $is_original_uploaded = self::is_uploaded_image( $url );
302 $size = $is_original_uploaded ? $this->parse_dimension_from_optimized_url( $url ) : $this->parse_dimensions_from_filename( $url );
303
304 $optimized_url = wp_get_attachment_image_src( $attachment_id, $size );
305 if ( ! isset( $optimized_url[0] ) ) {
306 continue;
307 }
308 if ( $is_original_uploaded === self::is_uploaded_image( $optimized_url[0] ) ) {
309 continue;
310 }
311 $content = str_replace( $url, $optimized_url[0], $content );
312 }
313 $data['post_content'] = wp_slash( $content );
314 return $data;
315 }
316 /**
317 * Trigger an update on content that contains the same attachment ID as the modified image.
318 *
319 * @param int $attachment_id The attachment id of the image to init an update.
320 */
321 public function update_content( $attachment_id ) {
322 $content = new \WP_Query( [ 's' => 'wp-image-' . $attachment_id, 'fields' => 'ids', 'posts_per_page' => 100 ] );
323 if ( ! empty( $content->found_posts ) ) {
324 $content_posts = array_unique( $content->get_posts() );
325 foreach ( $content_posts as $content_id ) {
326 wp_update_post( [ 'ID' => $content_id ] ); // existing filters should take care of providing optimized urls
327 }
328 }
329 }
330 /**
331 * Add inline action to push to our servers.
332 *
333 * @param array $actions All actions.
334 * @param \WP_Post $post The current post image object.
335 *
336 * @return array
337 */
338 public function add_inline_media_action( $actions, $post ) {
339 $file = wp_get_attachment_metadata( $post->ID )['file'];
340 if ( wp_check_filetype( $file, Optml_Config::$all_extensions )['ext'] === false || ! current_user_can( 'delete_post', $post->ID ) ) {
341 return $actions;
342 }
343 if ( ! self::is_uploaded_image( $file ) ) {
344 $upload_action_url = add_query_arg(
345 [
346 'action' => 'optimole_upload_image',
347 'media[]' => $post->ID,
348 '_wpnonce' => wp_create_nonce( 'bulk-media' ),
349 ],
350 'upload.php'
351 );
352
353 $actions['optimole_upload_image'] = sprintf(
354 '<a href="%s" aria-label="%s">%s</a>',
355 $upload_action_url,
356 esc_attr__( 'Offload to Optimole', 'optimole-wp' ),
357 esc_html__( 'Offload to Optimole', 'optimole-wp' )
358 );
359 }
360 if ( self::is_uploaded_image( $file ) ) {
361 $rollback_action_url = add_query_arg(
362 [
363 'action' => 'optimole_back_to_media',
364 'media[]' => $post->ID,
365 '_wpnonce' => wp_create_nonce( 'bulk-media' ),
366 ],
367 'upload.php'
368 );
369 $actions['optimole_back_to_media'] = sprintf(
370 '<a href="%s" aria-label="%s">%s</a>',
371 $rollback_action_url,
372 esc_attr__( 'Restore image to media library', 'optimole-wp' ),
373 esc_html__( 'Restore image to media library', 'optimole-wp' )
374 );
375 }
376 return $actions;
377 }
378 /**
379 * Upload images to our servers and update inside pages.
380 *
381 * @param array $image_ids The id of the attachments for the selected images.
382 * @return int The number of successfully processed images.
383 */
384 public function upload_and_update_existing_images( $image_ids ) {
385 $success_up = 0;
386 foreach ( $image_ids as $id ) {
387 if ( self::is_uploaded_image( wp_get_attachment_metadata( $id )['file'] ) ) {
388 $success_up ++;
389 continue;
390 }
391
392 $meta = $this->generate_image_meta( wp_get_attachment_metadata( $id ), $id );
393 if ( isset( $meta['file'] ) && self::is_uploaded_image( $meta['file'] ) ) {
394
395 $success_up ++;
396 wp_update_attachment_metadata( $id, $meta );
397 $this->update_content( $id );
398 }
399 }
400 return $success_up;
401 }
402
403 /**
404 * Return the original url of an image attachment.
405 *
406 * @param integer $post_id Image attachment id.
407 * @return string The original url of the image.
408 */
409 public static function get_original_url( $post_id ) {
410 self::$return_original_url = true;
411 $original_url = wp_get_attachment_url( $post_id );
412 self::$return_original_url = false;
413 return $original_url;
414 }
415 /**
416 * Bring images back to media library and update inside pages.
417 *
418 * @param array $image_ids The id of the attachments for the selected images.
419 * @return int The number of successfully processed images.
420 */
421 public function rollback_and_update_images( $image_ids ) {
422 $success_back = 0;
423 foreach ( $image_ids as $id ) {
424 $current_meta = wp_get_attachment_metadata( $id );
425 if ( ! isset( $current_meta['file'] ) || ! self::is_uploaded_image( $current_meta['file'] ) ) {
426 delete_post_meta( $id, 'optimole_offload' );
427 $success_back++;
428 continue;
429 }
430 $table_id = [];
431 $filename = pathinfo( $current_meta['file'], PATHINFO_BASENAME );
432 preg_match( '/\/' . self::KEYS['uploaded_flag'] . '([^\/]*)\//', $current_meta['file'], $table_id );
433 if ( ! isset( $table_id[1] ) ) {
434 continue;
435 }
436 $table_id = $table_id[1];
437 $request = new Optml_Api();
438 $get_response = $request->call_upload_api( '', 'false', $table_id, 'false', 'true' );
439
440 if ( is_wp_error( $get_response ) || wp_remote_retrieve_response_code( $get_response ) !== 200 ) {
441 update_post_meta( $id, 'optimole_rollback_error', 'true' );
442 continue;
443 }
444
445 $get_url = json_decode( $get_response['body'], true )['getUrl'];
446
447 if ( ! function_exists( 'download_url' ) ) {
448 include_once ABSPATH . 'wp-admin/includes/file.php';
449 }
450 if ( ! function_exists( 'download_url' ) ) {
451 update_post_meta( $id, 'optimole_rollback_error', 'true' );
452 continue;
453 }
454 $timeout_seconds = 60;
455 $temp_file = download_url( $get_url, $timeout_seconds );
456
457 if ( is_wp_error( $temp_file ) ) {
458 update_post_meta( $id, 'optimole_rollback_error', 'true' );
459 continue;
460 }
461
462 $extension = $this->get_ext( $filename );
463
464 if ( ! isset( Optml_Config::$image_extensions [ $extension ] ) ) {
465 update_post_meta( $id, 'optimole_rollback_error', 'true' );
466 continue;
467 }
468
469 $type = Optml_Config::$image_extensions [ $extension ];
470 $file = [
471 'name' => $filename,
472 'type' => $type,
473 'tmp_name' => $temp_file,
474 'error' => 0,
475 'size' => filesize( $temp_file ),
476 ];
477
478 $overrides = [
479 // do not expect the default form data from normal uploads
480 'test_form' => false,
481
482 // Setting this to false lets WordPress allow empty files, not recommended.
483 'test_size' => true,
484
485 // A properly uploaded file will pass this test. There should be no reason to override this one.
486 'test_upload' => true,
487 ];
488
489 if ( ! function_exists( 'wp_handle_sideload' ) ) {
490 include_once ABSPATH . '/wp-admin/includes/file.php';
491 }
492 if ( ! function_exists( 'wp_handle_sideload' ) ) {
493 update_post_meta( $id, 'optimole_rollback_error', 'true' );
494 continue;
495 }
496
497 // Move the temporary file into the uploads directory.
498 $results = wp_handle_sideload( $file, $overrides );
499 if ( ! empty( $results['error'] ) ) {
500 update_post_meta( $id, 'optimole_rollback_error', 'true' );
501 continue;
502 }
503
504 if ( ! function_exists( 'wp_create_image_subsizes' ) ) {
505 include_once ABSPATH . '/wp-admin/includes/image.php';
506 }
507 if ( ! function_exists( 'wp_create_image_subsizes' ) ) {
508 update_post_meta( $id, 'optimole_rollback_error', 'true' );
509 continue;
510 }
511 wp_create_image_subsizes( $results['file'], $id );
512 if ( $type === 'image/svg+xml' ) {
513 if ( ! function_exists( 'wp_get_attachment_metadata' ) || ! function_exists( 'wp_update_attachment_metadata' ) ) {
514 include_once ABSPATH . '/wp-admin/includes/post.php';
515 }
516 if ( ! function_exists( 'wp_get_attachment_metadata' ) ) {
517 update_post_meta( $id, 'optimole_rollback_error', 'true' );
518 continue;
519 }
520 $meta = wp_get_attachment_metadata( $id );
521 if ( ! isset( $meta['file'] ) ) {
522 update_post_meta( $id, 'optimole_rollback_error', 'true' );
523 continue;
524 }
525 $meta['file'] = $results['file'];
526 wp_update_attachment_metadata( $id, $meta );
527 }
528
529 if ( ! function_exists( 'update_attached_file' ) ) {
530 include_once ABSPATH . '/wp-admin/includes/post.php';
531 }
532 if ( ! function_exists( 'update_attached_file' ) ) {
533 update_post_meta( $id, 'optimole_rollback_error', 'true' );
534 continue;
535 }
536 update_attached_file( $id, $results['file'] );
537 $success_back++;
538 $this->update_content( $id );
539 $original_url = self::get_original_url( $id );
540 if ( $original_url === false ) {
541 continue;
542 }
543 $this->delete_attachment_from_server( $original_url, $id, $table_id );
544 }
545 return $success_back;
546 }
547
548 /**
549 * Handle the bulk actions.
550 *
551 * @param string $redirect The current url from the media library.
552 * @param string $doaction The current action selected.
553 * @param array $image_ids The id of the attachments for the selected images.
554 * @return string The url with the correspondent query args for the executed actions.
555 */
556 public function bulk_action_handler( $redirect, $doaction, $image_ids ) {
557 if ( empty( $image_ids ) ) {
558 return $redirect;
559 }
560 $redirect = remove_query_arg( [ 'optimole_offload_images_succes', 'optimole_offload_images_failed', 'optimole_back_to_media_success', 'optimole_back_to_media_failed', 'optimole_offload_images_extra', 'optimole_rollback_images_extra' ], $redirect );
561
562 if ( $doaction === 'optimole_upload_image' ) {
563 if ( count( $image_ids ) > 5 ) {
564 $redirect = add_query_arg( 'optimole_offload_images_extra', 'true', $redirect );
565 $image_ids = array_slice( $image_ids, 0, 5, true );
566 }
567 $success_up = $this->upload_and_update_existing_images( $image_ids );
568 $redirect = add_query_arg( 'optimole_offload_images_succes', $success_up, $redirect );
569 $failed_up = count( $image_ids ) - $success_up;
570 if ( $failed_up > 0 ) {
571 $redirect = add_query_arg( 'optimole_offload_images_failed', $failed_up, $redirect );
572 }
573 }
574
575 if ( $doaction === 'optimole_back_to_media' ) {
576 if ( count( $image_ids ) > 5 ) {
577 $redirect = add_query_arg( 'optimole_rollback_images_extra', 'true', $redirect );
578 $image_ids = array_slice( $image_ids, 0, 5, true );
579 }
580 $success_back = $this->rollback_and_update_images( $image_ids );
581 $failed_down = count( $image_ids ) - $success_back;
582 if ( $failed_down > 0 ) {
583 $redirect = add_query_arg( 'optimole_back_to_media_failed', $failed_down, $redirect );
584 }
585 $redirect = add_query_arg( 'optimole_back_to_media_success', $success_back, $redirect );
586 }
587 return $redirect;
588
589 }
590 /**
591 * Register the bulk media actions.
592 *
593 * @param array $bulk_array The existing actions array.
594 * @return array The array with the appended actions.
595 */
596 public function register_bulk_media_actions( $bulk_array ) {
597
598 $bulk_array['optimole_upload_image'] = __( 'Push Image to Optimole', 'optimole-wp' );
599 $bulk_array['optimole_back_to_media'] = __( 'Restore image to media library', 'optimole-wp' );
600 return $bulk_array;
601
602 }
603 /**
604 * Register the possible notices for the media bulk actions.
605 */
606 public function bulk_action_notices() {
607 if ( ! empty( $_REQUEST['optimole_offload_images_extra'] ) ) {
608
609 printf(
610 '<div id="message" class="updated notice is-dismissible"><p>' .
611 __( 'You can not offload more than 5 images at once from this menu.', 'optimole-wp' ) . '</p></div>'
612 );
613
614 }
615 if ( ! empty( $_REQUEST['optimole_rollback_images_extra'] ) ) {
616
617 printf(
618 '<div id="message" class="updated notice is-dismissible"><p>' .
619 __( 'You can not move back to library more than 5 images at once from this menu.', 'optimole-wp' ) . '</p></div>'
620 );
621
622 }
623 if ( ! empty( $_REQUEST['optimole_offload_images_succes'] ) ) {
624
625 printf(
626 '<div id="message" class="updated notice is-dismissible"><p>' .
627 _n(
628 '%s image was stored on our server.',
629 '%s images were stored on our servers.',
630 intval( $_REQUEST['optimole_offload_images_succes'] ),
631 'optimole-wp'
632 ) . '</p></div>',
633 intval( $_REQUEST['optimole_offload_images_succes'] )
634 );
635
636 }
637 if ( ! empty( $_REQUEST['optimole_offload_images_failed'] ) ) {
638 printf(
639 '<div id="message" class="updated notice is-dismissible"><p>' .
640 _n(
641 '%s image failed while uploading to our servers, please try again later.',
642 '%s images failed while uploading to our servers, please try again later',
643 intval( $_REQUEST['optimole_offload_images_failed'] ) .
644 'optimole-wp'
645 ) . '</p></div>',
646 intval( $_REQUEST['optimole_offload_images_failed'] )
647 );
648
649 }
650 if ( ! empty( $_REQUEST['optimole_back_to_media_success'] ) ) {
651 printf(
652 '<div id="message" class="updated notice is-dismissible"><p>' .
653 _n(
654 '%s image was stored back on media library.',
655 '%s images were stored back on media library.',
656 intval( $_REQUEST['optimole_back_to_media_success'] ),
657 'optimole-wp'
658 ) . '</p></div>',
659 intval( $_REQUEST['optimole_back_to_media_success'] )
660 );
661
662 }
663 if ( ! empty( $_REQUEST['optimole_back_to_media_failed'] ) ) {
664 printf(
665 '<div id="message" class="updated notice is-dismissible"><p>' .
666 _n(
667 '%s image failed while restoring to media library, please try again later.',
668 '%s images failed while restoring to media library, please try again later',
669 intval( $_REQUEST['optimole_back_to_media_failed'] ) .
670 'optimole-wp'
671 ) . '</p></div>',
672 intval( $_REQUEST['optimole_back_to_media_failed'] )
673 );
674 }
675 }
676
677 /**
678 * Send delete request to our servers and update the meta.
679 *
680 * @param string $original_url Original url of the image.
681 * @param integer $post_id Image id inside db.
682 * @param string $table_id Our cloud id for the image.
683 */
684 public function delete_attachment_from_server( $original_url, $post_id, $table_id ) {
685 $request = new Optml_Api();
686 $delete_response = $request->call_upload_api( $original_url, 'true', $table_id );
687
688 delete_post_meta( $post_id, 'optimole_offload' );
689 if ( is_wp_error( $delete_response ) || wp_remote_retrieve_response_code( $delete_response ) !== 200 ) {
690 // should add some routine to retry delete once if delete fails
691 }
692 }
693 /**
694 * Delete an image from our servers after it is removed from media.
695 *
696 * @param int $post_id The deleted post id.
697 */
698 public function delete_attachment_hook( $post_id ) {
699 $file = wp_get_attachment_metadata( $post_id );
700 if ( $file === false || ! isset( $file['file'] ) ) {
701 return;
702 }
703 $file = $file['file'];
704 if ( self::is_uploaded_image( $file ) ) {
705
706 $original_url = self::get_original_url( $post_id );
707 if ( $original_url === false ) {
708 return;
709 }
710 $table_id = [];
711
712 preg_match( '/\/' . self::KEYS['uploaded_flag'] . '([^\/]*)\//', $file, $table_id );
713
714 if ( ! isset( $table_id[1] ) ) {
715 return;
716 }
717
718 $this->delete_attachment_from_server( $original_url, $post_id, $table_id[1] );
719 }
720 }
721
722 /**
723 * Get optimized URL for an attachment image if it is uploaded to our servers.
724 *
725 * @param string $url The current url.
726 * @param int $attachment_id The attachment image id.
727 * @return string Optimole cdn URL.
728 * @uses filter:wp_get_attachment_url
729 */
730 public function get_image_attachment_url( $url, $attachment_id ) {
731 if ( self::$return_original_url === true ) {
732 return $url;
733 }
734 $meta = wp_get_attachment_metadata( $attachment_id );
735 if ( ! isset( $meta['file'] ) ) {
736 return $url;
737 }
738 $file = $meta['file'];
739 if ( self::is_uploaded_image( $file ) ) {
740 $optimized_url = ( new Optml_Image( $url, ['width' => 'auto', 'height' => 'auto', 'quality' => $this->settings->get_numeric_quality()], $this->settings->get( 'cache_buster' ) ) )->get_url();
741 return str_replace( '/' . $url, '/' . self::KEYS['not_processed_flag'] . $attachment_id . $file, $optimized_url );
742 }
743 return $url;
744 }
745 /**
746 * Filter the requested image url.
747 *
748 * @param null $image The previous image value (null).
749 * @param int $attachment_id The ID of the attachment.
750 * @param string|array $size Requested size of image. Image size name, or array of width and height values (in that order).
751 *
752 * @return array The image sizes and optimized url.
753 * @uses filter:image_downsize
754 */
755 public function generate_filter_downsize_urls( $image, $attachment_id, $size ) {
756 if ( self::$return_original_url === true ) {
757 return $image;
758 }
759 $sizes2crop = self::size_to_crop();
760 if ( wp_attachment_is( 'video', $attachment_id ) && doing_action( 'wp_insert_post_data' ) ) {
761 return $image;
762 }
763 $data = image_get_intermediate_size( $attachment_id, $size );
764 if ( ! isset( $data['url'] ) || ! isset( $data['width'] ) || ! isset( $data['height'] ) || ! self::is_uploaded_image( $data['url'] ) ) {
765 return $image;
766 }
767 $resize = apply_filters( 'optml_default_crop', [] );
768 if ( isset( $sizes2crop[ $data['width'] . $data['height'] ] ) ) {
769 $resize = $this->to_optml_crop( $sizes2crop[ $data['width'] . $data['height'] ] );
770 }
771 $id_filename = [];
772
773 preg_match( '/\/(' . self::KEYS['not_processed_flag'] . '.*)/', $data['url'], $id_filename );
774 if ( ! isset( $id_filename[1] ) ) {
775 return $image;
776 }
777 $url = self::get_original_url( $attachment_id );
778 $optimized_url = ( new Optml_Image( $url, ['width' => $data['width'], 'height' => $data['height'], 'resize' => $resize, 'quality' => $this->settings->get_numeric_quality()], $this->settings->get( 'cache_buster' ) ) )->get_url();
779 $optimized_url = str_replace( $url, $id_filename[1], $optimized_url );
780 $image = [
781 $optimized_url,
782 $data['width'],
783 $data['height'],
784 true,
785 ];
786 return $image;
787 }
788 /**
789 * Get image extension.
790 *
791 * @param string $path Image path.
792 *
793 * @return string
794 */
795 private function get_ext( $path ) {
796 return pathinfo( $path, PATHINFO_EXTENSION );
797 }
798 /**
799 * Update image meta with optimized cdn path.
800 *
801 * @param array $meta Meta information of the image.
802 * @param int $attachment_id The image attachment ID.
803 *
804 * @return array
805 * @uses filter:wp_generate_attachment_metadata
806 */
807 public function generate_image_meta( $meta, $attachment_id ) {
808
809 if ( ! isset( $meta['file'] ) || ! isset( $meta['width'] ) || ! isset( $meta['height'] ) || self::is_uploaded_image( $meta['file'] ) ) {
810 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
811 return $meta;
812 }
813 if ( false === Optml_Filters::should_do_image( $meta['file'], self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_FILENAME ] ) ) {
814 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
815 return $meta;
816 }
817 $original_url = self::get_original_url( $attachment_id );
818
819 if ( $original_url === false ) {
820 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
821 return $meta;
822 }
823
824 $local_file = get_attached_file( $attachment_id );
825 if ( ! file_exists( $local_file ) ) {
826 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
827 return $meta;
828 }
829 $extension = $this->get_ext( $local_file );
830
831 if ( ! isset( Optml_Config::$image_extensions [ $extension ] ) ) {
832 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
833 return $meta;
834 }
835 if ( false === Optml_Filters::should_do_extension( self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_EXT ], $extension ) ) {
836 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
837 return $meta;
838 }
839
840 $content_type = Optml_Config::$image_extensions [ $extension ];
841 $temp = explode( '/', $local_file );
842 $file_name = end( $temp );
843
844 $request = new Optml_Api();
845 $generate_url_response = $request->call_upload_api( $original_url );
846
847 if ( is_wp_error( $generate_url_response ) || wp_remote_retrieve_response_code( $generate_url_response ) !== 200 ) {
848 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
849 return $meta;
850 }
851 $decoded_response = json_decode( $generate_url_response['body'], true );
852
853 if ( ! isset( $decoded_response['tableId'] ) || ! isset( $decoded_response['uploadUrl'] ) ) {
854 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
855 return $meta;
856 }
857 $table_id = $decoded_response['tableId'];
858 $upload_signed_url = $decoded_response['uploadUrl'];
859 $image = file_get_contents( $local_file );
860 if ( $image === false ) {
861 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
862 return $meta;
863 }
864 if ( $upload_signed_url !== 'found_resource' ) {
865
866 $request = new Optml_Api();
867 $result = $request->upload_image( $upload_signed_url, $content_type, $image );
868
869 if ( is_wp_error( $result ) || wp_remote_retrieve_response_code( $result ) !== 200 ) {
870 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
871 return $meta;
872 }
873 $file_size = filesize( $local_file );
874 if ( $file_size === false ) {
875 $file_size = 0;
876 }
877 $request = new Optml_Api();
878 $result_update = $request->call_upload_api(
879 $original_url,
880 'false',
881 $table_id,
882 'success',
883 'false',
884 $meta['width'],
885 $meta['height'],
886 $file_size
887 );
888 if ( is_wp_error( $result_update ) || wp_remote_retrieve_response_code( $result_update ) !== 200 ) {
889 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
890 return $meta;
891 }
892 }
893 $url_to_append = $original_url;
894 $url_parts = parse_url( $original_url );
895 if ( isset( $url_parts['scheme'] ) && isset( $url_parts['host'] ) ) {
896 $url_to_append = $url_parts['scheme'] . '://' . $url_parts['host'] . '/' . $file_name;
897 }
898 $optimized_url = $this->get_media_optimized_url( $url_to_append, $table_id );
899 $request = new Optml_Api();
900 if ( $request->check_optimized_url( $optimized_url ) === false ) {
901 $request->call_upload_api( $original_url, 'true', $table_id );
902 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
903 return $meta;
904 }
905 file_exists( $local_file ) && unlink( $local_file );
906 update_post_meta( $attachment_id, 'optimole_offload', 'true' );
907 $meta['file'] = '/' . self::KEYS['uploaded_flag'] . $table_id . '/' . $url_to_append;
908 if ( isset( $meta['sizes'] ) ) {
909 foreach ( $meta['sizes'] as $key => $value ) {
910 $generated_image_size_path = str_replace( $file_name, $meta['sizes'][ $key ]['file'], $local_file );
911 file_exists( $generated_image_size_path ) && unlink( $generated_image_size_path );
912 $meta['sizes'][ $key ]['file'] = $file_name;
913 }
914 }
915 return $meta;
916 }
917
918 /** Get the args for wp query according to the scope.
919 *
920 * @param int $batch Number of images to get.
921 * @param string $action The action for which to get the images.
922 * @return array|false The query options array or false if not passed a valid action.
923 */
924 public static function get_images_query_args( $batch, $action ) {
925 $args = [
926 'post_type' => 'attachment',
927 'post_mime_type' => [ 'image' ],
928 'post_status' => 'inherit',
929 'posts_per_page' => $batch,
930 'fields' => 'ids',
931 'ignore_sticky_posts' => false,
932 'no_found_rows' => true,
933 ];
934 if ( $action === 'offload_images' ) {
935 $args['meta_query'] = [
936 'relation' => 'AND',
937 [
938 'key' => 'optimole_offload',
939 'compare' => 'NOT EXISTS',
940 ],
941 [
942 'key' => 'optimole_offload_error',
943 'compare' => 'NOT EXISTS',
944 ],
945 ];
946 return $args;
947 }
948 if ( $action === 'rollback_images' ) {
949 $args['meta_query'] = [
950 'relation' => 'AND',
951 [
952 'key' => 'optimole_offload',
953 'value' => 'true',
954 'compare' => '=',
955 ],
956 [
957 'key' => 'optimole_rollback_error',
958 'compare' => 'NOT EXISTS',
959 ],
960 ];
961 return $args;
962 }
963 return false;
964 }
965 /**
966 * Query the database and upload images to our servers.
967 *
968 * @param int $batch Number of images to process in a batch.
969 * @return array Number of found images and number of successfully processed images.
970 */
971 public static function upload_images( $batch ) {
972 $args = self::get_images_query_args( $batch, 'offload_images' );
973 $attachments = new \WP_Query( $args );
974 $ids = $attachments->get_posts();
975 $media_offload = new Optml_Media_Offload();
976 $result = [ 'found_images' => count( $ids ) ];
977 $result['success_offload'] = $media_offload->upload_and_update_existing_images( $ids );
978 return $result;
979 }
980
981 /**
982 * Query the database and bring back image to media library.
983 *
984 * @param int $batch Number of images to process in a batch.
985 * @return array Number of found images and number of successfully processed images.
986 */
987 public static function rollback_images( $batch ) {
988 $args = self::get_images_query_args( $batch, 'rollback_images' );
989 $attachments = new \WP_Query( $args );
990 $ids = $attachments->get_posts();
991 $media_offload = new Optml_Media_Offload();
992 $result = [ 'found_images' => count( $ids ) ];
993 $result['success_rollback'] = $media_offload->rollback_and_update_images( $ids );
994 return $result;
995 }
996 /**
997 * Calculate the number of images in media library.
998 *
999 * @param string $action The actions for which to get the number of images.
1000 * @return int Number of images.
1001 */
1002 public static function number_of_library_images( $action ) {
1003 $args = self::get_images_query_args( -1, $action );
1004 $attachments = new \WP_Query( $args );
1005 return $attachments->post_count;
1006 }
1007 }
1008