PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 3.1.0
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v3.1.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.1.0, at inc/media_offload.php

1,123 lines 36.7 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 = 'auto';
238 $width = 'auto';
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 /**
282 * Get attachment id from url
283 *
284 * @param string $url The optimized url .
285 * @return false|mixed The attachment id .
286 */
287 public static function get_attachment_id_from_url( $url ) {
288 preg_match( '/\/' . Optml_Media_Offload::KEYS['not_processed_flag'] . '([^\/]*)\//', $url, $attachment_id );
289 return isset( $attachment_id[1] ) ? $attachment_id[1] : false;
290 }
291 /**
292 * Filter out the urls that are saved to our servers when saving to the DB.
293 *
294 * @param array $data The post data array to save.
295 *
296 * @return array
297 * @uses filter:wp_insert_post_data
298 */
299 public function filter_uploaded_images( $data ) {
300
301 $content = trim( wp_unslash( $data['post_content'] ) );
302 if ( OPTML_DEBUG ) {
303 do_action( 'optml_log', 'content to update' );
304 do_action( 'optml_log', $content );
305 }
306 $images = Optml_Manager::instance()->extract_urls_from_content( $content );
307 if ( ! isset( $images[0] ) ) {
308 return $data;
309 }
310 if ( OPTML_DEBUG ) {
311 do_action( 'optml_log', 'images to update' );
312 do_action( 'optml_log', $images );
313 }
314 foreach ( $images as $url ) {
315 $is_original_uploaded = self::is_uploaded_image( $url );
316 $size = 'full';
317 $attachment_id = false;
318 if ( $is_original_uploaded ) {
319 $found_size = $this->parse_dimension_from_optimized_url( $url );
320 if ( $found_size[0] !== 'auto' && $found_size[1] !== 'auto' ) {
321 $size = $found_size;
322 }
323 $attachment_id = self::get_attachment_id_from_url( $url );
324 } else {
325 $found_size = $this->parse_dimensions_from_filename( $url );
326 $strip_url = $url;
327 if ( $found_size[0] !== false && $found_size[1] !== false ) {
328 $size = $found_size;
329 $strip_url = str_replace( '-' . $found_size[0] . 'x' . $found_size[1], '', $url );
330 }
331 $attachment_id = attachment_url_to_postid( $strip_url );
332 }
333
334 if ( OPTML_DEBUG ) {
335 do_action( 'optml_log', 'image id and found size' );
336 do_action( 'optml_log', $attachment_id );
337 do_action( 'optml_log', $size );
338 }
339 if ( false === $attachment_id || ! wp_attachment_is_image( $attachment_id ) ) {
340 continue;
341 }
342 $optimized_url = wp_get_attachment_image_src( $attachment_id, $size );
343 if ( OPTML_DEBUG ) {
344 do_action( 'optml_log', ' image url to replace with ' );
345 do_action( 'optml_log', $optimized_url );
346 }
347
348 if ( ! isset( $optimized_url[0] ) ) {
349 continue;
350 }
351 if ( $is_original_uploaded === self::is_uploaded_image( $optimized_url[0] ) ) {
352 continue;
353 }
354 $content = str_replace( $url, $optimized_url[0], $content );
355 }
356 $data['post_content'] = wp_slash( $content );
357 return $data;
358 }
359 /**
360 * Trigger an update on content that contains the same attachment ID as the modified image.
361 */
362 public function update_content() {
363 if ( OPTML_DEBUG ) {
364 do_action( 'optml_log', ' updating_content ' );
365 }
366 $have_posts = true;
367 $page = 1;
368 while ( $have_posts ) {
369 $have_posts = false;
370 $query_args = apply_filters(
371 'optml_replacement_wp_query_args',
372 ['post_type' => 'any', 'post_status' => 'any', 'fields' => 'ids',
373 'posts_per_page' => 15,
374 'update_post_meta_cache' => false,
375 'update_post_term_cache' => false,
376 ]
377 );
378 $query_args['paged'] = $page;
379 $content = new \WP_Query( $query_args );
380 if ( OPTML_DEBUG ) {
381 do_action( 'optml_log', $page );
382 }
383 if ( $content->have_posts() ) {
384 $have_posts = true;
385 while ( $content->have_posts() ) {
386 $content->the_post();
387 $content_id = get_the_ID();
388 if ( get_post_type() !== 'attachment' ) {
389 if ( OPTML_DEBUG ) {
390 do_action( 'optml_log', 'post_id ' . $content_id );
391 }
392 wp_update_post( ['ID' => $content_id] );
393 }
394 }
395 $page ++;
396 }
397 }
398 }
399 /**
400 * Add inline action to push to our servers.
401 *
402 * @param array $actions All actions.
403 * @param \WP_Post $post The current post image object.
404 *
405 * @return array
406 */
407 public function add_inline_media_action( $actions, $post ) {
408 $file = wp_get_attachment_metadata( $post->ID )['file'];
409 if ( wp_check_filetype( $file, Optml_Config::$all_extensions )['ext'] === false || ! current_user_can( 'delete_post', $post->ID ) ) {
410 return $actions;
411 }
412 if ( ! self::is_uploaded_image( $file ) ) {
413 $upload_action_url = add_query_arg(
414 [
415 'action' => 'optimole_upload_image',
416 'media[]' => $post->ID,
417 '_wpnonce' => wp_create_nonce( 'bulk-media' ),
418 ],
419 'upload.php'
420 );
421
422 $actions['optimole_upload_image'] = sprintf(
423 '<a href="%s" aria-label="%s">%s</a>',
424 $upload_action_url,
425 esc_attr__( 'Offload to Optimole', 'optimole-wp' ),
426 esc_html__( 'Offload to Optimole', 'optimole-wp' )
427 );
428 }
429 if ( self::is_uploaded_image( $file ) ) {
430 $rollback_action_url = add_query_arg(
431 [
432 'action' => 'optimole_back_to_media',
433 'media[]' => $post->ID,
434 '_wpnonce' => wp_create_nonce( 'bulk-media' ),
435 ],
436 'upload.php'
437 );
438 $actions['optimole_back_to_media'] = sprintf(
439 '<a href="%s" aria-label="%s">%s</a>',
440 $rollback_action_url,
441 esc_attr__( 'Restore image to media library', 'optimole-wp' ),
442 esc_html__( 'Restore image to media library', 'optimole-wp' )
443 );
444 }
445 return $actions;
446 }
447 /**
448 * Upload images to our servers and update inside pages.
449 *
450 * @param array $image_ids The id of the attachments for the selected images.
451 * @return int The number of successfully processed images.
452 */
453 public function upload_and_update_existing_images( $image_ids ) {
454 $success_up = 0;
455 if ( OPTML_DEBUG ) {
456 do_action( 'optml_log', ' images to upload ' );
457 do_action( 'optml_log', $image_ids );
458 }
459 foreach ( $image_ids as $id ) {
460 if ( self::is_uploaded_image( wp_get_attachment_metadata( $id )['file'] ) ) {
461 $success_up ++;
462 continue;
463 }
464
465 $meta = $this->generate_image_meta( wp_get_attachment_metadata( $id ), $id );
466 if ( isset( $meta['file'] ) && self::is_uploaded_image( $meta['file'] ) ) {
467 $success_up ++;
468 wp_update_attachment_metadata( $id, $meta );
469 }
470 }
471 if ( $success_up > 0 ) {
472 if ( OPTML_DEBUG ) {
473 do_action( 'optml_log', ' call post update, succesful images: ' );
474 do_action( 'optml_log', $success_up );
475 }
476 $this->update_content();
477 }
478 return $success_up;
479 }
480
481 /**
482 * Return the original url of an image attachment.
483 *
484 * @param integer $post_id Image attachment id.
485 * @return string The original url of the image.
486 */
487 public static function get_original_url( $post_id ) {
488 self::$return_original_url = true;
489 $original_url = wp_get_attachment_url( $post_id );
490 self::$return_original_url = false;
491 return $original_url;
492 }
493 /**
494 * Bring images back to media library and update inside pages.
495 *
496 * @param array $image_ids The id of the attachments for the selected images.
497 * @return int The number of successfully processed images.
498 */
499 public function rollback_and_update_images( $image_ids ) {
500 $success_back = 0;
501 if ( OPTML_DEBUG ) {
502 do_action( 'optml_log', ' images to rollback ' );
503 do_action( 'optml_log', $image_ids );
504 }
505 foreach ( $image_ids as $id ) {
506 $current_meta = wp_get_attachment_metadata( $id );
507 if ( ! isset( $current_meta['file'] ) || ! self::is_uploaded_image( $current_meta['file'] ) ) {
508 delete_post_meta( $id, 'optimole_offload' );
509 $success_back++;
510 continue;
511 }
512 $table_id = [];
513 $filename = pathinfo( $current_meta['file'], PATHINFO_BASENAME );
514 preg_match( '/\/' . self::KEYS['uploaded_flag'] . '([^\/]*)\//', $current_meta['file'], $table_id );
515 if ( ! isset( $table_id[1] ) ) {
516 continue;
517 }
518 $table_id = $table_id[1];
519 if ( OPTML_DEBUG ) {
520 do_action( 'optml_log', ' image cloud id ' );
521 do_action( 'optml_log', $table_id );
522 }
523 $request = new Optml_Api();
524 $get_response = $request->call_upload_api( '', 'false', $table_id, 'false', 'true' );
525
526 if ( is_wp_error( $get_response ) || wp_remote_retrieve_response_code( $get_response ) !== 200 ) {
527 update_post_meta( $id, 'optimole_rollback_error', 'true' );
528 if ( OPTML_DEBUG ) {
529 do_action( 'optml_log', ' error get url' );
530 }
531 continue;
532 }
533
534 $get_url = json_decode( $get_response['body'], true )['getUrl'];
535
536 if ( ! function_exists( 'download_url' ) ) {
537 include_once ABSPATH . 'wp-admin/includes/file.php';
538 }
539 if ( ! function_exists( 'download_url' ) ) {
540 update_post_meta( $id, 'optimole_rollback_error', 'true' );
541 continue;
542 }
543 $timeout_seconds = 60;
544 $temp_file = download_url( $get_url, $timeout_seconds );
545
546 if ( is_wp_error( $temp_file ) ) {
547 update_post_meta( $id, 'optimole_rollback_error', 'true' );
548 if ( OPTML_DEBUG ) {
549 do_action( 'optml_log', ' download_url error ' );
550 }
551 continue;
552 }
553
554 $extension = $this->get_ext( $filename );
555
556 if ( ! isset( Optml_Config::$image_extensions [ $extension ] ) ) {
557 if ( OPTML_DEBUG ) {
558 do_action( 'optml_log', ' image has invalid extension' );
559 do_action( 'optml_log', $extension );
560 }
561 update_post_meta( $id, 'optimole_rollback_error', 'true' );
562 continue;
563 }
564
565 $type = Optml_Config::$image_extensions [ $extension ];
566 $file = [
567 'name' => $filename,
568 'type' => $type,
569 'tmp_name' => $temp_file,
570 'error' => 0,
571 'size' => filesize( $temp_file ),
572 ];
573
574 $overrides = [
575 // do not expect the default form data from normal uploads
576 'test_form' => false,
577
578 // Setting this to false lets WordPress allow empty files, not recommended.
579 'test_size' => true,
580
581 // A properly uploaded file will pass this test. There should be no reason to override this one.
582 'test_upload' => true,
583 ];
584
585 if ( ! function_exists( 'wp_handle_sideload' ) ) {
586 include_once ABSPATH . '/wp-admin/includes/file.php';
587 }
588 if ( ! function_exists( 'wp_handle_sideload' ) ) {
589 update_post_meta( $id, 'optimole_rollback_error', 'true' );
590 continue;
591 }
592
593 // Move the temporary file into the uploads directory.
594 $results = wp_handle_sideload( $file, $overrides );
595 if ( ! empty( $results['error'] ) ) {
596 if ( OPTML_DEBUG ) {
597 do_action( 'optml_log', ' wp_handle_sideload error' );
598 }
599 update_post_meta( $id, 'optimole_rollback_error', 'true' );
600 continue;
601 }
602
603 if ( ! function_exists( 'wp_create_image_subsizes' ) ) {
604 include_once ABSPATH . '/wp-admin/includes/image.php';
605 }
606 if ( ! function_exists( 'wp_create_image_subsizes' ) ) {
607 update_post_meta( $id, 'optimole_rollback_error', 'true' );
608 continue;
609 }
610 wp_create_image_subsizes( $results['file'], $id );
611 if ( $type === 'image/svg+xml' ) {
612 if ( ! function_exists( 'wp_get_attachment_metadata' ) || ! function_exists( 'wp_update_attachment_metadata' ) ) {
613 include_once ABSPATH . '/wp-admin/includes/post.php';
614 }
615 if ( ! function_exists( 'wp_get_attachment_metadata' ) ) {
616 update_post_meta( $id, 'optimole_rollback_error', 'true' );
617 continue;
618 }
619 $meta = wp_get_attachment_metadata( $id );
620 if ( ! isset( $meta['file'] ) ) {
621 update_post_meta( $id, 'optimole_rollback_error', 'true' );
622 continue;
623 }
624 $meta['file'] = $results['file'];
625 wp_update_attachment_metadata( $id, $meta );
626 }
627
628 if ( ! function_exists( 'update_attached_file' ) ) {
629 include_once ABSPATH . '/wp-admin/includes/post.php';
630 }
631 if ( ! function_exists( 'update_attached_file' ) ) {
632 update_post_meta( $id, 'optimole_rollback_error', 'true' );
633 continue;
634 }
635 update_attached_file( $id, $results['file'] );
636 $success_back++;
637 $original_url = self::get_original_url( $id );
638 if ( $original_url === false ) {
639 continue;
640 }
641 $this->delete_attachment_from_server( $original_url, $id, $table_id );
642 }
643 if ( $success_back > 0 ) {
644 if ( OPTML_DEBUG ) {
645 do_action( 'optml_log', ' call update post, success rollback' );
646 do_action( 'optml_log', $success_back );
647 }
648 $this->update_content();
649 }
650 return $success_back;
651 }
652
653 /**
654 * Handle the bulk actions.
655 *
656 * @param string $redirect The current url from the media library.
657 * @param string $doaction The current action selected.
658 * @param array $image_ids The id of the attachments for the selected images.
659 * @return string The url with the correspondent query args for the executed actions.
660 */
661 public function bulk_action_handler( $redirect, $doaction, $image_ids ) {
662 if ( empty( $image_ids ) ) {
663 return $redirect;
664 }
665 $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 );
666
667 if ( $doaction === 'optimole_upload_image' ) {
668 if ( count( $image_ids ) > 5 ) {
669 $redirect = add_query_arg( 'optimole_offload_images_extra', 'true', $redirect );
670 $image_ids = array_slice( $image_ids, 0, 5, true );
671 }
672 $success_up = $this->upload_and_update_existing_images( $image_ids );
673 $redirect = add_query_arg( 'optimole_offload_images_succes', $success_up, $redirect );
674 $failed_up = count( $image_ids ) - $success_up;
675 if ( $failed_up > 0 ) {
676 $redirect = add_query_arg( 'optimole_offload_images_failed', $failed_up, $redirect );
677 }
678 }
679
680 if ( $doaction === 'optimole_back_to_media' ) {
681 if ( count( $image_ids ) > 5 ) {
682 $redirect = add_query_arg( 'optimole_rollback_images_extra', 'true', $redirect );
683 $image_ids = array_slice( $image_ids, 0, 5, true );
684 }
685 $success_back = $this->rollback_and_update_images( $image_ids );
686 $failed_down = count( $image_ids ) - $success_back;
687 if ( $failed_down > 0 ) {
688 $redirect = add_query_arg( 'optimole_back_to_media_failed', $failed_down, $redirect );
689 }
690 $redirect = add_query_arg( 'optimole_back_to_media_success', $success_back, $redirect );
691 }
692 return $redirect;
693
694 }
695 /**
696 * Register the bulk media actions.
697 *
698 * @param array $bulk_array The existing actions array.
699 * @return array The array with the appended actions.
700 */
701 public function register_bulk_media_actions( $bulk_array ) {
702
703 $bulk_array['optimole_upload_image'] = __( 'Push Image to Optimole', 'optimole-wp' );
704 $bulk_array['optimole_back_to_media'] = __( 'Restore image to media library', 'optimole-wp' );
705 return $bulk_array;
706
707 }
708 /**
709 * Register the possible notices for the media bulk actions.
710 */
711 public function bulk_action_notices() {
712 if ( ! empty( $_REQUEST['optimole_offload_images_extra'] ) ) {
713
714 printf(
715 '<div id="message" class="updated notice is-dismissible"><p>' .
716 __( 'You can not offload more than 5 images at once from this menu.', 'optimole-wp' ) . '</p></div>'
717 );
718
719 }
720 if ( ! empty( $_REQUEST['optimole_rollback_images_extra'] ) ) {
721
722 printf(
723 '<div id="message" class="updated notice is-dismissible"><p>' .
724 __( 'You can not move back to library more than 5 images at once from this menu.', 'optimole-wp' ) . '</p></div>'
725 );
726
727 }
728 if ( ! empty( $_REQUEST['optimole_offload_images_succes'] ) ) {
729
730 printf(
731 '<div id="message" class="updated notice is-dismissible"><p>' .
732 _n(
733 '%s image was moved on Optimole Cloud.',
734 '%s images were moved on Optimole Cloud.',
735 intval( $_REQUEST['optimole_offload_images_succes'] ),
736 'optimole-wp'
737 ) . '</p></div>',
738 intval( $_REQUEST['optimole_offload_images_succes'] )
739 );
740
741 }
742 if ( ! empty( $_REQUEST['optimole_offload_images_failed'] ) ) {
743 printf(
744 '<div id="message" class="updated notice is-dismissible"><p>' .
745 _n(
746 '%s image failed while uploading to Optimole Cloud, please try again later.',
747 '%s images failed while uploading to Optimole Cloud, please try again later',
748 intval( $_REQUEST['optimole_offload_images_failed'] ) .
749 'optimole-wp'
750 ) . '</p></div>',
751 intval( $_REQUEST['optimole_offload_images_failed'] )
752 );
753
754 }
755 if ( ! empty( $_REQUEST['optimole_back_to_media_success'] ) ) {
756 printf(
757 '<div id="message" class="updated notice is-dismissible"><p>' .
758 _n(
759 '%s image was stored back on media library.',
760 '%s images were stored back on media library.',
761 intval( $_REQUEST['optimole_back_to_media_success'] ),
762 'optimole-wp'
763 ) . '</p></div>',
764 intval( $_REQUEST['optimole_back_to_media_success'] )
765 );
766
767 }
768 if ( ! empty( $_REQUEST['optimole_back_to_media_failed'] ) ) {
769 printf(
770 '<div id="message" class="updated notice is-dismissible"><p>' .
771 _n(
772 '%s image failed while restoring to media library, please try again later.',
773 '%s images failed while restoring to media library, please try again later',
774 intval( $_REQUEST['optimole_back_to_media_failed'] ) .
775 'optimole-wp'
776 ) . '</p></div>',
777 intval( $_REQUEST['optimole_back_to_media_failed'] )
778 );
779 }
780 }
781
782 /**
783 * Send delete request to our servers and update the meta.
784 *
785 * @param string $original_url Original url of the image.
786 * @param integer $post_id Image id inside db.
787 * @param string $table_id Our cloud id for the image.
788 */
789 public function delete_attachment_from_server( $original_url, $post_id, $table_id ) {
790 $request = new Optml_Api();
791 $delete_response = $request->call_upload_api( $original_url, 'true', $table_id );
792
793 delete_post_meta( $post_id, 'optimole_offload' );
794 if ( is_wp_error( $delete_response ) || wp_remote_retrieve_response_code( $delete_response ) !== 200 ) {
795 // should add some routine to retry delete once if delete fails
796 }
797 }
798 /**
799 * Delete an image from our servers after it is removed from media.
800 *
801 * @param int $post_id The deleted post id.
802 */
803 public function delete_attachment_hook( $post_id ) {
804 $file = wp_get_attachment_metadata( $post_id );
805 if ( $file === false || ! isset( $file['file'] ) ) {
806 return;
807 }
808 $file = $file['file'];
809 if ( self::is_uploaded_image( $file ) ) {
810
811 $original_url = self::get_original_url( $post_id );
812 if ( $original_url === false ) {
813 return;
814 }
815 $table_id = [];
816
817 preg_match( '/\/' . self::KEYS['uploaded_flag'] . '([^\/]*)\//', $file, $table_id );
818
819 if ( ! isset( $table_id[1] ) ) {
820 return;
821 }
822
823 $this->delete_attachment_from_server( $original_url, $post_id, $table_id[1] );
824 }
825 }
826
827 /**
828 * Get optimized URL for an attachment image if it is uploaded to our servers.
829 *
830 * @param string $url The current url.
831 * @param int $attachment_id The attachment image id.
832 * @return string Optimole cdn URL.
833 * @uses filter:wp_get_attachment_url
834 */
835 public function get_image_attachment_url( $url, $attachment_id ) {
836 if ( self::$return_original_url === true ) {
837 return $url;
838 }
839 $meta = wp_get_attachment_metadata( $attachment_id );
840 if ( ! isset( $meta['file'] ) ) {
841 return $url;
842 }
843 $file = $meta['file'];
844 if ( self::is_uploaded_image( $file ) ) {
845 $optimized_url = ( new Optml_Image( $url, ['width' => 'auto', 'height' => 'auto', 'quality' => $this->settings->get_numeric_quality()], $this->settings->get( 'cache_buster' ) ) )->get_url();
846 return str_replace( '/' . $url, '/' . self::KEYS['not_processed_flag'] . $attachment_id . $file, $optimized_url );
847 }
848 return $url;
849 }
850 /**
851 * Filter the requested image url.
852 *
853 * @param null $image The previous image value (null).
854 * @param int $attachment_id The ID of the attachment.
855 * @param string|array $size Requested size of image. Image size name, or array of width and height values (in that order).
856 *
857 * @return array The image sizes and optimized url.
858 * @uses filter:image_downsize
859 */
860 public function generate_filter_downsize_urls( $image, $attachment_id, $size ) {
861 if ( self::$return_original_url === true ) {
862 return $image;
863 }
864 $sizes2crop = self::size_to_crop();
865 if ( wp_attachment_is( 'video', $attachment_id ) && doing_action( 'wp_insert_post_data' ) ) {
866 return $image;
867 }
868 $data = image_get_intermediate_size( $attachment_id, $size );
869 if ( ! isset( $data['url'] ) || ! isset( $data['width'] ) || ! isset( $data['height'] ) || ! self::is_uploaded_image( $data['url'] ) ) {
870 return $image;
871 }
872 $resize = apply_filters( 'optml_default_crop', [] );
873 if ( isset( $sizes2crop[ $data['width'] . $data['height'] ] ) ) {
874 $resize = $this->to_optml_crop( $sizes2crop[ $data['width'] . $data['height'] ] );
875 }
876 $id_filename = [];
877
878 preg_match( '/\/(' . self::KEYS['not_processed_flag'] . '.*)/', $data['url'], $id_filename );
879 if ( ! isset( $id_filename[1] ) ) {
880 return $image;
881 }
882 $url = self::get_original_url( $attachment_id );
883 $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();
884 $optimized_url = str_replace( $url, $id_filename[1], $optimized_url );
885 $image = [
886 $optimized_url,
887 $data['width'],
888 $data['height'],
889 true,
890 ];
891 return $image;
892 }
893 /**
894 * Get image extension.
895 *
896 * @param string $path Image path.
897 *
898 * @return string
899 */
900 private function get_ext( $path ) {
901 return pathinfo( $path, PATHINFO_EXTENSION );
902 }
903 /**
904 * Update image meta with optimized cdn path.
905 *
906 * @param array $meta Meta information of the image.
907 * @param int $attachment_id The image attachment ID.
908 *
909 * @return array
910 * @uses filter:wp_generate_attachment_metadata
911 */
912 public function generate_image_meta( $meta, $attachment_id ) {
913
914 if ( ! isset( $meta['file'] ) || ! isset( $meta['width'] ) || ! isset( $meta['height'] ) || self::is_uploaded_image( $meta['file'] ) ) {
915 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
916 return $meta;
917 }
918 if ( false === Optml_Filters::should_do_image( $meta['file'], self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_FILENAME ] ) ) {
919 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
920 return $meta;
921 }
922 $original_url = self::get_original_url( $attachment_id );
923
924 if ( $original_url === false ) {
925 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
926 return $meta;
927 }
928
929 $local_file = get_attached_file( $attachment_id );
930 if ( ! file_exists( $local_file ) ) {
931 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
932 return $meta;
933 }
934 $extension = $this->get_ext( $local_file );
935
936 if ( ! isset( Optml_Config::$image_extensions [ $extension ] ) ) {
937 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
938 return $meta;
939 }
940 if ( false === Optml_Filters::should_do_extension( self::$filters[ Optml_Settings::FILTER_TYPE_OPTIMIZE ][ Optml_Settings::FILTER_EXT ], $extension ) ) {
941 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
942 return $meta;
943 }
944
945 $content_type = Optml_Config::$image_extensions [ $extension ];
946 $temp = explode( '/', $local_file );
947 $file_name = end( $temp );
948
949 $request = new Optml_Api();
950 $generate_url_response = $request->call_upload_api( $original_url );
951
952 if ( is_wp_error( $generate_url_response ) || wp_remote_retrieve_response_code( $generate_url_response ) !== 200 ) {
953 if ( OPTML_DEBUG ) {
954 do_action( 'optml_log', ' call to signed url error' );
955 }
956 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
957 return $meta;
958 }
959 $decoded_response = json_decode( $generate_url_response['body'], true );
960
961 if ( ! isset( $decoded_response['tableId'] ) || ! isset( $decoded_response['uploadUrl'] ) ) {
962 if ( OPTML_DEBUG ) {
963 do_action( 'optml_log', ' missing table id or upload url' );
964 }
965 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
966 return $meta;
967 }
968 $table_id = $decoded_response['tableId'];
969 if ( OPTML_DEBUG ) {
970 do_action( 'optml_log', ' table id' );
971 do_action( 'optml_log', $table_id );
972 }
973 $upload_signed_url = $decoded_response['uploadUrl'];
974 $image = file_get_contents( $local_file );
975 if ( $image === false ) {
976 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
977 return $meta;
978 }
979 if ( $upload_signed_url !== 'found_resource' ) {
980
981 $request = new Optml_Api();
982 $result = $request->upload_image( $upload_signed_url, $content_type, $image );
983
984 if ( is_wp_error( $result ) || wp_remote_retrieve_response_code( $result ) !== 200 ) {
985 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
986 return $meta;
987 }
988 $file_size = filesize( $local_file );
989 if ( $file_size === false ) {
990 $file_size = 0;
991 }
992 $request = new Optml_Api();
993 $result_update = $request->call_upload_api(
994 $original_url,
995 'false',
996 $table_id,
997 'success',
998 'false',
999 $meta['width'],
1000 $meta['height'],
1001 $file_size
1002 );
1003 if ( is_wp_error( $result_update ) || wp_remote_retrieve_response_code( $result_update ) !== 200 ) {
1004 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
1005 return $meta;
1006 }
1007 }
1008 $url_to_append = $original_url;
1009 $url_parts = parse_url( $original_url );
1010 if ( isset( $url_parts['scheme'] ) && isset( $url_parts['host'] ) ) {
1011 $url_to_append = $url_parts['scheme'] . '://' . $url_parts['host'] . '/' . $file_name;
1012 }
1013 $optimized_url = $this->get_media_optimized_url( $url_to_append, $table_id );
1014 $request = new Optml_Api();
1015 if ( $request->check_optimized_url( $optimized_url ) === false ) {
1016 $request->call_upload_api( $original_url, 'true', $table_id );
1017 update_post_meta( $attachment_id, 'optimole_offload_error', 'true' );
1018 return $meta;
1019 }
1020 file_exists( $local_file ) && unlink( $local_file );
1021 update_post_meta( $attachment_id, 'optimole_offload', 'true' );
1022 $meta['file'] = '/' . self::KEYS['uploaded_flag'] . $table_id . '/' . $url_to_append;
1023 if ( isset( $meta['sizes'] ) ) {
1024 foreach ( $meta['sizes'] as $key => $value ) {
1025 $generated_image_size_path = str_replace( $file_name, $meta['sizes'][ $key ]['file'], $local_file );
1026 file_exists( $generated_image_size_path ) && unlink( $generated_image_size_path );
1027 $meta['sizes'][ $key ]['file'] = $file_name;
1028 }
1029 }
1030 return $meta;
1031 }
1032
1033 /** Get the args for wp query according to the scope.
1034 *
1035 * @param int $batch Number of images to get.
1036 * @param string $action The action for which to get the images.
1037 * @return array|false The query options array or false if not passed a valid action.
1038 */
1039 public static function get_images_query_args( $batch, $action ) {
1040 $args = [
1041 'post_type' => 'attachment',
1042 'post_mime_type' => [ 'image' ],
1043 'post_status' => 'inherit',
1044 'posts_per_page' => $batch,
1045 'fields' => 'ids',
1046 'ignore_sticky_posts' => false,
1047 'no_found_rows' => true,
1048 ];
1049 if ( $action === 'offload_images' ) {
1050 $args['meta_query'] = [
1051 'relation' => 'AND',
1052 [
1053 'key' => 'optimole_offload',
1054 'compare' => 'NOT EXISTS',
1055 ],
1056 [
1057 'key' => 'optimole_offload_error',
1058 'compare' => 'NOT EXISTS',
1059 ],
1060 ];
1061 return $args;
1062 }
1063 if ( $action === 'rollback_images' ) {
1064 $args['meta_query'] = [
1065 'relation' => 'AND',
1066 [
1067 'key' => 'optimole_offload',
1068 'value' => 'true',
1069 'compare' => '=',
1070 ],
1071 [
1072 'key' => 'optimole_rollback_error',
1073 'compare' => 'NOT EXISTS',
1074 ],
1075 ];
1076 return $args;
1077 }
1078 return false;
1079 }
1080 /**
1081 * Query the database and upload images to our servers.
1082 *
1083 * @param int $batch Number of images to process in a batch.
1084 * @return array Number of found images and number of successfully processed images.
1085 */
1086 public static function upload_images( $batch ) {
1087 $args = self::get_images_query_args( $batch, 'offload_images' );
1088 $attachments = new \WP_Query( $args );
1089 $ids = $attachments->get_posts();
1090 $media_offload = new Optml_Media_Offload();
1091 $result = [ 'found_images' => count( $ids ) ];
1092 $result['success_offload'] = $media_offload->upload_and_update_existing_images( $ids );
1093 return $result;
1094 }
1095
1096 /**
1097 * Query the database and bring back image to media library.
1098 *
1099 * @param int $batch Number of images to process in a batch.
1100 * @return array Number of found images and number of successfully processed images.
1101 */
1102 public static function rollback_images( $batch ) {
1103 $args = self::get_images_query_args( $batch, 'rollback_images' );
1104 $attachments = new \WP_Query( $args );
1105 $ids = $attachments->get_posts();
1106 $media_offload = new Optml_Media_Offload();
1107 $result = [ 'found_images' => count( $ids ) ];
1108 $result['success_rollback'] = $media_offload->rollback_and_update_images( $ids );
1109 return $result;
1110 }
1111 /**
1112 * Calculate the number of images in media library.
1113 *
1114 * @param string $action The actions for which to get the number of images.
1115 * @return int Number of images.
1116 */
1117 public static function number_of_library_images( $action ) {
1118 $args = self::get_images_query_args( -1, $action );
1119 $attachments = new \WP_Query( $args );
1120 return $attachments->post_count;
1121 }
1122 }
1123