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

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

592 lines 22.4 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 *
258 */
259 $image_sizes = self::get_path_and_url($metadata, $attachment_id);
260 foreach($image_sizes as $size => $img){
261 // also skips full size image if already uploaded using that feature.
262 // and delete it in stateless mode as it already bin uploaded through intermediate_image_sizes_advanced filter.
263 if( !$img['is_thumb'] && $stateless_synced_full_size == $attachment_id ){
264 if(ud_get_stateless_media()->get( 'sm.mode' ) === 'stateless' && $args['no_thumb'] != true && \file_exists($img['path'])){
265 unlink($img['path']);
266 }
267 continue;
268 }
269
270 // skips thumbs when it's called from Upload the full size image first, through intermediate_image_sizes_advanced filter.
271 if($args['no_thumb'] && $img['is_thumb'] || !empty(self::$synced_sizes[$attachment_id][$size])){
272 continue;
273 }
274
275 // GCS metadata
276 $_metadata = array(
277 "width" => $img[ 'width' ],
278 "height" => $img[ 'height' ],
279 'child-of' => $attachment_id,
280 'file-hash' => md5( $file ),
281 );
282
283 // adding extra GCS meta for full size image.
284 if(!$img['is_thumb']){
285 unset($_metadata['child-of']); // no need in full size image.
286 $_metadata['object-id'] = $attachment_id;
287 $_metadata['source-id'] = md5( $attachment_id.ud_get_stateless_media()->get( 'sm.bucket' ) );
288 }
289
290 /* Add default image */
291 $media = $client->add_media( array_filter( array(
292 'force' => $img['is_thumb'] ? $force : $force && $stateless_synced_full_size != $attachment_id,
293 'name' => $img['gs_name'],
294 'is_webp' => $args['is_webp'],
295 'mimeType' => $img['mime_type'],
296 'metadata' => $_metadata,
297 'absolutePath' => $img['path'],
298 'cacheControl' => $_cacheControl,
299 'contentDisposition' => $_contentDisposition,
300 ) ));
301
302 /* Break if we have errors. */
303 if( !is_wp_error( $media ) ) {
304 // @note We don't add storageClass because it's same as parent...
305 $cloud_meta = self::generate_cloud_meta($cloud_meta, $media, $size, $img, $bucketLink);
306
307 // Stateless mode: we don't need the local version.
308 // Except when uploading the full size image first.
309 if(self::can_delete_attachment($attachment_id, $args)){
310 unlink($img['path']);
311 }
312
313 // Setting
314 if(empty(self::$synced_sizes[$attachment_id][$size])){
315 self::$synced_sizes[$attachment_id][$size] = true;
316 }
317 }
318 }
319 // End of image sync loop
320
321 if(!$args['is_webp']){
322 update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
323 }
324 else{
325 // There is no use case for is_webp meta.
326 // $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true);
327 // $cloud_meta['is_webp'] = true;
328 // update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
329 }
330
331 if($args['no_thumb'] == true){
332 $stateless_synced_full_size = $attachment_id;
333 }
334
335 /**
336 * Triggers when the media and it's thumbs are synced.
337 *
338 * $force and $args params will no be passed on non media library uploads.
339 * This two will be passed on by compatibility.
340 *
341 * @since 2.2.5
342 *
343 * @param int $metadata Metadata for the attachment.
344 * @param string $attachment_id Attachment ID.
345 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
346 * @param bool $args (optional) Whether to only sync the full size image.
347 */
348 do_action( 'wp_stateless_media_synced', $metadata, $attachment_id, $force, $args);
349 }
350
351 return $metadata;
352 }
353
354 /**
355 * Remove Media from Bucket by post ID
356 * Fired on calling function wp_delete_attachment()
357 *
358 * @todo: add error logging. peshkov@UD
359 * @see wp_delete_attachment()
360 * @action delete_attachment
361 * @author peshkov@UD
362 * @param $post_id
363 */
364 public static function remove_media( $post_id ) {
365 /* Get attachments metadata */
366 $metadata = wp_get_attachment_metadata( $post_id );
367
368 /* Be sure we have the same bucket in settings and have GS object's name before proceed. */
369 if(
370 isset( $metadata[ 'gs_name' ] ) &&
371 isset( $metadata[ 'gs_bucket' ] ) &&
372 $metadata[ 'gs_bucket' ] == ud_get_stateless_media()->get( 'sm.bucket' )
373 ) {
374
375 $client = ud_get_stateless_media()->get_client();
376 if( !is_wp_error( $client ) ) {
377
378 /* Remove default image */
379 $client->remove_media( $metadata[ 'gs_name' ] );
380 // Remove webp
381 $client->remove_media( $metadata[ 'gs_name' ] . '.webp' );
382
383 /* Now, go through all sizes and remove 'image sizes' images from Bucket too. */
384 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
385 foreach( $metadata[ 'sizes' ] as $k => $v ) {
386 if( !empty( $v[ 'gs_name' ] ) ) {
387 $client->remove_media( $v[ 'gs_name' ] );
388 $client->remove_media( $v[ 'gs_name' ] . '.webp' );
389 }
390 }
391 }
392
393 }
394
395 }
396
397 }
398
399 /**
400 * Return URL and path for all image sizes of a attachment.
401 */
402 public static function get_path_and_url( $metadata, $attachment_id ){
403 /* Get metadata in case if method is called directly. */
404 if( empty($metadata) && current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' ) {
405 $metadata = wp_get_attachment_metadata( $attachment_id );
406 }
407
408 $gs_name_path = array();
409 $full_size_path = get_attached_file( $attachment_id );
410 $base_dir = dirname( $full_size_path );
411 $gs_name = apply_filters('wp_stateless_file_name', $full_size_path);
412
413 if( !isset($metadata['width']) && file_exists($full_size_path) ){
414 try{
415 $_image_size = getimagesize($full_size_path);
416 $metadata['width'] = $_image_size[0];
417 $metadata['height'] = $_image_size[1];
418 }
419 catch(Exception $e){
420 // lets do nothing.
421 }
422 }
423
424
425 $gs_name_path['__full'] = array(
426 'gs_name' => $gs_name,
427 'path' => $full_size_path,
428 'sm_meta' => true,
429 'is_thumb' => false,
430 'mime_type' => get_post_mime_type( $attachment_id ),
431 'width' => isset($metadata['width']) ? $metadata['width'] : null,
432 'height' => isset($metadata['height']) ? $metadata['height'] : null,
433 );
434
435
436 /* Now we go through all available image sizes and upload them to Google Storage */
437 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
438 foreach( $metadata[ 'sizes' ] as $image_size => $data ) {
439 if(empty($data[ 'file' ])) continue;
440 $absolutePath = wp_normalize_path( $base_dir . '/' . $data[ 'file' ] );
441 $gs_name = apply_filters('wp_stateless_file_name', $absolutePath);
442
443 $gs_name_path[$image_size] = array(
444 'gs_name' => $gs_name,
445 'path' => $absolutePath,
446 'sm_meta' => true,
447 'is_thumb' => true,
448 'mime_type' => $data['mime-type'],
449 'width' => $data['width'],
450 'height' => $data['height'],
451 );
452 }
453 }
454
455 return apply_filters( 'wp_stateless_get_path_and_url', $gs_name_path, $metadata, $attachment_id );
456 }
457
458 /**
459 * Return URL and path for all image sizes of a attachment.
460 */
461 public static function generate_cloud_meta( $cloud_meta, $media, $image_size, $img, $bucketLink ){
462 $gs_name = !empty($media['name']) ? $media['name'] : $img['gs_name'];
463 $fileLink = trailingslashit($bucketLink) . $gs_name;
464
465 if($img['is_thumb']){
466 // Cloud meta for thumbs.
467 $cloud_meta[ 'sizes' ][ $image_size ]['id'] = $media[ 'id' ];
468 $cloud_meta[ 'sizes' ][ $image_size ]['name'] = $gs_name;
469 $cloud_meta[ 'sizes' ][ $image_size ]['fileLink'] = $fileLink;
470 $cloud_meta[ 'sizes' ][ $image_size ]['mediaLink'] = $media[ 'mediaLink' ];
471 $cloud_meta[ 'sizes' ][ $image_size ]['selfLink'] = $media[ 'selfLink' ];
472 }
473 else{
474 // cloud meta for full size image.
475 $cloud_meta['id'] = $media[ 'id' ];
476 $cloud_meta['name'] = $gs_name;
477 $cloud_meta['fileLink'] = $fileLink;
478 $cloud_meta['storageClass'] = $media[ 'storageClass' ];
479 $cloud_meta['mediaLink'] = $media[ 'mediaLink' ];
480 $cloud_meta['selfLink'] = $media[ 'selfLink' ];
481 $cloud_meta['bucket'] = ud_get_stateless_media()->get( 'sm.bucket' );
482 $cloud_meta['object'] = $media;
483 }
484 return apply_filters( 'wp_stateless_generate_cloud_meta', $cloud_meta, $media, $image_size, $img, $bucketLink );
485 }
486
487 /**
488 * join_url
489 *
490 * @param array $parts
491 * @param boolean $encode
492 * @return string $url
493 */
494 public static function join_url( $parts, $encode=TRUE ){
495 if ( $encode ){
496 if ( isset( $parts['user'] ) )
497 $parts['user'] = rawurlencode( $parts['user'] );
498 if ( isset( $parts['pass'] ) )
499 $parts['pass'] = rawurlencode( $parts['pass'] );
500 if ( isset( $parts['host'] ) &&
501 !preg_match( '!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'] ) )
502 $parts['host'] = rawurlencode( $parts['host'] );
503 if ( !empty( $parts['path'] ) )
504 $parts['path'] = preg_replace( '!%2F!ui', '/',
505 rawurlencode( $parts['path'] ) );
506 if ( isset( $parts['query'] ) )
507 $parts['query'] = rawurlencode( $parts['query'] );
508 if ( isset( $parts['fragment'] ) )
509 $parts['fragment'] = rawurlencode( $parts['fragment'] );
510 }
511
512 $url = '';
513 if ( !empty( $parts['scheme'] ) )
514 $url .= $parts['scheme'] . ':';
515 if ( isset( $parts['host'] ) ){
516 $url .= '//';
517 if ( isset( $parts['user'] ) ){
518 $url .= $parts['user'];
519 if ( isset( $parts['pass'] ) )
520 $url .= ':' . $parts['pass'];
521 $url .= '@';
522 }
523 if ( preg_match( '!^[\da-f]*:[\da-f.:]+$!ui', $parts['host'] ) )
524 $url .= '[' . $parts['host'] . ']'; // IPv6
525 else
526 $url .= $parts['host']; // IPv4 or name
527 if ( isset( $parts['port'] ) )
528 $url .= ':' . $parts['port'];
529 if ( !empty( $parts['path'] ) && $parts['path'][0] != '/' )
530 $url .= '/';
531 }
532 if ( !empty( $parts['path'] ) )
533 $url .= $parts['path'];
534 if ( isset( $parts['query'] ) )
535 $url .= '?' . $parts['query'];
536 if ( isset( $parts['fragment'] ) )
537 $url .= '#' . $parts['fragment'];
538 return $url;
539 }
540
541 /**
542 * add_webp_mime
543 *
544 */
545 public function add_webp_mime($t, $user){
546 $t['webp'] = 'image/webp';
547 return $t;
548 }
549
550 /**
551 * Store attachment id in a static variable on 'intermediate_image_sizes_advanced' filter.
552 * To indicate that we can now delete attachment from server now.
553 *
554 * @param array $new_sizes
555 * @param array $image_meta
556 * @param int $attachment_id
557 * @return array $new_sizes
558 */
559 public static function store_can_delete_attachment( $new_sizes, $image_meta, $attachment_id ){
560 if( !in_array($attachment_id, self::$can_delete_attachment)){
561 self::$can_delete_attachment[] = $attachment_id;
562 }
563 return $new_sizes;
564 }
565
566 /**
567 * Check whether to delete attachment from server or not.
568 *
569 * @param int $attachment_id
570 * @return boolean
571 */
572 public static function can_delete_attachment($attachment_id, $args){
573 if(
574 ud_get_stateless_media()->get( 'sm.mode' ) === 'stateless' &&
575 $args['no_thumb'] != true
576 ){
577 // checks whether it's WP 5.3 and 'intermediate_image_sizes_advanced' is passed.
578 // To be sure that we don't delete full size image before thumbnails are generated.
579 if(is_wp_version_compatible('5.3-RC4-46673') && !in_array($attachment_id, self::$can_delete_attachment)){
580 return false;
581 }
582 return true;
583 }
584 return false;
585 }
586
587 }
588
589 }
590
591 }
592