PluginProbe
WP-Stateless – Google Cloud Storage / 2.3.2
WP-Stateless – Google Cloud Storage v2.3.2
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 / class-utility.php

class-utility.php in WP-Stateless – Google Cloud Storage 2.3.2, at lib/classes/class-utility.php

610 lines 23.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Helper Functions List
4 *
5 * Can be called via Singleton. Since Singleton uses magic method __call().
6 * Example:
7 *
8 * Add Media to GS storage:
9 * ud_get_stateless_media()->add_media( false, $post_id );
10 *
11 * @class Utility
12 */
13 namespace wpCloud\StatelessMedia {
14
15 if( !class_exists( 'wpCloud\StatelessMedia\Utility' ) ) {
16
17 class Utility {
18
19 static $can_delete_attachment = [];
20 static $synced_sizes = [];
21
22 /**
23 * ChromeLogger
24 *
25 * @author potanin@UD
26 * @param $data
27 */
28 static public function log( $data ) {
29
30 if( !class_exists( 'wpCloud\StatelessMedia\Logger' )) {
31 include_once( __DIR__ . '/class-logger.php' );
32 }
33
34 if( !class_exists( 'wpCloud\StatelessMedia\Logger' )) {
35 return;
36 }
37
38 if( defined( 'WP_STATELESS_CONSOLE_LOG' ) && WP_STATELESS_CONSOLE_LOG ) {
39 Logger::log( '[wp-stateless]', $data );
40 }
41
42 }
43
44 /**
45 * Override Cache Control
46 * @param $cacheControl
47 * @return mixed
48 */
49 public static function override_cache_control( $cacheControl ) {
50 return ud_get_stateless_media()->get( 'sm.cache_control' );
51 }
52
53 /**
54 * wp_normalize_path was added in 3.9.0
55 *
56 * @param $path
57 * @return mixed|string
58 *
59 */
60 public static function normalize_path( $path ) {
61
62 if( function_exists( 'wp_normalize_path' ) ) {
63 return wp_normalize_path( $path );
64 }
65
66 $path = str_replace( '\\', '/', $path );
67 $path = preg_replace( '|/+|','/', $path );
68 return $path;
69
70 }
71
72 /**
73 * Randomize file name
74 * @param $filename
75 * @return string
76 */
77 public static function randomize_filename( $filename ) {
78 $return = apply_filters('stateless_skip_cache_busting', null, $filename);
79 if($return){
80 return $return;
81 }
82
83 if(preg_match('/^[a-f0-9]{8}-/', $filename)){
84 return $filename;
85 }
86
87 $info = pathinfo($filename);
88 $ext = empty($info['extension']) ? '' : '' . $info['extension'];
89 $_parts = array();
90 $rand = substr(md5(time()), 0, 8);
91
92 if (strpos($info['filename'], '@')) {
93 $_cleanName = explode('@', $info['filename'])[0];
94 $_retna = explode('@', $info['filename'])[1];
95 $_parts[] = $rand;
96 $_parts[] = '-';
97 $_parts[] = strtolower($_cleanName);
98 $_parts[] = '@' . strtolower($_retna);
99 } else {
100 $_parts[] = $rand;
101 $_parts[] = '-';
102 $_parts[] = strtolower($info['filename']);
103 }
104
105 $filename = join('', $_parts);
106 if(!empty($ext)){
107 $filename .= '.' . $ext;
108 }
109
110 return $filename;
111 }
112
113 /**
114 * Get Media Item Content Disposition
115 *
116 * @param null $attachment_id
117 * @param array $metadata
118 * @param array $data
119 * @return string
120 */
121 public static function getContentDisposition( $attachment_id = null, $metadata = array(), $data = array() ) {
122 // return 'Content-Disposition: attachment; filename=some-file.sql';
123
124 return apply_filters( 'sm:item:contentDisposition', null, array( 'attachment_id' => $attachment_id, 'mime_type' => get_post_mime_type( $attachment_id ), 'metadata' => $metadata, 'data' => $data ) );
125
126 }
127
128 /**
129 * @param null $attachment_id
130 * @param array $metadata
131 * @param array $data
132 * @return string
133 */
134 public static function getCacheControl( $attachment_id = null, $metadata = array(), $data = array() ) {
135
136 if( !$attachment_id ) {
137 return apply_filters( 'sm:item:cacheControl', 'private, no-cache, no-store', $attachment_id, array( 'attachment_id' => null, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
138 }
139
140 $_mime_type = get_post_mime_type( $attachment_id );
141
142 // Treat images as public.
143 if( strpos( $_mime_type, 'image/' ) !== false ) {
144 return apply_filters( 'sm:item:cacheControl', 'public, max-age=36000, must-revalidate', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
145 }
146
147 // Treat images as public.
148 if( strpos( $_mime_type, 'sql' ) !== false ) {
149 return apply_filters( 'sm:item:cacheControl', 'private, no-cache, no-store', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
150 }
151
152 return apply_filters( 'sm:item:cacheControl', 'public, max-age=30, no-store, must-revalidate', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
153
154 }
155
156 /**
157 * Add/Update Media to Bucket
158 * Fired for every action with image add or update
159 *
160 * $force and $args params will no be passed on media library uploads.
161 * This two will be passed on by compatibility.
162 *
163 * @action wp_generate_attachment_metadata
164 * @author peshkov@UD
165 * @param $metadata
166 * @param $attachment_id
167 * @param $force Whether to force the upload incase of it's already exists.
168 * @param $args Whether to only sync the full size image.
169 * @return bool|string
170 */
171 public static function add_media( $metadata, $attachment_id, $force = false, $args = array() ) {
172 global $stateless_synced_full_size;
173 $file = '';
174 $upload_dir = wp_upload_dir();
175 $args = wp_parse_args($args, array(
176 'no_thumb' => false,
177 'is_webp' => '', // expected value ".webp";
178 ));
179
180 /* Get metadata in case if method is called directly. */
181 if( current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' && current_filter() !== 'intermediate_image_sizes_advanced' ) {
182 $metadata = wp_get_attachment_metadata( $attachment_id );
183 }
184
185 // making sure meta data isn't null.
186 if(empty($metadata)){
187 $metadata = array();
188 }
189
190 /**
191 * To skip the sync process.
192 *
193 * Returning a non-null value
194 * will effectively short-circuit the function.
195 *
196 * $force and $args params will no be passed on non media library uploads.
197 * This two will be passed on by compatibility.
198 *
199 * @since 2.2.4
200 *
201 * @param bool $value This should return true if want to skip the sync.
202 * @param int $metadata Metadata for the attachment.
203 * @param string $attachment_id Attachment ID.
204 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
205 * @param bool $args (optional) Whether to only sync the full size image.
206 */
207 $check = apply_filters('wp_stateless_skip_add_media', null, $metadata, $attachment_id, $force, $args);
208
209 $client = ud_get_stateless_media()->get_client();
210
211 if( !is_wp_error( $client ) && !$check ) {
212
213 $image_host = ud_get_stateless_media()->get_gs_host();
214 $bucketLink = apply_filters('wp_stateless_bucket_link', $image_host);
215 $fullsizepath = wp_normalize_path( get_attached_file( $attachment_id ) );
216 $_cacheControl = self::getCacheControl( $attachment_id, $metadata, null );
217 $_contentDisposition = self::getContentDisposition( $attachment_id, $metadata, null );
218
219 // Ensure image upload to GCS when attachment is updated,
220 // by checking if the attachment metadata is changed.
221 if($attachment_id && !empty($metadata) && !$force){
222 $db_metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
223 if($db_metadata != $metadata){
224 $force = true;
225 }
226 }
227
228 // Make non-images uploadable.
229 // empty $metadata['file'] can cause problem, so we need to generate it.
230 if( empty( $metadata['file'] ) && $attachment_id ) {
231 $mime_type = get_post_mime_type( $attachment_id );
232 $file = str_replace( wp_normalize_path(trailingslashit( $upload_dir[ 'basedir' ] )), '', $fullsizepath );
233
234 // We shouldn't create $metadata["file"] if it's PDF file.
235 if($mime_type != "application/pdf"){
236 $metadata["file"] = $file;
237 }
238 }
239
240 $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true );
241
242 $cloud_meta = wp_parse_args($cloud_meta, array(
243 'id' => '',
244 'name' => '',
245 'bucket' => ud_get_stateless_media()->get( 'sm.bucket' ),
246 'storageClass' => '',
247 'fileLink' => '',
248 'mediaLink' => '',
249 'selfLink' => '',
250 'cacheControl' => $_cacheControl,
251 'contentDisposition' => $_contentDisposition,
252 'object' => '',
253 'sizes' => array(),
254 ));
255
256 /**
257 * Storing file size to sm_cloud first,
258 * Because assigning directly to $metadata['filesize'] don't work.
259 * Maybe filesize gets removed in first run (when file exists).
260 */
261 if ( file_exists( $fullsizepath ) ) {
262 $cloud_meta['filesize'] = filesize( $fullsizepath );
263 }
264 // Getting file size from sm_cloud.
265 if(!empty($cloud_meta['filesize'])){
266 $metadata['filesize'] = $cloud_meta['filesize'];
267 }
268
269 /**
270 *
271 */
272 $image_sizes = self::get_path_and_url($metadata, $attachment_id);
273 foreach($image_sizes as $size => $img){
274 // also skips full size image if already uploaded using that feature.
275 // and delete it in stateless mode as it already bin uploaded through intermediate_image_sizes_advanced filter.
276 if( !$img['is_thumb'] && $stateless_synced_full_size == $attachment_id ){
277 if(ud_get_stateless_media()->get( 'sm.mode' ) === 'stateless' && $args['no_thumb'] != true && \file_exists($img['path'])){
278 unlink($img['path']);
279 }
280 continue;
281 }
282
283 // skips thumbs when it's called from Upload the full size image first, through intermediate_image_sizes_advanced filter.
284 if($args['no_thumb'] && $img['is_thumb'] || !empty(self::$synced_sizes[$attachment_id][$size])){
285 continue;
286 }
287
288 // GCS metadata
289 $_metadata = array(
290 "width" => $img[ 'width' ],
291 "height" => $img[ 'height' ],
292 'child-of' => $attachment_id,
293 'file-hash' => md5( $file ),
294 );
295
296 // adding extra GCS meta for full size image.
297 if(!$img['is_thumb']){
298 unset($_metadata['child-of']); // no need in full size image.
299 $_metadata['object-id'] = $attachment_id;
300 $_metadata['source-id'] = md5( $attachment_id.ud_get_stateless_media()->get( 'sm.bucket' ) );
301 }
302
303 /* Add default image */
304 $media = $client->add_media( array_filter( array(
305 'force' => $img['is_thumb'] ? $force : $force && $stateless_synced_full_size != $attachment_id,
306 'name' => $img['gs_name'],
307 'is_webp' => $args['is_webp'],
308 'mimeType' => $img['mime_type'],
309 'metadata' => $_metadata,
310 'absolutePath' => $img['path'],
311 'cacheControl' => $_cacheControl,
312 'contentDisposition' => $_contentDisposition,
313 ) ));
314
315 /* Break if we have errors. */
316 if( !is_wp_error( $media ) ) {
317 // @note We don't add storageClass because it's same as parent...
318 $cloud_meta = self::generate_cloud_meta($cloud_meta, $media, $size, $img, $bucketLink);
319
320 // Stateless mode: we don't need the local version.
321 // Except when uploading the full size image first.
322 if(self::can_delete_attachment($attachment_id, $args)){
323 unlink($img['path']);
324 }
325
326 // Setting
327 if(empty(self::$synced_sizes[$attachment_id][$size])){
328 self::$synced_sizes[$attachment_id][$size] = true;
329 }
330 }
331 }
332 // End of image sync loop
333
334 if(!$args['is_webp']){
335 update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
336 }
337 else{
338 // There is no use case for is_webp meta.
339 // $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true);
340 // $cloud_meta['is_webp'] = true;
341 // update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
342 }
343
344 if($args['no_thumb'] == true){
345 $stateless_synced_full_size = $attachment_id;
346 }
347
348 /**
349 * Triggers when the media and it's thumbs are synced.
350 *
351 * $force and $args params will no be passed on non media library uploads.
352 * This two will be passed on by compatibility.
353 *
354 * @since 2.2.5
355 *
356 * @param int $metadata Metadata for the attachment.
357 * @param string $attachment_id Attachment ID.
358 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
359 * @param bool $args (optional) Whether to only sync the full size image.
360 */
361 do_action( 'wp_stateless_media_synced', $metadata, $attachment_id, $force, $args);
362 }
363
364 return $metadata;
365 }
366
367 /**
368 * Remove Media from Bucket by post ID
369 * Fired on calling function wp_delete_attachment()
370 *
371 * @todo: add error logging. peshkov@UD
372 * @see wp_delete_attachment()
373 * @action delete_attachment
374 * @author peshkov@UD
375 * @param $post_id
376 */
377 public static function remove_media( $post_id ) {
378 /* Get attachments metadata */
379 $metadata = wp_get_attachment_metadata( $post_id );
380
381 /* Be sure we have the same bucket in settings and have GS object's name before proceed. */
382 if(
383 isset( $metadata[ 'gs_name' ] ) &&
384 isset( $metadata[ 'gs_bucket' ] ) &&
385 $metadata[ 'gs_bucket' ] == ud_get_stateless_media()->get( 'sm.bucket' )
386 ) {
387
388 $client = ud_get_stateless_media()->get_client();
389 if( !is_wp_error( $client ) ) {
390
391 /* Remove default image */
392 $client->remove_media( $metadata[ 'gs_name' ] );
393 // Remove webp
394 $client->remove_media( $metadata[ 'gs_name' ] . '.webp' );
395
396 /* Now, go through all sizes and remove 'image sizes' images from Bucket too. */
397 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
398 foreach( $metadata[ 'sizes' ] as $k => $v ) {
399 if( !empty( $v[ 'gs_name' ] ) ) {
400 $client->remove_media( $v[ 'gs_name' ] );
401 $client->remove_media( $v[ 'gs_name' ] . '.webp' );
402 }
403 }
404 }
405
406 }
407
408 }
409
410 }
411
412 /**
413 * Return URL and path for all image sizes of a attachment.
414 */
415 public static function get_path_and_url( $metadata, $attachment_id ){
416 /* Get metadata in case if method is called directly. */
417 if( empty($metadata) && current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' ) {
418 $metadata = wp_get_attachment_metadata( $attachment_id );
419 }
420
421 $gs_name_path = array();
422 $full_size_path = get_attached_file( $attachment_id );
423 $base_dir = dirname( $full_size_path );
424 $gs_name = apply_filters('wp_stateless_file_name', $full_size_path);
425
426 if( !isset($metadata['width']) && file_exists($full_size_path) ){
427 try{
428 $_image_size = getimagesize($full_size_path);
429 $metadata['width'] = $_image_size[0];
430 $metadata['height'] = $_image_size[1];
431 }
432 catch(Exception $e){
433 // lets do nothing.
434 }
435 }
436
437
438 $gs_name_path['__full'] = array(
439 'gs_name' => $gs_name,
440 'path' => $full_size_path,
441 'sm_meta' => true,
442 'is_thumb' => false,
443 'mime_type' => get_post_mime_type( $attachment_id ),
444 'width' => isset($metadata['width']) ? $metadata['width'] : null,
445 'height' => isset($metadata['height']) ? $metadata['height'] : null,
446 );
447
448
449 /* Now we go through all available image sizes and upload them to Google Storage */
450 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
451 foreach( $metadata[ 'sizes' ] as $image_size => $data ) {
452 if(empty($data[ 'file' ])) continue;
453 $absolutePath = wp_normalize_path( $base_dir . '/' . $data[ 'file' ] );
454 $gs_name = apply_filters('wp_stateless_file_name', $absolutePath);
455
456 $gs_name_path[$image_size] = array(
457 'gs_name' => $gs_name,
458 'path' => $absolutePath,
459 'sm_meta' => true,
460 'is_thumb' => true,
461 'mime_type' => $data['mime-type'],
462 'width' => $data['width'],
463 'height' => $data['height'],
464 );
465 }
466 }
467
468 return apply_filters( 'wp_stateless_get_path_and_url', $gs_name_path, $metadata, $attachment_id );
469 }
470
471 /**
472 * Return URL and path for all image sizes of a attachment.
473 */
474 public static function generate_cloud_meta( $cloud_meta, $media, $image_size, $img, $bucketLink ){
475 $gs_name = !empty($media['name']) ? $media['name'] : $img['gs_name'];
476 $fileLink = trailingslashit($bucketLink) . $gs_name;
477
478 if($img['is_thumb']){
479 // Cloud meta for thumbs.
480 $cloud_meta[ 'sizes' ][ $image_size ]['id'] = $media[ 'id' ];
481 $cloud_meta[ 'sizes' ][ $image_size ]['name'] = $gs_name;
482 $cloud_meta[ 'sizes' ][ $image_size ]['fileLink'] = $fileLink;
483 $cloud_meta[ 'sizes' ][ $image_size ]['mediaLink'] = $media[ 'mediaLink' ];
484 $cloud_meta[ 'sizes' ][ $image_size ]['selfLink'] = $media[ 'selfLink' ];
485 }
486 else{
487 // cloud meta for full size image.
488 $cloud_meta['id'] = $media[ 'id' ];
489 $cloud_meta['name'] = $gs_name;
490 $cloud_meta['fileLink'] = $fileLink;
491 $cloud_meta['storageClass'] = $media[ 'storageClass' ];
492 $cloud_meta['mediaLink'] = $media[ 'mediaLink' ];
493 $cloud_meta['selfLink'] = $media[ 'selfLink' ];
494 $cloud_meta['bucket'] = ud_get_stateless_media()->get( 'sm.bucket' );
495 $cloud_meta['object'] = $media;
496 }
497 return apply_filters( 'wp_stateless_generate_cloud_meta', $cloud_meta, $media, $image_size, $img, $bucketLink );
498 }
499
500 /**
501 * join_url
502 *
503 * @param array $parts
504 * @param boolean $encode
505 * @return string $url
506 */
507 public static function join_url( $parts, $encode=TRUE ){
508 if ( $encode ){
509 if ( isset( $parts['user'] ) )
510 $parts['user'] = rawurlencode( $parts['user'] );
511 if ( isset( $parts['pass'] ) )
512 $parts['pass'] = rawurlencode( $parts['pass'] );
513 if ( isset( $parts['host'] ) &&
514 !preg_match( '!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'] ) )
515 $parts['host'] = rawurlencode( $parts['host'] );
516 if ( !empty( $parts['path'] ) )
517 $parts['path'] = preg_replace( '!%2F!ui', '/',
518 rawurlencode( $parts['path'] ) );
519 if ( isset( $parts['query'] ) )
520 $parts['query'] = rawurlencode( $parts['query'] );
521 if ( isset( $parts['fragment'] ) )
522 $parts['fragment'] = rawurlencode( $parts['fragment'] );
523 }
524
525 $url = '';
526 if ( !empty( $parts['scheme'] ) )
527 $url .= $parts['scheme'] . ':';
528 if ( isset( $parts['host'] ) ){
529 $url .= '//';
530 if ( isset( $parts['user'] ) ){
531 $url .= $parts['user'];
532 if ( isset( $parts['pass'] ) )
533 $url .= ':' . $parts['pass'];
534 $url .= '@';
535 }
536 if ( preg_match( '!^[\da-f]*:[\da-f.:]+$!ui', $parts['host'] ) )
537 $url .= '[' . $parts['host'] . ']'; // IPv6
538 else
539 $url .= $parts['host']; // IPv4 or name
540 if ( isset( $parts['port'] ) )
541 $url .= ':' . $parts['port'];
542 if ( !empty( $parts['path'] ) && $parts['path'][0] != '/' )
543 $url .= '/';
544 }
545 if ( !empty( $parts['path'] ) )
546 $url .= $parts['path'];
547 if ( isset( $parts['query'] ) )
548 $url .= '?' . $parts['query'];
549 if ( isset( $parts['fragment'] ) )
550 $url .= '#' . $parts['fragment'];
551 return $url;
552 }
553
554 /**
555 * add_webp_mime
556 *
557 */
558 public function add_webp_mime($t, $user){
559 $t['webp'] = 'image/webp';
560 return $t;
561 }
562
563 /**
564 * Store attachment id in a static variable on 'intermediate_image_sizes_advanced' filter.
565 * To indicate that we can now delete attachment from server now.
566 *
567 * @param array $new_sizes
568 * @param array $image_meta
569 * @param int $attachment_id
570 * @return array $new_sizes
571 */
572 public static function store_can_delete_attachment( $new_sizes, $image_meta, $attachment_id ){
573 if( !in_array($attachment_id, self::$can_delete_attachment)){
574 self::$can_delete_attachment[] = $attachment_id;
575 }
576 return $new_sizes;
577 }
578
579 /**
580 * Check whether to delete attachment from server or not.
581 *
582 * @param int $attachment_id
583 * @return boolean
584 */
585 public static function can_delete_attachment($attachment_id, $args){
586 if(
587 ud_get_stateless_media()->get( 'sm.mode' ) === 'stateless' &&
588 $args['no_thumb'] != true
589 ){
590 // checks whether it's WP 5.3 and 'intermediate_image_sizes_advanced' is passed.
591 // To be sure that we don't delete full size image before thumbnails are generated.
592 if(
593 wp_attachment_is_image($attachment_id) &&
594 function_exists('is_wp_version_compatible') &&
595 is_wp_version_compatible('5.3-RC4-46673') &&
596 !in_array($attachment_id, self::$can_delete_attachment)
597 ){
598 return false;
599 }
600 return true;
601 }
602 return false;
603 }
604
605 }
606
607 }
608
609 }
610