PluginProbe
WP-Stateless – Google Cloud Storage / 3.0.4
WP-Stateless – Google Cloud Storage v3.0.4
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 3.0.4, at lib/classes/class-utility.php

1,113 lines 41.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
14 namespace wpCloud\StatelessMedia {
15
16 if( !class_exists( 'wpCloud\StatelessMedia\Utility' ) ) {
17
18 class Utility {
19
20 static $can_delete_attachment = [];
21 static $synced_sizes = [];
22
23 /**
24 * ChromeLogger
25 *
26 * @author potanin@UD
27 * @param $data
28 */
29 static public function log( $data ) {
30
31 if( !class_exists( 'wpCloud\StatelessMedia\Logger' ) ) {
32 include_once( __DIR__ . '/class-logger.php' );
33 }
34
35 if( !class_exists( 'wpCloud\StatelessMedia\Logger' ) ) {
36 return;
37 }
38
39 if( defined( 'WP_STATELESS_CONSOLE_LOG' ) && WP_STATELESS_CONSOLE_LOG ) {
40 Logger::log( '[wp-stateless]', $data );
41 }
42
43 }
44
45 /**
46 * Override Cache Control
47 * @param $cacheControl
48 * @return mixed
49 */
50 public static function override_cache_control( $cacheControl ) {
51 return ud_get_stateless_media()->get( 'sm.cache_control' );
52 }
53
54 /**
55 * wp_normalize_path was added in 3.9.0
56 *
57 * @param $path
58 * @return mixed|string
59 *
60 */
61 public static function normalize_path( $path ) {
62
63 if( function_exists( 'wp_normalize_path' ) ) {
64 return wp_normalize_path( $path );
65 }
66
67 $path = str_replace( '\\', '/', $path );
68 $path = preg_replace( '|/+|', '/', $path );
69 return $path;
70
71 }
72
73 /**
74 * Randomize file name
75 * @param $filename
76 * @return string
77 */
78 public static function randomize_filename( $filename ) {
79 $return = apply_filters( 'stateless_skip_cache_busting', null, $filename );
80 if( $return ) {
81 return $return;
82 }
83
84 if( preg_match( '/^[a-f0-9]{8}-/', $filename ) ) {
85 return $filename;
86 }
87
88 $info = pathinfo( $filename );
89 $ext = empty( $info[ 'extension' ] ) ? '' : '' . $info[ 'extension' ];
90 $_parts = array();
91 $rand = substr( md5( time() ), 0, 8 );
92
93 if( strpos( $info[ 'filename' ], '@' ) ) {
94 $_cleanName = explode( '@', $info[ 'filename' ] )[ 0 ];
95 $_retna = explode( '@', $info[ 'filename' ] )[ 1 ];
96 $_parts[] = $rand;
97 $_parts[] = '-';
98 $_parts[] = strtolower( $_cleanName );
99 $_parts[] = '@' . strtolower( $_retna );
100 } else {
101 $_parts[] = $rand;
102 $_parts[] = '-';
103 $_parts[] = strtolower( $info[ 'filename' ] );
104 }
105
106 $filename = join( '', $_parts );
107 if( !empty( $ext ) ) {
108 $filename .= '.' . $ext;
109 }
110
111 return $filename;
112 }
113
114 /**
115 * Get Media Item Content Disposition
116 *
117 * @param null $attachment_id
118 * @param array $metadata
119 * @param array $data
120 * @return string
121 */
122 public static function getContentDisposition( $attachment_id = null, $metadata = array(), $data = array() ) {
123 // return 'Content-Disposition: attachment; filename=some-file.sql';
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 * @param null $attachment_id
129 * @param array $metadata
130 * @param array $data
131 * @return string
132 */
133 public static function getCacheControl( $attachment_id = null, $metadata = array(), $data = array() ) {
134 if( !$attachment_id ) {
135 return apply_filters( 'sm:item:cacheControl', 'private, no-cache, no-store', $attachment_id, array( 'attachment_id' => null, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
136 }
137
138 $_mime_type = get_post_mime_type( $attachment_id );
139
140 // Treat images as public.
141 if( strpos( $_mime_type, 'image/' ) !== false ) {
142 return apply_filters( 'sm:item:cacheControl', 'public, max-age=36000, must-revalidate', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
143 }
144
145 // Treat images as public.
146 if( strpos( $_mime_type, 'sql' ) !== false ) {
147 return apply_filters( 'sm:item:cacheControl', 'private, no-cache, no-store', array( 'attachment_id' => $attachment_id, 'mime_type' => null, 'metadata' => $metadata, 'data' => $data ) );
148 }
149
150 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 ) );
151 }
152
153 /**
154 * Add/Update Media to Bucket
155 * Fired for every action with image add or update
156 *
157 * $force and $args params will no be passed on media library uploads.
158 * This two will be passed on by compatibility.
159 *
160 * @action wp_generate_attachment_metadata
161 * @author peshkov@UD
162 * @param $metadata
163 * @param $attachment_id
164 * @param boolean $force Whether to force the upload incase of it's already exists.
165 * @param array $args Whether to only sync the full size image.
166 * @return bool|string
167 */
168 public static function add_media( $metadata, $attachment_id, $force = false, $args = array() ) {
169 global $stateless_synced_full_size;
170 $sm_mode = ud_get_stateless_media()->get( 'sm.mode' );
171 $file = '';
172 $upload_dir = wp_upload_dir();
173 $args = wp_parse_args( $args, array( 'no_thumb' => false, 'is_webp' => '', // expected value ".webp";
174 ) );
175
176 /* Get metadata in case if method is called directly. */
177 if( current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' && current_filter() !== 'intermediate_image_sizes_advanced' ) {
178 $metadata = wp_get_attachment_metadata( $attachment_id );
179 }
180
181 // making sure meta data isn't null.
182 if( empty( $metadata ) ) {
183 $metadata = array();
184 }
185
186 /**
187 * To skip the sync process.
188 *
189 * Returning a non-null value
190 * will effectively short-circuit the function.
191 *
192 * $force and $args params will no be passed on non media library uploads.
193 * This two will be passed on by compatibility.
194 *
195 * @since 2.2.4
196 *
197 * @param bool $value This should return true if want to skip the sync.
198 * @param int $metadata Metadata for the attachment.
199 * @param string $attachment_id Attachment ID.
200 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
201 * @param bool $args (optional) Whether to only sync the full size image.
202 */
203 $check = apply_filters( 'wp_stateless_skip_add_media', null, $metadata, $attachment_id, $force, $args );
204
205 $client = ud_get_stateless_media()->get_client();
206
207 if( ( !is_wp_error( $client ) || ( $sm_mode == 'stateless' && !wp_doing_ajax() ) ) && !$check ) {
208
209 $image_host = ud_get_stateless_media()->get_gs_host();
210 $bucketLink = apply_filters('wp_stateless_bucket_link', $image_host);
211 $fullsizepath = wp_normalize_path( get_attached_file( $attachment_id ) );
212 $_cacheControl = self::getCacheControl( $attachment_id, $metadata, null );
213 $_contentDisposition = self::getContentDisposition( $attachment_id, $metadata, null );
214
215 // Ensure image upload to GCS when attachment is updated,
216 // by checking if the attachment metadata is changed.
217 if( $attachment_id && !empty( $metadata ) && !$force ) {
218 $db_metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
219 if( $db_metadata != $metadata ) {
220 $force = true;
221 }
222 }
223
224 /**
225 * To skip removing files from server
226 *
227 * Returning a non-null value
228 * will effectively short-circuit the function.
229 *
230 * $force and $args params will no be passed on non media library uploads.
231 * This two will be passed on by compatibility.
232 *
233 * @since 3.0
234 *
235 * @param bool $value This should return true if want to skip the sync.
236 * @param int $metadata Metadata for the attachment.
237 * @param string $attachment_id Attachment ID.
238 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
239 * @param bool $args (optional) Whether to only sync the full size image.
240 */
241 $skip_remove_media = apply_filters( 'wp_stateless_skip_remove_media', false, $metadata, $attachment_id, $force, $args );
242
243 // Make non-images uploadable.
244 // empty $metadata['file'] can cause problem, so we need to generate it.
245 if( empty( $metadata[ 'file' ] ) && $attachment_id ) {
246 $mime_type = get_post_mime_type( $attachment_id );
247 $file = str_replace( wp_normalize_path( trailingslashit( $upload_dir[ 'basedir' ] ) ), '', $fullsizepath );
248
249 // We shouldn't create $metadata["file"] if it's PDF file.
250 if( $mime_type != "application/pdf" ) {
251 $metadata[ "file" ] = $file;
252 }
253 }
254
255 $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true );
256
257 $cloud_meta = wp_parse_args($cloud_meta, array(
258 'name' => '',
259 'bucket' => ud_get_stateless_media()->get( 'sm.bucket' ),
260 'fileLink' => '',
261 'mediaLink' => '',
262 'cacheControl' => $_cacheControl,
263 'contentDisposition' => $_contentDisposition,
264 'sizes' => array(),
265 ));
266
267 /**
268 * Storing file size to sm_cloud first,
269 * Because assigning directly to $metadata['filesize'] don't work.
270 * Maybe filesize gets removed in first run (when file exists).
271 */
272 if( file_exists( $fullsizepath ) ) {
273 $cloud_meta[ 'filesize' ] = filesize( $fullsizepath );
274 }
275 // Getting file size from sm_cloud.
276 if( !empty( $cloud_meta[ 'filesize' ] ) ) {
277 $metadata[ 'filesize' ] = $cloud_meta[ 'filesize' ];
278 }
279
280 $image_sizes = self::get_path_and_url( $metadata, $attachment_id );
281 foreach( $image_sizes as $size => $img ) {
282 if ( (isset($_REQUEST['size']) && $_REQUEST['size'] == $size) || empty($_REQUEST['size']) ) {
283 // also skips full size image if already uploaded using that feature.
284 // and delete it in ephemeral modes as it already bin uploaded through intermediate_image_sizes_advanced filter.
285 if( !$img[ 'is_thumb' ] && $stateless_synced_full_size == $attachment_id ) {
286 if( $sm_mode === 'ephemeral' && $args[ 'no_thumb' ] != true && \file_exists( $img[ 'path' ] ) && !$skip_remove_media ) {
287 unlink( $img[ 'path' ] );
288 }
289 continue;
290 }
291
292 // skips thumbs when it's called from Upload the full size image first, through intermediate_image_sizes_advanced filter.
293 if( $args[ 'no_thumb' ] && $img[ 'is_thumb' ] || !empty( self::$synced_sizes[ $attachment_id ][ $size ] ) && $sm_mode !== 'stateless' && !$args[ 'is_webp' ] ) {
294 continue;
295 }
296
297 // GCS metadata
298 $_metadata = array(
299 "width" => $img[ 'width' ],
300 "height" => $img[ 'height' ],
301 'child-of' => $attachment_id,
302 'file-hash' => md5( $file ),
303 'size' => $size,
304 );
305
306 // adding extra GCS meta for full size image.
307 if( !$img[ 'is_thumb' ] ) {
308 unset( $_metadata[ 'child-of' ] ); // no need in full size image.
309 $_metadata[ 'object-id' ] = $attachment_id;
310 $_metadata[ 'source-id' ] = md5( $attachment_id . ud_get_stateless_media()->get( 'sm.bucket' ) );
311 }
312
313 $media_args = array_filter( array(
314 'force' => $img['is_thumb'] ? $force : $force && $stateless_synced_full_size != $attachment_id,
315 'name' => $img['gs_name'],
316 'is_webp' => $args['is_webp'],
317 'mimeType' => $img['mime_type'],
318 'metadata' => $_metadata,
319 'absolutePath' => $img['path'],
320 'cacheControl' => $_cacheControl,
321 'contentDisposition' => $_contentDisposition,
322 ) );
323
324 if ( $sm_mode == 'stateless' && !wp_doing_ajax() ) {
325 global $gs_client;
326
327 $media_args = wp_parse_args( $media_args, array(
328 'use_root' => true,
329 'force' => false,
330 'name' => false,
331 'absolutePath' => false,
332 'mimeType' => 'image/jpeg',
333 'metadata' => array(),
334 'is_webp' => '',
335 ) );
336 $media_args = apply_filters('wp_stateless_add_media_args', $media_args);
337
338 //Bucket
339 $bucket = ud_get_stateless_media()->get( 'sm.bucket' );
340
341 $bucket = $gs_client->bucket($bucket);
342 $object = $bucket->object($media_args['name']);
343
344 /**
345 * Updating object metadata, ACL, CacheControl and contentDisposition
346 * @return media object
347 */
348 try {
349 $media = $object->update( array( 'metadata' => $media_args['metadata']) +
350 array('cacheControl' => $_cacheControl,
351 'predefinedAcl' => 'publicRead',
352 'contentDisposition' => $_contentDisposition)
353 );
354
355 $cloud_meta = self::generate_cloud_meta($cloud_meta, $media, $size, $img, $bucketLink);
356 } catch (\Throwable $th) {
357 //throw $th;
358 }
359
360 $cloud_meta = self::generate_cloud_meta($cloud_meta, $media, $size, $img, $bucketLink);
361
362 } else {
363 /* Add default image */
364 $media = $client->add_media( $media_args);
365
366 /* Break if we have errors. */
367 if( !is_wp_error( $media ) ) {
368 // @note We don't add storageClass because it's same as parent...
369 $cloud_meta = self::generate_cloud_meta( $cloud_meta, $media, $size, $img, $bucketLink );
370
371 /**
372 * Ephemeral and stateless mode: we don't need the local version.
373 * Except when uploading the full size image first.
374 */
375 if( self::can_delete_attachment( $attachment_id, $args ) && !$skip_remove_media ) {
376 unlink( $img[ 'path' ] );
377 }
378 }
379 }
380 // Setting
381 if( empty( self::$synced_sizes[ $attachment_id ][ $size ] ) ) {
382 self::$synced_sizes[ $attachment_id ][ $size ] = true;
383 }
384 }
385 }
386 // End of image sync loop
387 if( !$args[ 'is_webp' ] ) {
388 update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
389 } else {
390 // There is no use case for is_webp meta.
391 // $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true);
392 // $cloud_meta['is_webp'] = true;
393 // update_post_meta( $attachment_id, 'sm_cloud', $cloud_meta );
394 }
395
396 if( $args[ 'no_thumb' ] == true ) {
397 $stateless_synced_full_size = $attachment_id;
398 }
399
400 /**
401 * Triggers when the media and it's thumbs are synced.
402 *
403 * $force and $args params will no be passed on non media library uploads.
404 * This two will be passed on by compatibility.
405 *
406 * @since 2.2.5
407 *
408 * @param int $metadata Metadata for the attachment.
409 * @param string $attachment_id Attachment ID.
410 * @param bool $force (optional) Whether to force the sync even the file already exist in GCS.
411 * @param bool $args (optional) Whether to only sync the full size image.
412 */
413 $metadata = apply_filters( 'wp_stateless_media_synced', $metadata, $attachment_id, $force, $args );
414 }
415
416 return $metadata;
417 }
418
419 /**
420 * Remove Media from Bucket by post ID
421 * Fired on calling function wp_delete_attachment()
422 *
423 * @todo: add error logging. peshkov@UD
424 * @see wp_delete_attachment()
425 * @action delete_attachment
426 * @author peshkov@UD
427 * @param $post_id
428 */
429 public static function remove_media( $post_id ) {
430 /* Get attachments metadata */
431 $metadata = wp_get_attachment_metadata( $post_id );
432
433 /* Be sure we have the same bucket in settings and have GS object's name before proceed. */
434 if( isset( $metadata[ 'gs_name' ] ) && isset( $metadata[ 'gs_bucket' ] ) && $metadata[ 'gs_bucket' ] == ud_get_stateless_media()->get( 'sm.bucket' ) ) {
435 $client = ud_get_stateless_media()->get_client();
436 if( !is_wp_error( $client ) ) {
437
438 /* Remove default image */
439 $client->remove_media( $metadata[ 'gs_name' ], $post_id );
440 // Remove webp
441 $client->remove_media( $metadata[ 'gs_name' ] . '.webp', $post_id, true, "", true );
442
443 /* Now, go through all sizes and remove 'image sizes' images from Bucket too. */
444 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
445 foreach( $metadata[ 'sizes' ] as $k => $v ) {
446 if( !empty( $v[ 'gs_name' ] ) ) {
447 $client->remove_media( $v[ 'gs_name' ], $post_id, true, $k );
448 $client->remove_media( $v[ 'gs_name' ] . '.webp', $post_id, true, $k, true );
449 }
450 }
451 }
452
453 }
454 }
455 }
456
457 /**
458 * Return URL and path for all image sizes of a attachment.
459 * @param $metadata
460 * @param $attachment_id
461 * @return mixed
462 */
463 public static function get_path_and_url( $metadata, $attachment_id ) {
464 /* Get metadata in case if method is called directly. */
465 if( empty( $metadata ) && current_filter() !== 'wp_generate_attachment_metadata' && current_filter() !== 'wp_update_attachment_metadata' ) {
466 $metadata = wp_get_attachment_metadata( $attachment_id );
467 }
468
469 $gs_name_path = array();
470 $full_size_path = get_attached_file( $attachment_id );
471 $base_dir = dirname( $full_size_path );
472
473 $use_wildcards = self::is_use_wildcards();
474 $gs_name = apply_filters( 'wp_stateless_file_name', $full_size_path, true, $attachment_id, '', $use_wildcards );
475 $gs_base_dir = dirname( $gs_name ) == '.' ? '' : trailingslashit(dirname( $gs_name ));
476
477 if( !isset( $metadata[ 'width' ] ) && file_exists( $full_size_path ) ) {
478 try {
479 $_image_size = getimagesize( $full_size_path );
480 $metadata[ 'width' ] = $_image_size[ 0 ];
481 $metadata[ 'height' ] = $_image_size[ 1 ];
482 } catch( \Exception $e ) {
483 // lets do nothing.
484 }
485 }
486
487 $gs_name_path['__full'] = array(
488 'gs_name' => $gs_name,
489 'path' => $full_size_path,
490 'sm_meta' => true,
491 'is_thumb' => false,
492 'mime_type' => get_post_mime_type( $attachment_id ),
493 'width' => isset($metadata['width']) ? $metadata['width'] : null,
494 'height' => isset($metadata['height']) ? $metadata['height'] : null,
495 );
496
497 /* Now we go through all available image sizes and upload them to Google Storage */
498 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
499 foreach( $metadata[ 'sizes' ] as $image_size => $data ) {
500 if( empty( $data[ 'file' ] ) ) continue;
501 $absolutePath = wp_normalize_path( $base_dir . '/' . $data[ 'file' ] );
502 $gs_name = $gs_base_dir . $data[ 'file' ];
503 $gs_name = apply_filters( 'wp_stateless_file_name', $gs_name, true, $attachment_id, $image_size, $use_wildcards );
504
505 $gs_name_path[$image_size] = array(
506 'gs_name' => $gs_name,
507 'path' => $absolutePath,
508 'sm_meta' => true,
509 'is_thumb' => true,
510 'mime_type' => $data['mime-type'],
511 'width' => $data['width'],
512 'height' => $data['height'],
513 );
514 }
515 }
516
517 return apply_filters( 'wp_stateless_get_path_and_url', $gs_name_path, $metadata, $attachment_id );
518 }
519
520 /**
521 * Return URL and path for all image sizes of a attachment.
522 * @param $cloud_meta
523 * @param $media
524 * @param $image_size
525 * @param $img
526 * @param $bucketLink
527 * @return mixed
528 */
529 public static function generate_cloud_meta( $cloud_meta, $media, $image_size, $img, $bucketLink ) {
530 $gs_name = !empty( $media[ 'name' ] ) ? $media[ 'name' ] : $img[ 'gs_name' ];
531 $fileLink = trailingslashit( $bucketLink ) . $gs_name;
532 $version = get_option( 'wp_sm_version', false );
533
534 if( $img[ 'is_thumb' ] ) {
535 // Cloud meta for thumbs.
536 $cloud_meta[ 'sizes' ][ $image_size ]['name'] = $gs_name;
537 $cloud_meta[ 'sizes' ][ $image_size ]['fileLink'] = $fileLink;
538 $cloud_meta[ 'sizes' ][ $image_size ]['mediaLink'] = $media[ 'mediaLink' ];
539 $cloud_meta[ 'sizes' ][ $image_size ]['width'] = ($media[ 'metadata' ][ 'width' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'width' ];
540 $cloud_meta[ 'sizes' ][ $image_size ]['height'] = ($media[ 'metadata' ][ 'height' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'height' ];
541 }
542 else{
543 // cloud meta for full size image.
544 $cloud_meta['name'] = $gs_name;
545 $cloud_meta['fileLink'] = $fileLink;
546 $cloud_meta['mediaLink'] = $media[ 'mediaLink' ];
547 $cloud_meta['width'] = ($media[ 'metadata' ][ 'width' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'width' ];
548 $cloud_meta['height'] = ($media[ 'metadata' ][ 'height' ]) ? $media[ 'metadata' ][ 'width' ] : $img[ 'height' ];
549 $cloud_meta['bucket'] = ud_get_stateless_media()->get( 'sm.bucket' );
550 $cloud_meta['sm_version'] = $version;
551 }
552 return apply_filters( 'wp_stateless_generate_cloud_meta', $cloud_meta, $media, $image_size, $img, $bucketLink );
553 }
554
555 /**
556 * join_url
557 *
558 * @param array $parts
559 * @param boolean $encode
560 * @return string $url
561 */
562 public static function join_url( $parts, $encode=TRUE ){
563 if ( $encode ){
564 if ( isset( $parts['user'] ) )
565 $parts['user'] = rawurlencode( $parts['user'] );
566 if ( isset( $parts['pass'] ) )
567 $parts['pass'] = rawurlencode( $parts['pass'] );
568 if ( isset( $parts['host'] ) &&
569 !preg_match( '!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'] ) )
570 $parts['host'] = rawurlencode( $parts['host'] );
571 if ( !empty( $parts['path'] ) )
572 $parts['path'] = preg_replace( '!%2F!ui', '/',
573 rawurlencode( $parts['path'] ) );
574 if ( isset( $parts['query'] ) )
575 $parts['query'] = rawurlencode( $parts['query'] );
576 if ( isset( $parts['fragment'] ) )
577 $parts['fragment'] = rawurlencode( $parts['fragment'] );
578 }
579
580 $url = '';
581 if ( !empty( $parts['scheme'] ) )
582 $url .= $parts['scheme'] . ':';
583 if ( isset( $parts['host'] ) ){
584 $url .= '//';
585 if ( isset( $parts['user'] ) ){
586 $url .= $parts['user'];
587 if ( isset( $parts['pass'] ) )
588 $url .= ':' . $parts['pass'];
589 $url .= '@';
590 }
591 if ( preg_match( '!^[\da-f]*:[\da-f.:]+$!ui', $parts['host'] ) )
592 $url .= '[' . $parts['host'] . ']'; // IPv6
593 else
594 $url .= $parts['host']; // IPv4 or name
595 if ( isset( $parts['port'] ) )
596 $url .= ':' . $parts['port'];
597 if ( !empty( $parts['path'] ) && $parts['path'][0] != '/' )
598 $url .= '/';
599 }
600 if ( !empty( $parts['path'] ) )
601 $url .= $parts['path'];
602 if ( isset( $parts['query'] ) )
603 $url .= '?' . $parts['query'];
604 if ( isset( $parts['fragment'] ) )
605 $url .= '#' . $parts['fragment'];
606 return $url;
607 }
608
609 /**
610 * add_webp_mime
611 * @param $t
612 * @param $user
613 * @return mixed
614 */
615 public function add_webp_mime( $t, $user ) {
616 $t[ 'webp' ] = 'image/webp';
617 return $t;
618 }
619
620 /**
621 * Store attachment id in a static variable on 'intermediate_image_sizes_advanced' filter.
622 * To indicate that we can now delete attachment from server now.
623 *
624 * @param array $new_sizes
625 * @param array $image_meta
626 * @param int $attachment_id
627 * @return array $new_sizes
628 */
629 public static function store_can_delete_attachment( $new_sizes, $image_meta, $attachment_id ) {
630 if( !in_array( $attachment_id, self::$can_delete_attachment ) ) {
631 self::$can_delete_attachment[] = $attachment_id;
632 }
633 return $new_sizes;
634 }
635
636 /**
637 * Check whether to delete attachment from server or not.
638 *
639 * @param int $attachment_id
640 * @param array $args
641 * @return boolean
642 */
643 public static function can_delete_attachment( $attachment_id, $args ) {
644 $sm_mode = ud_get_stateless_media()->get( 'sm.mode' );
645
646 if( in_array( $sm_mode, array( 'ephemeral', 'stateless' ) ) && $args[ 'no_thumb' ] != true ) {
647 // checks whether it's WP 5.3 and 'intermediate_image_sizes_advanced' is passed.
648 // To be sure that we don't delete full size image before thumbnails are generated.
649 if(
650 wp_attachment_is_image($attachment_id) &&
651 function_exists('is_wp_version_compatible') &&
652 is_wp_version_compatible('5.3-RC4-46673') &&
653 !in_array($attachment_id, self::$can_delete_attachment)
654 ){
655 return false;
656 }
657 return true;
658 }
659 return false;
660 }
661
662 /**
663 * Useful when there is a need to do things depending on a call stack.
664 * Returns true if any of the conditions met. Returns false otherwise.
665 *
666 * @param $callstack array Result of debug_backtrace function.
667 * @param $conditions array CallStack fingerprint with `stack_level` integer.
668 *
669 * Example:
670 * array(
671 * array(
672 * 'stack_level' => 4,
673 * 'function' => '__construct',
674 * 'class' => 'ET_Core_PageResource'
675 * ),
676 * array(
677 * 'stack_level' => 4,
678 * 'function' => 'get_cache_filename',
679 * 'class' => 'ET_Builder_Element'
680 * )
681 * )
682 *
683 * @return bool
684 */
685 public static function isCallStackMatches( $callstack, $conditions ) {
686 if( !is_array( $conditions ) ) {
687 $conditions = array( $conditions );
688 }
689
690 foreach( $conditions as $condition ) {
691 $condition[ 'stack_level' ] = $condition[ 'stack_level' ] ? $condition[ 'stack_level' ] : 0;
692
693 $levelData = $callstack[ $condition[ 'stack_level' ] ];
694
695 unset( $condition[ 'stack_level' ] );
696
697 $levelMatches = false;
698 foreach( $condition as $key => $value ) {
699 if( isset( $levelData[ $key ] ) && $levelData[ $key ] === $value ) {
700 $levelMatches = true;
701 } else {
702 $levelMatches = false;
703 }
704 }
705
706 if( $levelMatches ) return true;
707 }
708
709 return false;
710 }
711
712 /**
713 * Fail over to image URL if not found on disk
714 * In case image not available on both local and bucket
715 * try to pull image from image URL in case it is accessible by some sort of proxy.
716 *
717 * @param:
718 * $url (int/string): URL of the image.
719 * $save_to (string): Path where to save the image.
720 *
721 * @return bool|int
722 * @throws \Exception
723 */
724 public static function sync_get_attachment_if_exist( $url, $save_to ) {
725 if( is_int( $url ) ) $url = wp_get_attachment_url( $url );
726
727 $response = wp_remote_get( $url );
728 if( !is_wp_error( $response ) && is_array( $response ) ) {
729 if( !empty( $response[ 'response' ][ 'code' ] ) && $response[ 'response' ][ 'code' ] == 200 ) {
730 try {
731 if( wp_mkdir_p( dirname( $save_to ) ) ) {
732 return file_put_contents( $save_to, $response[ 'body' ] );
733 }
734 } catch( \Exception $e ) {
735 throw $e;
736 }
737 }
738 }
739 return false;
740 }
741
742 /**
743 * Store failed attachment
744 * @param $attachment_id
745 * @param $mode
746 */
747 public static function sync_store_failed_attachment( $attachment_id, $mode ) {
748 if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
749 $mode = 'images';
750 }
751
752 $fails = get_option( 'wp_stateless_failed_' . $mode );
753 if( !empty( $fails ) && is_array( $fails ) ) {
754 if( !in_array( $attachment_id, $fails ) ) {
755 $fails[] = $attachment_id;
756 }
757 } else {
758 $fails = array( $attachment_id );
759 }
760
761 update_option( 'wp_stateless_failed_' . $mode, $fails );
762 }
763
764 /**
765 * Checking maybe attachment have already fixed
766 * @param $mode
767 * @param $attachment_id
768 */
769 public static function sync_maybe_fix_failed_attachment( $mode, $attachment_id ) {
770 $fails = get_option( 'wp_stateless_failed_' . $mode );
771
772 if( !empty( $fails ) && is_array( $fails ) ) {
773 if( in_array( $attachment_id, $fails ) ) {
774 foreach( array_keys( $fails, $attachment_id ) as $key ) {
775 unset( $fails[ $key ] );
776 }
777 }
778 }
779
780 update_option( 'wp_stateless_failed_' . $mode, $fails );
781 }
782
783 /**
784 * Store current synchronization progress
785 * @param $mode
786 * @param $id
787 * @param $cli
788 */
789 public static function sync_store_current_progress( $mode, $id, $cli = false ) {
790 if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
791 $mode = 'images';
792 }
793
794 $first_processed = get_option( 'wp_stateless_' . $mode . '_first_processed' );
795 if( !$first_processed ) {
796 update_option( 'wp_stateless_' . $mode . '_first_processed', $id );
797 }
798 $last_processed = get_option( 'wp_stateless_' . $mode . '_last_processed' );
799 if( !$last_processed || $id < (int) $last_processed || $cli ) {
800 update_option( 'wp_stateless_' . $mode . '_last_processed', $id );
801 }
802 }
803
804 /**
805 * Get synchronization progress
806 * @param $mode
807 * @return array|bool
808 */
809 public static function sync_retrieve_current_progress( $mode ) {
810 if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
811 $mode = 'images';
812 }
813
814 $first_processed = get_option( 'wp_stateless_' . $mode . '_first_processed' );
815 $last_processed = get_option( 'wp_stateless_' . $mode . '_last_processed' );
816
817 if( !$first_processed || !$last_processed ) {
818 return false;
819 }
820
821 return array( (int) $first_processed, (int) $last_processed );
822 }
823
824 /**
825 * Reset synchronization progress
826 * @param $mode
827 */
828 public static function sync_reset_current_progress( $mode ) {
829 if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
830 $mode = 'images';
831 }
832
833 delete_option( 'wp_stateless_' . $mode . '_first_processed' );
834 delete_option( 'wp_stateless_' . $mode . '_last_processed' );
835 }
836
837 /**
838 * Get fails
839 *
840 * @param $mode
841 * @return mixed|void
842 */
843 public static function sync_get_fails( $mode ) {
844 if( !in_array( $mode, [ 'other', 'cli_images', 'cli_other' ] ) ) {
845 $mode = 'images';
846 }
847
848 return get_option( 'wp_stateless_failed_' . $mode );
849 }
850
851 /**
852 * Get_non_processed_media_ids
853 *
854 * @param $mode
855 * @param $files
856 * @param bool $continue
857 * @param $start_from
858 * @return array
859 * @throws \Exception
860 */
861 public static function sync_get_non_processed_media_ids( $mode, $files, $continue = false, $start_from = 0 ) {
862 if( ud_get_stateless_media()->is_connected_to_gs() !== true ) {
863 throw new \Exception( __( 'Not connected to GCS', ud_get_stateless_media()->domain ) );
864 }
865
866 if( $continue ) {
867 $progress = self::sync_retrieve_current_progress( $mode );
868
869 if( false !== $progress ) {
870 if( $start_from && $start_from != 0 ) {
871 // adding 1 because we subtracted 1 in js code for presentation.
872 $progress[ 1 ] = $start_from + 1;
873 }
874 $ids = array();
875 foreach( $files as $file ) {
876 $id = (int) $file->ID;
877 // only include IDs that have not been processed yet
878 if( $id > $progress[ 0 ] || $id < $progress[ 1 ] ) {
879 $ids[] = $id;
880 }
881 }
882 return $ids;
883 }
884 }
885
886 self::sync_reset_current_progress( $mode );
887
888 $ids = array();
889 foreach( $files as $file ) $ids[] = (int) $file->ID;
890
891 return $ids;
892 }
893
894 /**
895 * Generate JWT token signed by current site AUTH_SALT
896 * If no AUTH_SALT defined - admin email used
897 *
898 * @param $payload
899 * @param int $ttl
900 * @return string
901 */
902 public static function generate_jwt_token($payload, $ttl = 3600) {
903 $payload = wp_parse_args( $payload, [
904 'iat' => $now = time(),
905 'iss' => $site_url = get_site_url(),
906 'aud' => $site_url,
907 'exp' => $now + $ttl
908 ] );
909
910 $key = defined('AUTH_SALT') ? AUTH_SALT : get_option('admin_email');
911 return \Firebase\JWT\JWT::encode( $payload, $key );
912 }
913
914 /**
915 * Verify and decode token
916 * If no AUTH_SALT defined - admin email used
917 * Throws exceptions if cannot decode
918 *
919 * @param $token
920 * @return object
921 * @throws \Exception
922 */
923 public static function verify_jwt_token($token) {
924 $key = defined('AUTH_SALT') ? AUTH_SALT : get_option('admin_email');
925 return \Firebase\JWT\JWT::decode($token, $key, ['HS256']);
926 }
927
928 /**
929 * Generate auth token for wizard iframe
930 *
931 * @param int $ttl
932 * @return string
933 */
934 public static function generate_wizard_auth_token( $ttl = 3600 ) {
935 $payload = [
936 'is_network' => is_network_admin(),
937 'user_id' => get_current_user_id()
938 ];
939 return self::generate_jwt_token( $payload, $ttl );
940 }
941
942 /**
943 * Maps a file extensions to a mimetype.
944 *
945 * @param $extension string The file extension.
946 *
947 * @return string|null
948 * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
949 */
950 public static function mimetype_from_extension($extension){
951 $file_type = wp_check_filetype($extension);
952 if(!empty($file_type['type'])){
953 return $file_type['type'];
954 }
955 static $mimetypes = [
956 '7z' => 'application/x-7z-compressed',
957 'aac' => 'audio/x-aac',
958 'ai' => 'application/postscript',
959 'aif' => 'audio/x-aiff',
960 'asc' => 'text/plain',
961 'asf' => 'video/x-ms-asf',
962 'atom' => 'application/atom+xml',
963 'avi' => 'video/x-msvideo',
964 'bmp' => 'image/bmp',
965 'bz2' => 'application/x-bzip2',
966 'cer' => 'application/pkix-cert',
967 'crl' => 'application/pkix-crl',
968 'crt' => 'application/x-x509-ca-cert',
969 'css' => 'text/css',
970 'csv' => 'text/csv',
971 'cu' => 'application/cu-seeme',
972 'deb' => 'application/x-debian-package',
973 'doc' => 'application/msword',
974 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
975 'dvi' => 'application/x-dvi',
976 'eot' => 'application/vnd.ms-fontobject',
977 'eps' => 'application/postscript',
978 'epub' => 'application/epub+zip',
979 'etx' => 'text/x-setext',
980 'flac' => 'audio/flac',
981 'flv' => 'video/x-flv',
982 'gif' => 'image/gif',
983 'gz' => 'application/gzip',
984 'htm' => 'text/html',
985 'html' => 'text/html',
986 'ico' => 'image/x-icon',
987 'ics' => 'text/calendar',
988 'ini' => 'text/plain',
989 'iso' => 'application/x-iso9660-image',
990 'jar' => 'application/java-archive',
991 'jpe' => 'image/jpeg',
992 'jpeg' => 'image/jpeg',
993 'jpg' => 'image/jpeg',
994 'js' => 'text/javascript',
995 'json' => 'application/json',
996 'latex' => 'application/x-latex',
997 'log' => 'text/plain',
998 'm4a' => 'audio/mp4',
999 'm4v' => 'video/mp4',
1000 'mid' => 'audio/midi',
1001 'midi' => 'audio/midi',
1002 'mov' => 'video/quicktime',
1003 'mp3' => 'audio/mpeg',
1004 'mp4' => 'video/mp4',
1005 'mp4a' => 'audio/mp4',
1006 'mp4v' => 'video/mp4',
1007 'mpe' => 'video/mpeg',
1008 'mpeg' => 'video/mpeg',
1009 'mpg' => 'video/mpeg',
1010 'mpg4' => 'video/mp4',
1011 'oga' => 'audio/ogg',
1012 'ogg' => 'audio/ogg',
1013 'ogv' => 'video/ogg',
1014 'ogx' => 'application/ogg',
1015 'pbm' => 'image/x-portable-bitmap',
1016 'pdf' => 'application/pdf',
1017 'pgm' => 'image/x-portable-graymap',
1018 'png' => 'image/png',
1019 'pnm' => 'image/x-portable-anymap',
1020 'ppm' => 'image/x-portable-pixmap',
1021 'ppt' => 'application/vnd.ms-powerpoint',
1022 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
1023 'ps' => 'application/postscript',
1024 'qt' => 'video/quicktime',
1025 'rar' => 'application/x-rar-compressed',
1026 'ras' => 'image/x-cmu-raster',
1027 'rss' => 'application/rss+xml',
1028 'rtf' => 'application/rtf',
1029 'sgm' => 'text/sgml',
1030 'sgml' => 'text/sgml',
1031 'svg' => 'image/svg+xml',
1032 'swf' => 'application/x-shockwave-flash',
1033 'tar' => 'application/x-tar',
1034 'tif' => 'image/tiff',
1035 'tiff' => 'image/tiff',
1036 'torrent' => 'application/x-bittorrent',
1037 'ttf' => 'application/x-font-ttf',
1038 'txt' => 'text/plain',
1039 'wav' => 'audio/x-wav',
1040 'webm' => 'video/webm',
1041 'webp' => 'image/webp',
1042 'wma' => 'audio/x-ms-wma',
1043 'wmv' => 'video/x-ms-wmv',
1044 'woff' => 'application/x-font-woff',
1045 'wsdl' => 'application/wsdl+xml',
1046 'xbm' => 'image/x-xbitmap',
1047 'xls' => 'application/vnd.ms-excel',
1048 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1049 'xml' => 'application/xml',
1050 'xpm' => 'image/x-xpixmap',
1051 'xwd' => 'image/x-xwindowdump',
1052 'yaml' => 'text/yaml',
1053 'yml' => 'text/yaml',
1054 'zip' => 'application/zip',
1055 ];
1056
1057 $extension = strtolower( $extension );
1058
1059 return isset( $mimetypes[ $extension ] ) ? $mimetypes[ $extension ] : false;
1060 }
1061
1062 /**
1063 * Check using wildcards
1064 * @return bool
1065 */
1066 public static function is_use_wildcards() {
1067 return isset( $_REQUEST[ 'use_wildcards' ] ) ? $_REQUEST[ 'use_wildcards' ] : false;
1068 }
1069
1070 /**
1071 * @param $size
1072 * @return float|int
1073 */
1074 public static function convert_to_byte($size) {
1075 $lastCharacter = \substr($size, -1);
1076 $base = \strtoupper($lastCharacter);
1077 if (!\ctype_digit($lastCharacter)) {
1078 switch ($base) {
1079 case 'B':
1080 $size = (int) $size;
1081 break;
1082 case 'K':
1083 $size = (int) $size * 1024;
1084 break;
1085 case 'M':
1086 $size = (int) $size * pow(1024, 2);
1087 break;
1088 case 'G':
1089 $size = (int) $size * pow(1024, 3);
1090 break;
1091 }
1092 }
1093 return $size;
1094 }
1095
1096 /**
1097 * Get stateless data, count of stateless media
1098 * @return mixed
1099 */
1100 public static function get_stateless_media_data_count() {
1101 global $wpdb;
1102
1103 $stateless_media = $wpdb->get_var($wpdb->prepare("
1104 SELECT COUNT(meta_id)
1105 FROM ".$wpdb->postmeta."
1106 WHERE meta_key = %s
1107 ", 'sm_cloud'));
1108
1109 return $stateless_media;
1110 }
1111 }
1112 }
1113 }