PluginProbe
WP-Stateless – Google Cloud Storage / 3.0
WP-Stateless – Google Cloud Storage v3.0
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / lib / classes / compatibility / shortpixel.php

shortpixel.php in WP-Stateless – Google Cloud Storage 3.0, at lib/classes/compatibility/shortpixel.php

494 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: ShortPixel Image Optimizer
4 * Plugin URI: https://wordpress.org/plugins/shortpixel-image-optimiser/
5 *
6 * Compatibility Description: Ensures compatibility with ShortPixel Image Optimizer.
7 *
8 * We don't need to download image to server from GCS to optimize image. As it can directly use GCS URL.
9 *
10 * @todo Use backup version of image on Regenerate and Sync with GCS.
11 * @todo Restore image now download from backup dir then upload to regular GCS path again. We need to fix that.
12 * https://cloud.google.com/storage/docs/renaming-copying-moving-objects#copy
13 * @todo implement a customized version of ShortPixelMetaFacade::getURLsAndPATHs() function.
14 * @todo convert GCS URL from http://storage.googleapis.com/udx-ci-develop-alim/ to http://udx-ci-develop-alim.storage.googleapis.com
15 */
16
17 namespace wpCloud\StatelessMedia {
18
19 if( !class_exists( 'wpCloud\StatelessMedia\ShortPixel' ) ) {
20
21 class ShortPixel extends ICompatibility {
22
23 protected $id = 'shortpixel';
24 protected $title = 'ShortPixel Image Optimizer';
25 protected $constant = 'WP_STATELESS_COMPATIBILITY_SHORTPIXEL';
26 protected $description = 'Ensures compatibility with ShortPixel Image Optimizer.';
27 protected $plugin_file = 'shortpixel-image-optimiser/wp-shortpixel.php';
28 protected $sm_mode_not_supported = [ 'stateless' ];
29
30 /**
31 * @param $sm
32 */
33 public function module_init( $sm ) {
34 add_action( 'shortpixel_image_optimised', array( $this, 'shortpixel_image_optimised' ) );
35 add_filter( 'shortpixel_image_exists', array( $this, 'shortpixel_image_exists' ), 10, 3 );
36 // A way to disable the URL conversion to subdomain format.
37 if( !defined( 'WP_STATELESS_MEDIA_SHORTPIXEL_DISABLE_SUBDOMAIN_LINK' ) || !WP_STATELESS_MEDIA_SHORTPIXEL_DISABLE_SUBDOMAIN_LINK ) {
38 // URL conversion to subdomain format {bucket}.storage.googleapis.com instead of storage.googleapis.com/{bucket}
39 // So that ShortPixel don't count all Stateless request as one domain.
40 add_filter( 'shortpixel_image_urls', array( $this, 'shortpixel_image_urls' ), 10, 2 );
41 }
42 add_filter( 'shortpixel_skip_backup', array( $this, 'shortpixel_skip_backup' ), 10, 3 );
43 add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 10, 2 );
44 // Remove backup and webp version of image.
45 add_filter( 'shortpixel_skip_delete_backups_and_webps', array( $this, 'shortpixel_skip_delete_backups_and_webps' ), 10, 2 );
46 add_filter( 'shortpixel_backup_folder', array( $this, 'getBackupFolderAny' ), 10, 3 );
47 add_filter( 'wp_stateless_add_media_args', array( $this, 'wp_stateless_add_media_args' ) );
48 add_filter( 'shortpixel_webp_image_base', array( $this, 'shortpixel_webp_image_base' ), 10, 2 );
49 // add_filter( 'shortpixel_skip_restore_image', array( $this, 'shortpixel_skip_restore_image' ), 10, 2 );
50
51 add_action( 'shortpixel_before_restore_image', array( $this, 'shortpixel_before_restore_image' ) );
52 add_action( 'shortpixel_after_restore_image', array( $this, 'handleRestoreBackup' ) );
53 add_action( 'admin_enqueue_scripts', array( $this, 'shortPixelJS' ) );
54 // Sync from sync tab
55 add_action( 'sm:synced::image', array( $this, 'sync_backup_file' ), 10, 2 );
56 add_action( 'sm:synced::image', array( $this, 'sync_webp_file' ), 10, 2 );
57 }
58
59 public function shortPixelJS() {
60 $upload_dir = wp_upload_dir();
61
62 wp_enqueue_script( 'stateless-short-pixel', ud_get_stateless_media()->path( 'lib/classes/compatibility/js/shortpixel.js', 'url' ), array( 'shortpixel' ), '', true );
63
64 $image_host = ud_get_stateless_media()->get_gs_host();
65 $bucketLink = apply_filters( 'wp_stateless_bucket_link', $image_host );
66
67 wp_localize_script( 'stateless-short-pixel', '_stateless_short_pixel', array( 'baseurl' => $upload_dir[ 'baseurl' ], 'bucketLink' => $bucketLink, ) );
68
69 }
70
71 /**
72 * Return Path of ShortPixel backup path.
73 * Bypass the checking whether a backup file exist when returning backup path.
74 * @todo check on GCS if backup really exist. Maybe we can implement transient caching for performance.
75 *
76 * @param $ret
77 * @param $file
78 * @param $thumbs
79 * @return string
80 */
81 public function getBackupFolderAny( $ret, $file, $thumbs ) {
82 if( $ret == false ) {
83 $fullSubDir = $this->returnSubDir( $file );
84 $ret = SHORTPIXEL_BACKUP_FOLDER . '/' . $fullSubDir;
85
86 }
87 return $ret;
88 }
89
90 /**
91 * Check whether image exist on GCS.
92 * Only when image is not available on server.
93 *
94 * @param $return
95 * @param $path
96 * @param null $id
97 * @return bool
98 */
99 public function shortpixel_image_exists( $return, $path, $id = null ) {
100 if( $return ) return $return;
101
102 $key = "stateless_url_to_postid_" . md5( $path );
103 $return = get_transient( $key );
104 // echo "\npath: $path \nKey: $key\nReturn: $return\nID: $id\n ";
105 if( !$return ) {
106 // Checking by matching file name in gs_name and $path.
107 if( !empty( $id ) ) {
108 $metadata = wp_get_attachment_metadata( $id );
109 $basename = basename( $path );
110 if( !empty( $metadata[ 'gs_name' ] ) ) {
111 $gs_basename = basename( $metadata[ 'gs_name' ] );
112 if( $gs_basename == $basename ) {
113 $return = true;
114 }
115
116 if( is_array( $metadata[ 'sizes' ] ) ) {
117 foreach( $metadata[ 'sizes' ] as $key => &$data ) {
118 if( empty( $data[ 'gs_name' ] ) ) continue;
119 $gs_basename = basename( $data[ 'gs_name' ] );
120 if( $gs_basename == $basename ) {
121 $return = true;
122 }
123 }
124 }
125 }
126 } // Directly check on GCS if image exist.
127 else if( empty( $id ) ) {
128 $wp_uploads_dir = wp_get_upload_dir();
129 $gs_name = str_replace( trailingslashit( $wp_uploads_dir[ 'basedir' ] ), '', $path );
130 $gs_name = str_replace( trailingslashit( $wp_uploads_dir[ 'baseurl' ] ), '', $gs_name );
131 $gs_name = str_replace( trailingslashit( ud_get_stateless_media()->get_gs_host() ), '', $gs_name );
132 $gs_name = apply_filters( 'wp_stateless_file_name', $gs_name );
133 if( $media = ud_get_stateless_media()->get_client()->media_exists( $gs_name ) ) {
134 $return = true;
135 }
136 }
137 set_transient( $key, $return, 10 * MINUTE_IN_SECONDS );
138 }
139 return $return;
140 }
141
142 /**
143 * Short-circuit filter.
144 * We need to remove backup image from GCS when shortpixel tries to delete from server.
145 * We are returning true to short-circuit in ephemeral mode, because file not exist in server.
146 *
147 * @param $return
148 * @param $paths
149 * @return bool
150 */
151 public function shortpixel_skip_delete_backups_and_webps( $return, $paths ) {
152 if( empty( $paths ) || !is_array( $paths ) ) return $return;
153
154 $sp__uploads = wp_upload_dir();
155 $fullSubDir = $this->returnSubDir( $paths[ 0 ] );
156 $backup_path = SHORTPIXEL_BACKUP_FOLDER . '/' . $fullSubDir;
157
158 foreach( $paths as $key => $path ) {
159 // Removing backup
160 $name = apply_filters( 'wp_stateless_file_name', SHORTPIXEL_BACKUP . '/' . $fullSubDir . basename( $path ) );
161 do_action( 'sm:sync::deleteFile', $name );
162
163 // Removing WebP
164 $backup_images = \WPShortPixelSettings::getOpt( 'wp-short-create-webp' );
165 if( $backup_images ) {
166 $name = str_replace( $sp__uploads[ 'basedir' ], '', $path );
167 $name = apply_filters( 'wp_stateless_file_name', $name . '.webp' );
168 do_action( 'sm:sync::deleteFile', $name );
169 }
170 }
171
172 if( ud_get_stateless_media()->get( 'sm.mode' ) == 'ephemeral' ) {
173 return true;
174 }
175 // When ephemeral mode isn't set backup files will be available in server. So no short-circuit.
176 return $return;
177 }
178
179 /**
180 * Skip the original backup process in ephemeral mode.
181 *
182 * @param $return
183 * @param $mainPath
184 * @param $PATHs
185 * @return bool
186 */
187 public function shortpixel_skip_backup( $return, $mainPath, $PATHs ) {
188 if( ud_get_stateless_media()->get( 'sm.mode' ) == 'ephemeral' ) {
189 return true;
190 }
191 return $return;
192 }
193
194 /**
195 * In ephemeral mode we need to take backup directly from original location.
196 * Because we will skip the backup process of shortpixel.
197 *
198 * @param $metadata
199 * @param $attachment_id
200 * @return mixed
201 */
202 public function wp_update_attachment_metadata( $metadata, $attachment_id ) {
203 if( ud_get_stateless_media()->get( 'sm.mode' ) == 'ephemeral' ) {
204 $backup_images = \WPShortPixelSettings::getOpt( 'wp-short-backup_images' );
205 if( $backup_images ) {
206 $this->sync_backup_file( $attachment_id, $metadata, false, array( 'before_optimization' => true ) );
207 }
208 }
209 return $metadata;
210 }
211
212 /**
213 * Sync image after optimization.
214 *
215 * @param $id
216 */
217 public function shortpixel_image_optimised( $id ) {
218 $metadata = wp_get_attachment_metadata( $id );
219 ud_get_stateless_media()->add_media( $metadata, $id, true );
220 // Sync the webp to GCS
221 $create_webp = \WPShortPixelSettings::getOpt( 'wp-short-create-webp' );
222 if( $create_webp ) {
223 $this->sync_webp_file($id, $metadata);
224 }
225 // Don't needed in ephemeral mode. In ephemeral mode the back will be sync once on wp_update_attachment_metadata filter.
226 if( ud_get_stateless_media()->get( 'sm.mode' ) !== 'ephemeral' ) {
227 $this->sync_backup_file( $id, $metadata, true );
228 }
229 }
230
231 /**
232 * Before shortpixel tries to restore from backup we need to make files available on server.
233 *
234 * @param $id
235 * @param null $metadata
236 */
237 public function shortpixel_before_restore_image( $id, $metadata = null ) {
238 $this->sync_backup_file( $id, $metadata, true, array( 'download' => true ) );
239 }
240
241 /**
242 * Disable default shortpixel restore and directly update image on GCS from backup copy in GCS.
243 *
244 * @param $return
245 * @param null $id
246 * @return bool
247 */
248 public function shortpixel_skip_restore_image( $return, $id = null ) {
249 if( ud_get_stateless_media()->get( 'sm.mode' ) === 'ephemeral' ) {
250 $this->client = ud_get_stateless_media()->get_client();
251 $this->client->copy_media( 'localhost/ShortpixelBackups/wp-content/uploads/2019/04/htpps.png', 'localhost/2019/04/htpps.png' );
252 return true;
253 }
254 return $return;
255 }
256
257 /**
258 * Sync backup image
259 *
260 * @param $id
261 * @param null $metadata
262 * @param bool $force
263 * @param array $args
264 * before_optimization : pass true if you want to sync directly from original path instead of backup path.
265 */
266 public function sync_backup_file( $id, $metadata = null, $force = false, $args = array() ) {
267 $args = wp_parse_args( $args, array( 'download' => false, // whether to only download.
268 'before_optimization' => false, // whether to delete local file in ephemeral mode.
269 ) );
270
271 /* Get metadata in case if method is called directly. */
272 if( empty( $metadata ) ) {
273 $metadata = wp_get_attachment_metadata( $id );
274 }
275 /* Now we go through all available image sizes and upload them to Google Storage */
276 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
277
278 // Sync backup file with GCS
279 $file_path = get_attached_file( $id );
280 $fullSubDir = $this->returnSubDir( $file_path );
281 $backup_path = SHORTPIXEL_BACKUP_FOLDER . '/' . $fullSubDir;
282 if( $args[ 'before_optimization' ] ) {
283 $upload_dir = wp_upload_dir();
284 $backup_path = $upload_dir[ 'basedir' ] . '/' . dirname( $metadata[ 'file' ] );
285 $args = array( 'ephemeral' => false );
286 }
287
288 $absolutePath = trailingslashit( $backup_path ) . basename( $metadata[ 'file' ] );
289 $name = apply_filters( 'wp_stateless_file_name', SHORTPIXEL_BACKUP . '/' . $fullSubDir . basename( $metadata[ 'file' ] ) );
290 do_action( 'sm:sync::syncFile', $name, $absolutePath, $force, $args );
291
292 foreach( (array) $metadata[ 'sizes' ] as $image_size => $data ) {
293 $absolutePath = trailingslashit( $backup_path ) . $data[ 'file' ];
294 $name = apply_filters( 'wp_stateless_file_name', SHORTPIXEL_BACKUP . '/' . $fullSubDir . $data[ 'file' ] );
295
296 do_action( 'sm:sync::syncFile', $name, $absolutePath, $force, $args );
297 }
298
299 }
300 }
301
302 /**
303 * Sync from sync tab
304 *
305 * @param $id
306 * @param null $metadata
307 */
308 public function sync_webp_file( $id, $metadata = null ) {
309 /* Get metadata in case if method is called directly. */
310 if( empty( $metadata ) ) {
311 $metadata = wp_get_attachment_metadata( $id );
312 }
313 add_filter( 'upload_mimes', array( $this, 'add_webp_mime' ), 10, 2 );
314 // Sync the webp to GCS
315 ud_get_stateless_media()->add_media( $metadata, $id, true, array( 'is_webp' => '.webp' ) );
316 remove_filter( 'upload_mimes', array( $this, 'add_webp_mime' ), 10 );
317 }
318
319 /**
320 * return subdir for that particular attached file - if it's media library then last 3 path items, otherwise substract the uploads path
321 * Has trailing directory separator (/)
322 *
323 * @copied from shortpixel-image-optimiser\class\db\shortpixel-meta-facade.php
324 * @param type $file
325 * @return string
326 */
327 public function returnSubDir( $file ) {
328 $hp = wp_normalize_path( get_home_path() );
329 $file = wp_normalize_path( $file );
330 $sp__uploads = wp_upload_dir();
331 if( strstr( $file, $hp ) ) {
332 $path = str_replace( $hp, "", $file );
333 } elseif( strstr( $file, dirname( WP_CONTENT_DIR ) ) ) { //in some situations the content dir is not inside the root, check this also (ex. single.shortpixel.com)
334 $path = str_replace( trailingslashit( dirname( WP_CONTENT_DIR ) ), "", $file );
335 } elseif( ( strstr( realpath( $file ), realpath( $hp ) ) ) ) {
336 $path = str_replace( realpath( $hp ), "", realpath( $file ) );
337 } elseif( strstr( $file, trailingslashit( dirname( dirname( $sp__uploads[ 'basedir' ] ) ) ) ) ) {
338 $path = str_replace( trailingslashit( dirname( dirname( $sp__uploads[ 'basedir' ] ) ) ), "", $file );
339 } else {
340 $path = ( substr( $file, 1 ) );
341 }
342 $pathArr = explode( '/', $path );
343 unset( $pathArr[ count( $pathArr ) - 1 ] );
344 return implode( '/', $pathArr ) . '/';
345 }
346
347 /**
348 * Sync images after shortpixel restore them from backup.
349 *
350 * @param $attachmentID
351 */
352 public function handleRestoreBackup( $attachmentID ) {
353 $metadata = wp_get_attachment_metadata( $attachmentID );
354 $this->add_media( $metadata, $attachmentID );
355 }
356
357 /**
358 * Customized version of wpCloud\StatelessMedia\Utility::add_media()
359 * to satisfied our need in restore backup
360 * If a image isn't restored from backup then ignore it.
361 *
362 * @param $metadata
363 * @param $attachment_id
364 */
365 public static function add_media( $metadata, $attachment_id ) {
366 $upload_dir = wp_upload_dir();
367
368 $client = ud_get_stateless_media()->get_client();
369
370 if( !is_wp_error( $client ) ) {
371
372 $fullsizepath = wp_normalize_path( get_attached_file( $attachment_id ) );
373 // Make non-images uploadable.
374 if( empty( $metadata[ 'file' ] ) && $attachment_id ) {
375 $metadata = array( "file" => str_replace( trailingslashit( $upload_dir[ 'basedir' ] ), '', get_attached_file( $attachment_id ) ) );
376 }
377
378 $file = wp_normalize_path( $metadata[ 'file' ] );
379 $image_host = ud_get_stateless_media()->get_gs_host();
380 $bucketLink = apply_filters( 'wp_stateless_bucket_link', $image_host );
381 $_cacheControl = \wpCloud\StatelessMedia\Utility::getCacheControl( $attachment_id, $metadata, null );
382 $_contentDisposition = \wpCloud\StatelessMedia\Utility::getContentDisposition( $attachment_id, $metadata, null );
383 $_metadata = array( "width" => isset( $metadata[ 'width' ] ) ? $metadata[ 'width' ] : null, "height" => isset( $metadata[ 'height' ] ) ? $metadata[ 'height' ] : null, 'object-id' => $attachment_id, 'source-id' => md5( $attachment_id . ud_get_stateless_media()->get( 'sm.bucket' ) ), 'file-hash' => md5( $metadata[ 'file' ] ) );
384
385 if( file_exists( $fullsizepath ) ) {
386 $file = apply_filters( 'wp_stateless_file_name', $file );
387
388 /* Add default image */
389 $media = $client->add_media( $_mediaOptions = array_filter( array( 'force' => true, 'name' => $file, 'absolutePath' => wp_normalize_path( get_attached_file( $attachment_id ) ), 'cacheControl' => $_cacheControl, 'contentDisposition' => $_contentDisposition, 'mimeType' => get_post_mime_type( $attachment_id ), 'metadata' => $_metadata ) ) );
390
391 // ephemeral mode: we don't need the local version.
392 if( ud_get_stateless_media()->get( 'sm.mode' ) === 'ephemeral' ) {
393 unlink( $fullsizepath );
394 }
395 }
396
397 /* Now we go through all available image sizes and upload them to Google Storage */
398 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
399
400 $path = wp_normalize_path( dirname( get_attached_file( $attachment_id ) ) );
401 $mediaPath = apply_filters( 'wp_stateless_file_name', trim( dirname( $metadata[ 'file' ] ), '\/\\' ) );
402
403 foreach( (array) $metadata[ 'sizes' ] as $image_size => $data ) {
404
405 $absolutePath = wp_normalize_path( $path . '/' . $data[ 'file' ] );
406
407 if( !file_exists( $absolutePath ) ) {
408 continue;
409 }
410
411 /* Add 'image size' image */
412 $media = $client->add_media( array( 'force' => true, 'name' => $file_path = trim( $mediaPath . '/' . $data[ 'file' ], '/' ), 'absolutePath' => $absolutePath, 'cacheControl' => $_cacheControl, 'contentDisposition' => $_contentDisposition, 'mimeType' => $data[ 'mime-type' ], 'metadata' => array_merge( $_metadata, array( 'width' => $data[ 'width' ], 'height' => $data[ 'height' ], 'child-of' => $attachment_id, 'file-hash' => md5( $data[ 'file' ] ) ) ) ) );
413
414 /* Break if we have errors. */
415 if( !is_wp_error( $media ) ) {
416 // ephemeral mode: we don't need the local version.
417 if( ud_get_stateless_media()->get( 'sm.mode' ) === 'ephemeral' ) {
418 unlink( $absolutePath );
419 }
420 }
421
422 }
423
424 }
425
426 }
427 }
428 // End add_media
429
430 /**
431 * modifying gs_name and absolutePath so that we can upload webp image using the same Utility::add_media function.
432 *
433 * @param $args
434 * @return mixed
435 */
436 public function wp_stateless_add_media_args( $args ) {
437 if( !empty( $args[ 'is_webp' ] ) && $args[ 'is_webp' ] ) {
438 if( \file_exists( $args[ 'absolutePath' ] . '.webp' ) ) {
439 $args[ 'name' ] = $args[ 'name' ] . '.webp';
440 $args[ 'absolutePath' ] = $args[ 'absolutePath' ] . '.webp';
441 } else {
442 $pathinfo = pathinfo( $args[ 'absolutePath' ] );
443 $absolutePath = trailingslashit( $pathinfo[ 'dirname' ] ) . $pathinfo[ 'filename' ] . '.webp';
444 if( file_exists( $absolutePath ) ) {
445 $args[ 'name' ] = $args[ 'name' ] . '.webp';
446 $args[ 'absolutePath' ] = $absolutePath;
447 }
448 }
449 $args[ 'mimeType' ] = 'image/webp';
450 }
451 return $args;
452 }
453
454 /**
455 * Bypass server url check and return base url for GCS image.
456 *
457 * @param $imageBase
458 * @param $src
459 * @return mixed
460 */
461 public function shortpixel_webp_image_base( $imageBase, $src ) {
462 $gs_link = \ud_get_stateless_media()->convert_to_gs_link( $src, true );
463 if( $gs_link ) {
464 $imageBase = trailingslashit( dirname( $gs_link ) );
465 }
466 return $imageBase;
467 }
468
469 /**
470 * @param $URLs
471 * @param $id
472 * @return mixed
473 */
474 public function shortpixel_image_urls( $URLs, $id ) {
475 foreach( $URLs as $key => $url ) {
476 $url_parts = wp_parse_url( $url );
477 if( $url_parts[ 'host' ] == 'storage.googleapis.com' ) {
478 if( preg_match( "@(^/?.*?/)(.*)@", $url_parts[ 'path' ], $matches ) ) {
479 $bucket = trim( $matches[ 1 ], '/' );
480 $url_parts[ 'path' ] = $matches[ 2 ];
481 $url_parts[ 'host' ] = $bucket . '.' . $url_parts[ 'host' ];
482 $URLs[ $key ] = Utility::join_url( $url_parts );
483 }
484 }
485 }
486 return $URLs;
487 }
488
489 }
490
491 }
492
493 }
494