PluginProbe
WP-Stateless – Google Cloud Storage / 3.0.2
WP-Stateless – Google Cloud Storage v3.0.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-bootstrap.php

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

1,910 lines 69.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bootstrap
4 *
5 * @since 0.2.0
6 */
7 namespace wpCloud\StatelessMedia {
8
9 use Google\Cloud\Storage\StorageClient;
10 use Google\Auth\HttpHandler\HttpHandlerFactory;
11
12 if( !class_exists( 'wpCloud\StatelessMedia\Bootstrap' ) ) {
13
14 final class Bootstrap extends \UsabilityDynamics\WP\Bootstrap_Plugin {
15
16 /**
17 * Google Storage Client
18 * Use $this->get_client()
19 *
20 * @var \wpCloud\StatelessMedia\GS_CLient
21 */
22 private $client;
23
24 /**
25 * Plugin core version.
26 *
27 * @static
28 * @property $version
29 * @type {Object}
30 */
31 public static $version = '3.0';
32
33 /**
34 * Singleton Instance Reference.
35 *
36 * @protected
37 * @static
38 * @property $instance
39 * @type \wpCloud\StatelessMedia\Bootstrap object
40 */
41 protected static $instance = null;
42
43 /**
44 * Constructor
45 * Attention: MUST NOT BE CALLED DIRECTLY! USE get_instance() INSTEAD!
46 * @param $args
47 * @author peshkov@UD
48 */
49 protected function __construct( $args ) {
50 parent::__construct( $args );
51
52 /**
53 * Add custom args to api ping request
54 */
55 add_filter('ud-api-client-ping-args', function($args, $_, $__) {
56 $args['multisite'] = is_multisite();
57 $args['stateless_media'] = Utility::get_stateless_media_data_count();
58 return $args;
59 }, 10, 3);
60
61 //** Define our Admin Notices handler object */
62 $this->errors = new Errors( array_merge( $args, array(
63 'type' => $this->type
64 ) ) );
65
66 // Initialize compatibility modules.
67 add_action( 'plugins_loaded', function(){
68 new Module();
69 });
70
71 /**
72 * Define settings and UI.
73 *
74 * Example:
75 *
76 * Get option
77 * $this->get( 'sm.client_id' )
78 *
79 * Manually Update/Add option
80 * $this->set( 'sm.client_id', 'zxcvv12adffse' );
81 */
82 $this->settings = new Settings($this);
83 }
84
85 /**
86 * Prevent loading of textdomain
87 */
88 public function load_textdomain(){}
89
90 /**
91 * Instantiate class.
92 */
93 public function init() {
94 // Parse feature falgs, set constants.
95 $this->parse_feature_flags();
96 $sm_mode = $this->get( 'sm.mode' );
97
98 new SyncNonMedia();
99
100 // Invoke REST API
101 add_action( 'rest_api_init', array( $this, 'api_init' ) );
102
103 // Register meta boxes and fields for media edit page
104 add_filter( 'rwmb_meta_boxes', array( $this, 'attachment_meta_box_callback' ) );
105
106 // Register meta boxes and fields for media modal page
107 add_filter( 'attachment_fields_to_edit', array( $this, 'attachment_modal_meta_box_callback' ), 11, 2 );
108
109 /**
110 * Init hook
111 */
112 add_action( 'admin_init', array( $this, 'admin_init' ) );
113
114 /**
115 * Handle switch blog properly.
116 */
117 add_action( 'switch_blog', array( $this, 'on_switch_blog' ), 10, 2 );
118
119 /**
120 * Filter for getting stateless settings
121 */
122 add_filter( 'stateless::get_settings', array( $this, 'get_settings' ), 10);
123
124 /**
125 * Init AJAX jobs
126 */
127 new Ajax();
128
129 /**
130 * Load WP-CLI Commands
131 */
132 if( defined( 'WP_CLI' ) && WP_CLI ) {
133 include_once($this->path('lib/cli/class-sm-cli-command.php', 'dir'));
134 }
135
136 $this->is_network_detected();
137
138 /**
139 * Add scripts
140 */
141 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
142
143 /**
144 * Delete table when blog is deleted.
145 */
146 add_action( 'wp_delete_site', array($this, 'wp_delete_site'));
147
148 /**
149 * To prevent fatal errors for users who use PHP 5.5 or less.
150 */
151 if( version_compare(PHP_VERSION, '5.5', '<') ) {
152 $this->errors->add( sprintf( __( 'The plugin requires PHP %s or higher. You current PHP version %s is too old.', ud_get_stateless_media()->domain ), '<b>5.5</b>', '<b>' . PHP_VERSION . '</b>' ) );
153 }
154
155 /* Initialize plugin only if Mode is not 'disabled'. */
156 if ( ($sm_mode !== 'disabled' && $sm_mode !== 'stateless') || ( $sm_mode === 'stateless' && wp_doing_ajax() )) {
157
158 /**
159 * Determine if we have issues with connection to Google Storage Bucket
160 * if SM is not disabled.
161 */
162 $is_connected = $this->is_connected_to_gs();
163
164 if ( is_wp_error( $is_connected ) ) {
165 $this->errors->add( $is_connected->get_error_message(), 'warning' );
166 }
167
168 if ( $googleSDKVersionConflictError = get_transient( "wp_stateless_google_sdk_conflict" ) ) {
169 $this->errors->add( $googleSDKVersionConflictError, 'warning' );
170 }
171
172 /**
173 * Carry on only if we do not have errors.
174 */
175 if( !$this->has_errors() ) {
176
177 if( in_array( $sm_mode , array( 'cdn', 'ephemeral' ) ) ) {
178 /**
179 * init main filters
180 */
181 $this->_init_filters( 'main' );
182 }
183
184 if( $sm_mode === 'ephemeral' || $sm_mode === 'stateless' ){
185 // Store attachment id in a static variable on 'intermediate_image_sizes_advanced' filter.
186 // Utility::store_can_delete_attachment();
187 if(function_exists('is_wp_version_compatible') && is_wp_version_compatible('5.3-RC4-46673')){
188 add_filter( 'intermediate_image_sizes_advanced', array('wpCloud\StatelessMedia\Utility', 'store_can_delete_attachment'), 10, 3 );
189 }
190 }
191
192 if ( $this->get( 'sm.delete_remote' ) == 'true' ) {
193 /**
194 * On physical file deletion we remove any from GS
195 * We need priority grater than default (10) for ShortPixel plugin to work properly.
196 */
197 add_filter('delete_attachment', array($this, 'remove_media'), 11);
198 }
199
200 /**
201 * init client's filters
202 */
203 $this->_init_filters( 'client' );
204 }
205
206 } elseif ( $sm_mode == 'stateless' ) {
207 /**
208 * Replacing local path to gs:// for using it on StreamWrapper
209 */
210 add_filter('upload_dir', array( $this, 'filter_upload_dir' ), 99);
211
212 /**
213 * Stateless mode working only with GD library
214 */
215 add_filter( 'wp_image_editors', array($this, 'select_wp_image_editors') );
216
217 //init GS client
218 global $gs_client;
219 if ( $gs_client = $this->init_gs_client() ) {
220 StreamWrapper::register($gs_client);
221 }
222
223 /**
224 * init client's filters
225 */
226 $this->_init_filters( 'client' );
227
228 /**
229 * init main filters
230 */
231 $this->_init_filters( 'main' );
232 }
233 }
234
235 /**
236 * Init additional filters which uses on all modes
237 * @param string $type
238 */
239 private function _init_filters ( $type = '' ) {
240 switch( $type) {
241 case 'main':
242 add_filter( 'wp_get_attachment_image_attributes', array( $this, 'wp_get_attachment_image_attributes' ), 20, 3 );
243 add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 20, 2 );
244 add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 9, 2 );
245 add_filter( 'attachment_url_to_postid', array( $this, 'attachment_url_to_postid' ), 20, 2 );
246
247 if( $this->get( 'sm.body_rewrite' ) == 'true' || $this->get( 'sm.body_rewrite' ) == 'enable_editor' ) {
248 add_filter( 'the_content', array( $this, 'the_content_filter' ), 99 );
249 }
250
251 if( $this->get( 'sm.body_rewrite' ) == 'true' || $this->get( 'sm.body_rewrite' ) == 'enable_meta' ) {
252 add_filter( 'get_post_metadata', array( $this, 'post_metadata_filter' ), 2, 4 );
253 }
254
255 add_filter( 'wp_stateless_bucket_link', array( $this, 'wp_stateless_bucket_link' ) );
256 break;
257 case 'client':
258 /**
259 * Add custom actions to media rows
260 */
261 add_filter( 'media_row_actions', array( $this, 'add_custom_row_actions' ), 10, 3 );
262
263 /**
264 * Hashify file name if option is enabled
265 */
266 if ( $this->get( 'sm.hashify_file_name' ) == 'true' ) {
267 add_filter('sanitize_file_name', array( 'wpCloud\StatelessMedia\Utility', 'randomize_filename' ), 10);
268 }
269
270 /**
271 * Override Cache Control is option is enabled
272 */
273 $cacheControl = trim($this->get( 'sm.cache_control' ));
274 if ( !empty($cacheControl) ) {
275 add_filter( 'sm:item:cacheControl', array( $this, 'override_cache_control' ) );
276 }
277
278 add_filter( 'wp_stateless_file_name', array( $this, 'handle_root_dir' ), 10, 5 );
279
280 /**
281 * Extends metadata by adding GS information.
282 */
283 add_filter( 'wp_get_attachment_metadata', array( $this, 'wp_get_attachment_metadata' ), 10, 2 );
284
285 /**
286 * Add/Edit Media
287 *
288 * Once added or edited we can get into Attachment ID then get all image sizes and sync them with GS
289 * We can't use this. That's prevent removing this filter.
290 */
291 add_filter( 'wp_update_attachment_metadata', array( 'wpCloud\StatelessMedia\Utility', 'add_media' ), 999, 2 );
292
293 /**
294 * Rewrite Image URLS
295 */
296 add_filter( 'image_downsize', array( $this, 'image_downsize' ), 99, 3 );
297 add_filter( 'wp_calculate_image_srcset', array($this, 'wp_calculate_image_srcset'), 10, 5 );
298
299 /**
300 * Trigger module initialization and registration.
301 */
302 do_action('sm::module::init', $this->get( 'sm' ));
303
304 break;
305 case 'default':
306 break;
307 }
308 }
309
310 /**
311 * The default $editors value:
312 *
313 * array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' )
314 * @param $editors
315 * @return array
316 */
317 public function select_wp_image_editors( $editors ) {
318 return array( 'WP_Image_Editor_GD' );
319 }
320
321 /**
322 * @param callable|null $httpHandler
323 * @return StorageClient
324 * @throws \Exception
325 */
326 public function init_gs_client( callable $httpHandler = null ) {
327 // May be Loading Google SDK....
328 if( !class_exists( 'HttpHandlerFactory' ) ) {
329 include_once( ud_get_stateless_media()->path('lib/Google/vendor/autoload.php', 'dir') );
330 }
331
332 $httpHandler = $httpHandler ? $httpHandler : HttpHandlerFactory::build();
333
334 $json_key = json_decode($this->settings->get('sm.key_json'), true);
335
336 if ( !empty( $json_key ) ) {
337 return new StorageClient(
338 [ 'keyFile' => $json_key,
339 'httpHandler' => function ( $request, $options ) use ( $httpHandler ) {
340 $xGoogApiClientHeader = $request->getHeaderLine( 'x-goog-api-client' );
341 $request = $request->withHeader( 'x-goog-api-client', $xGoogApiClientHeader );
342
343 return call_user_func_array( $httpHandler, [ $request, $options ] );
344 },
345 'authHttpHandler' => HttpHandlerFactory::build(), ] );
346 }
347 }
348
349 /**
350 * Replacing root dir with GCS path
351 * @param $uploads
352 * @return array
353 */
354 public function filter_upload_dir( $uploads ) {
355 global $default_dir;
356 if ($default_dir) return $uploads;
357 //Bucket
358 $bucket = $this->get( 'sm.bucket' );
359
360 //Bucket folder path
361 $root_dir = $this->get( 'sm.root_dir' );
362 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
363
364 /**
365 * Subdir not uses on Stateless mode
366 */
367 $uploads['subdir'] = '';
368
369 $basedir = rtrim(sprintf('gs://%s/%s', $bucket, $root_dir), '/');
370 $baseurl = rtrim(sprintf('https://storage.googleapis.com/%s/%s', $bucket, $root_dir ), '/');
371
372 $uploads = array(
373 'url' => rtrim($baseurl . $uploads['subdir'], '/'),
374 'path' => $basedir . $uploads['subdir'],
375 'subdir' => $uploads['subdir'],
376 'basedir' => $basedir,
377 'baseurl' => $baseurl,
378 'error' => false,
379 );
380 return $uploads;
381 }
382
383 /**
384 * Rebuild srcset from gs_link.
385 * Using calculations returned from WordPress wp_calculate_image_srcset()
386 *
387 * @param $sources
388 * @param $size_array
389 * @param $image_src
390 * @param $image_meta
391 * @param $attachment_id
392 * @return array
393 */
394 public function wp_calculate_image_srcset($sources, $size_array, $image_src, $image_meta, $attachment_id){
395 $sm_mode = $this->get( 'sm.mode' );
396 if (empty($image_meta['gs_link'])) {
397 $image_meta = wp_get_attachment_metadata($attachment_id);
398 }
399
400 if(is_array($sources) && !empty( $image_meta['gs_link'] )){
401 $gs_name = $image_meta['gs_name'];
402 // getting position of root_dir in gs_name.
403 $root_dir_pos = strpos($gs_name, $image_meta['file']);
404 // removing rood_dir from gs_name so we can compare to replace url with gs_link.
405 if($root_dir_pos !== false){
406 $gs_name = substr($gs_name, $root_dir_pos);
407 }
408
409 if ( !isset($gs_name) || empty($gs_name) ) {
410 return [];
411 }
412
413 foreach ($sources as $width => &$image) {
414
415 // If srcset includes original image src, replace it
416 if (substr_compare($image['url'], $gs_name, -strlen($gs_name)) === 0) {
417 $image['url'] = $image_meta['gs_link'];
418 // Replace all sizes
419 } elseif (isset($image_meta['sizes']) && is_array($image_meta['sizes'])) {
420 $found = false;
421 foreach ($image_meta['sizes'] as $key => $meta) {
422 if (!isset($meta['gs_name']) || empty($meta['gs_name'])) {
423 continue;
424 }
425
426 $thumb_gs_name = $meta['gs_name'];
427 // removing rood_dir from gs_name
428 if($root_dir_pos !== false){
429 $thumb_gs_name = substr($thumb_gs_name, $root_dir_pos);
430 }
431
432 if (substr_compare($image['url'], $thumb_gs_name, -strlen($thumb_gs_name)) === 0) {
433 $image['url'] = $meta['gs_link'];
434 $found = true;
435 break;
436 }
437 }
438
439 // if no size found and mode is ephemeral or stateless and nothing to show for srcset item - unset that item
440 if (!$found && ( $sm_mode === 'ephemeral' || $sm_mode === 'stateless' )) {
441 $image = null;
442 }
443 } else {
444 // if mode is stateless and nothing to show for srcset item - unset that item
445 if ( $sm_mode === 'ephemeral' || $sm_mode === 'stateless' ) {
446 $image = null;
447 }
448 }
449 }
450 } elseif ( is_array( $sources ) && $sm_mode === 'stateless' ) {
451 foreach ($sources as $width => &$image) {
452 // Set default src
453 $image['url'] = $image_src;
454 }
455 }
456
457 return array_filter( $sources );
458 }
459
460 /**
461 * Return gs host.
462 * If custom domain is set it's return bucket name as host,
463 * else return storage.googleapis.com as host and append bucket name at the end.
464 *
465 * @param array $sm
466 * @return mixed|void
467 */
468 public function get_gs_host($sm = array()) {
469 $sm = $sm?$sm: $this->get( 'sm');
470 $image_host = 'https://storage.googleapis.com/';
471 $image_host .= $sm['bucket'];
472
473 $custom_domain = $sm['custom_domain'];
474 $is_ssl = strpos($custom_domain, 'https://');
475 $custom_domain = str_replace(array('http://', 'https://'), '', $custom_domain);
476 $custom_domain = trim($custom_domain, '/');
477
478 // checking whether the provided domain is valid.
479 // if the custom domain is same as the bucket name
480 // or the custom domain is using https.
481 if ( !empty($sm['bucket']) && !empty($custom_domain) && $custom_domain !== 'storage.googleapis.com' && ( $is_ssl === 0 || $custom_domain == $sm['bucket'] ) ) {
482 $image_host = $is_ssl === 0 ? 'https://' : 'http://'; // bucketname will be host
483 $image_host .= $custom_domain;
484 }
485
486 return apply_filters( 'get_gs_host', $image_host, $image_host, $sm['bucket'], $is_ssl, $sm );
487 }
488
489 /**
490 * Filter for wp_stateless_bucket_link if custom domain is set.
491 * It's get attachment url and remove "storage.googleapis.com" from url.
492 * So that custom url can be used.
493 *
494 * @param $fileLink
495 * @return mixed|string
496 */
497 public function wp_stateless_bucket_link($fileLink) {
498 $bucketname = $this->get( 'sm.bucket' );
499 $custom_domain = $this->get( 'sm.custom_domain' );
500 $is_ssl = strpos($custom_domain, 'https://') === 0;
501 $fileLink_is_ssl = strpos($fileLink, 'https://') === 0;
502 $custom_domain = str_replace(array('http://', 'https://'), '', $custom_domain);
503 $custom_domain = trim($custom_domain, '/');
504
505 if ( !empty($bucketname) && $custom_domain !== 'storage.googleapis.com' && $custom_domain == $bucketname && strpos($fileLink, $bucketname) > 8 ) {
506 $fileLink = ($is_ssl ? 'https://' : 'http://') . substr($fileLink, strpos($fileLink, $bucketname));
507 }
508 elseif( $custom_domain !== 'storage.googleapis.com' && $custom_domain == $bucketname && $fileLink_is_ssl !== $is_ssl){
509 if($is_ssl)
510 $fileLink = str_replace(array('http://', 'https://'), 'https://', $fileLink);
511 else
512 $fileLink = str_replace(array('http://', 'https://'), 'http://', $fileLink);
513 }
514 return $fileLink;
515 }
516
517 /**
518 * Return settings page url.
519 *
520 * @param string $path
521 * @return string
522 */
523 public function get_settings_page_url( $path = '' ) {
524 $url = get_admin_url( get_current_blog_id(), ( is_network_admin() ? 'network/settings.php' : 'upload.php' ) );
525 return $url . $path;
526 }
527
528 /**
529 * Get new blog settings once switched blog.
530 * @param $new_blog
531 * @param $prev_blog_id
532 */
533 public function on_switch_blog( $new_blog, $prev_blog_id ) {
534 $this->settings->refresh();
535 }
536
537 /**
538 * Get settings handler.
539 * Filling array if some settings missing.
540 *
541 * @param $settings
542 * @return
543 */
544 public function get_settings($settings) {
545
546 $settings_list = array(
547 'mode',
548 'body_rewrite',
549 'body_rewrite_types',
550 'bucket',
551 'root_dir',
552 'key_json',
553 'cache_control',
554 'delete_remote',
555 'custom_domain',
556 'organize_media',
557 'hashify_file_name'
558 );
559
560 foreach ($settings_list as $setting) {
561
562 /** If setting is already exist, just skip it */
563 if( isset( $settings[ $setting ] ) ) {
564 continue;
565 }
566
567 $value = $this->get( 'sm.' . $setting );
568
569 /** Decode json to array */
570 if( $value && is_string( $value ) && $setting === 'key_json' ) {
571 $value = json_decode( $value, true );
572 $setting = 'key';
573 }
574
575 $settings[ $setting ] = $value;
576 }
577
578 return $settings;
579 }
580
581 /**
582 * Remove all settings.
583 * @param bool $network
584 */
585 public function reset($network = false) {
586 $this->settings->reset($network);
587 }
588
589 /**
590 * @param $actions
591 * @param $post
592 * @param $detached
593 * @return mixed
594 */
595 public function add_custom_row_actions( $actions, $post, $detached ) {
596
597 if ( !current_user_can( 'upload_files' ) ) return $actions;
598
599 $sm_cloud = get_post_meta( $post->ID, 'sm_cloud', 1 );
600 $sm_mode = $this->get( 'sm.mode' );
601 if ( !empty($sm_cloud) && $sm_mode === 'stateless' ) return $actions;
602
603 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
604 $actions['sm_sync'] = '<a href="javascript:;" data-id="'.$post->ID.'" data-type="image" class="sm_inline_sync">' . __('Regenerate and Sync with GCS', ud_get_stateless_media()->domain) . '</a>';
605 }
606
607 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
608 $actions['sm_sync'] = '<a href="javascript:;" data-id="'.$post->ID.'" data-type="other" class="sm_inline_sync">' . __('Sync with GCS', ud_get_stateless_media()->domain) . '</a>';
609 }
610
611 return $actions;
612
613 }
614
615 /**
616 * Define REST API.
617 *
618 * @author korotkov@UD
619 */
620 public function api_init() {
621
622 $route_namespace = 'wp-stateless/v1';
623 $api_namespace = 'wpCloud\StatelessMedia\API';
624
625 register_rest_route( $route_namespace, '/status', array(
626 'methods' => \WP_REST_Server::READABLE,
627 'callback' => array( $api_namespace, 'status' ),
628 'permission_callback' => '__return_true'
629 ) );
630
631 register_rest_route( $route_namespace, '/getSettings', array(
632 'methods' => \WP_REST_Server::READABLE,
633 'callback' => array( $api_namespace, 'getSettings' ),
634 'permission_callback' => array( $api_namespace, 'authCheck' )
635 ) );
636
637 register_rest_route( $route_namespace, '/updateSettings', array(
638 'methods' => \WP_REST_Server::CREATABLE,
639 'callback' => array( $api_namespace, 'updateSettings' ),
640 'permission_callback' => array( $api_namespace, 'authCheck' )
641 ) );
642
643 }
644
645 /**
646 * Metabox for media modal page
647 * @param $form_fields
648 * @param $post
649 * @return array
650 */
651 public function attachment_modal_meta_box_callback ( $form_fields, $post ) {
652
653 //do not show on media edit page, only on modal
654 if ( isset($_GET['post'])) {
655 return $form_fields;
656 }
657
658 $link = get_edit_post_link($post->ID);
659
660 $form_field[ 'label' ] = '';
661 $form_field[ 'input' ] = 'html';
662 $form_field[ 'html' ] = sprintf("<script>jQuery('.actions').prepend('<a href=\"%s#sm-attachment-metabox\">%s</a> | ')</script>", $link, __("View stateless meta", ud_get_stateless_media()->domain));
663 $form_field[ 'show_in_modal' ] = true;
664
665 $form_fields[ 'sm_html' ] = $form_field;
666 return $form_fields;
667 }
668
669 /**
670 * Metabox for media edit page
671 * @param $meta_boxes
672 * @return array
673 */
674 public function attachment_meta_box_callback( $meta_boxes ) {
675 $post_id = false;
676 if ( isset( $_GET['post'] ) ) {
677 $post_id = intval( $_GET['post'] );
678 } elseif ( isset( $_POST['post_ID'] ) ) {
679 $post_id = intval( $_POST['post_ID'] );
680 } elseif ( isset( $_GET['item'] ) ) {
681 $post_id = intval( $_GET['item'] );
682 } elseif ( isset( $_POST['item'] ) ) {
683 $post_id = intval( $_POST['item'] );
684 }
685
686 return $this->_prepare_data_for_metabox( $meta_boxes, $post_id );
687 }
688
689 /**
690 * Prepare data for metabox fields
691 * @param $meta_boxes
692 * @param $post_id
693 * @return array
694 */
695 private function _prepare_data_for_metabox ( $meta_boxes, $post_id ) {
696 $post = get_post ( $post_id );
697 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
698 $sm_mode = $this->get( 'sm.mode' );
699
700 if ( empty( $post ) ) {
701 return $meta_boxes;
702 }
703
704 $sizes = $this->get_image_sizes();
705
706 $fields = array();
707
708 if ( $sm_mode !== 'stateless' || empty( $sm_cloud ) ) {
709 $fields[] = array(
710 'name' => __( 'Regenerate', ud_get_stateless_media()->domain ),
711 'id' => 'storage_bucket_url',
712 'type' => 'custom_html',
713 'std' => $this->_prepare_generate_link( $post, false, '', true, $sm_cloud ),
714 'tab' => 'thumbnails',
715 );
716 }
717
718 if ( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
719
720 $fields[] = array(
721 'type' => 'heading',
722 'name' => 'Files',
723 'tab' => 'thumbnails',
724 );
725
726 $fields[] = array(
727 'name' => __( 'Original', ud_get_stateless_media()->domain ),
728 'id' => 'storage_bucket_url',
729 'type' => 'custom_html',
730 'media_modal' => true,
731 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="'.esc_attr($sm_cloud[ 'fileLink' ]).'" />
732 <a href="'.$sm_cloud[ 'fileLink' ].'" target="_blank" class="sm-view-link"><i class="dashicons dashicons-external"></i></a>&nbsp;&nbsp;&nbsp;'.$this->_prepare_generate_link( $post, true, '', false, $sm_cloud ).' </label>',
733 'tab' => 'thumbnails',
734 );
735
736 if ( !empty( $sm_cloud['sizes'] ) && is_array( $sm_cloud['sizes'] ) ) {
737 foreach( $sm_cloud['sizes'] as $size_label => $size ) {
738
739 $fields[] = array(
740 'name' => __( sprintf( "%s x %s", ($size['width'] ?: $sizes[$size_label]['width']), ($size['height'] ?: $sizes[$size_label]['height']) ), ud_get_stateless_media()->domain ),
741 'id' => 'storage_bucket_url'.$size_label,
742 'type' => 'custom_html',
743 'media_modal' => true,
744 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="'.esc_attr($size[ 'fileLink' ]).'" />
745 <a href="'.$size[ 'fileLink' ].'" target="_blank" class="sm-view-link"><i class="dashicons dashicons-external"></i></a>&nbsp;&nbsp;&nbsp;'.$this->_prepare_generate_link( $post, true, $size_label, false, $sm_cloud ).' </label>',
746 'tab' => 'thumbnails',
747 );
748 }
749 }
750
751 if( !empty( $sm_cloud[ 'cacheControl' ] ) ) {
752 $fields[] = array(
753 'name' => __( 'Cache Control', ud_get_stateless_media()->domain ),
754 'id' => 'cache_control',
755 'type' => 'custom_html',
756 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="'.$sm_cloud[ 'cacheControl' ].'" /></label>',
757 'tab' => 'meta',
758 );
759 }
760
761 if ( !empty( $sm_cloud[ 'bucket' ] ) ) {
762 $fields[] = array(
763 'name' => __( 'Storage Bucket', ud_get_stateless_media()->domain ),
764 'id' => 'storage_bukcet',
765 'type' => 'custom_html',
766 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="gs://'.esc_attr($sm_cloud[ 'bucket' ]).'" />
767 <a href="https://console.cloud.google.com/storage/browser/'.esc_attr($sm_cloud[ 'bucket' ]).'" target="_blank" class="sm-view-link"><i class="dashicons dashicons-external"></i></a></label>',
768 'tab' => 'meta',
769 );
770 }
771
772 }
773
774 $meta_boxes[] = apply_filters( 'sm::attachment::meta', array(
775 'id' => 'sm-attachment-metabox',
776 'title' => __( 'Stateless', ud_get_stateless_media()->domain ),
777 'post_types' => 'attachment',
778 //'media_modal' => true,
779 //set context `side` for left column
780 'context' => 'normal',
781 'priority' => 'low',
782 'tabs' => array(
783 'thumbnails' => array(
784 'label' => __( 'Thumbnails', ud_get_stateless_media()->domain ),
785 'icon' => 'dashicons-format-gallery',
786 ),
787 'meta' => array(
788 'label' => __( 'Meta', ud_get_stateless_media()->domain ),
789 'icon' => 'dashicons-admin-site',
790 ),
791 ),
792 // Tab style: 'default', 'box' or 'left'. Optional
793 'tab_style' => 'left',
794 // Show meta box wrapper around tabs? true (default) or false. Optional
795 'tab_wrapper' => true,
796 'fields' => $fields
797 ), $post->ID );
798
799 return $meta_boxes;
800 }
801
802 /**
803 * Preparing link for sync
804 * @param $post
805 * @param bool $use_icon
806 * @param string $size
807 * @param bool $button
808 * @param array $sm_cloud
809 * @return string
810 */
811 private function _prepare_generate_link ( $post, $use_icon = false, $size = '', $button = false, $sm_cloud = array() ) {
812 $sync = '';
813
814 $sm_mode = $this->get( 'sm.mode' );
815
816 if ( current_user_can( 'upload_files' ) && $sm_mode !== 'disabled' && ( $sm_mode !== 'stateless' || empty($sm_cloud) ) ) {
817 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
818 $sync = '<a href="javascript:;" data-type="image" data-id="'.$post->ID.'" data-size="'.$size.'" data-reload_page="'.$button.'"
819 class="sm_inline_sync '.($button ? 'button button-primary button-large' : '').'">'. ( $use_icon ? "<i class='dashicons dashicons-image-rotate'></i>" : __( 'Regenerate and Sync with GCS', ud_get_stateless_media()->domain )).'</a>';
820 }
821 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
822 $sync = '<a href="javascript:;" data-type="other" data-id="'.$post->ID.'" data-size="'.$size.'" data-reload_page="'.$button.'"
823 class="sm_inline_sync '.($button ? 'button button-primary button-large' : '').'">'. ($use_icon ? "<i class='dashicons dashicons-image-rotate'></i>" : __( 'Sync with GCS', ud_get_stateless_media()->domain )).'</a>';
824 }
825 } elseif ( $button && $sm_mode !== 'stateless' ) {
826 $sync = __('You do not have access to sync or Stateless mode is Disabled', ud_get_stateless_media()->domain );
827 }
828 return $sync;
829 }
830
831 /**
832 * @param $current_path
833 * @param $use_root boolean: whether to use the root dir or not.
834 * 0 will be passed from various compatibilities so that the root dir is not used.
835 * false will passed from some compatibilities to use the value as local path.
836 * @param $attachment_id
837 * @param $size
838 * @param $use_wildcards
839 * @return string
840 */
841 public function handle_root_dir( $current_path, $use_root = true, $attachment_id = '', $size = '', $use_wildcards = false ) {
842 global $wpdb;
843
844 //for existing media
845 $cloud_meta = get_post_meta( $attachment_id, 'sm_cloud', true );
846
847 if ( !$use_wildcards && $cloud_meta ) {
848 if ( ( !$size || $size === '__full' ) && !empty( $cloud_meta['name'] ) ) {
849 return $cloud_meta['name'];
850 } else {
851 if ( !empty( $cloud_meta['sizes'] ) && isset( $cloud_meta['sizes'][$size] ) ) {
852 return !empty( $cloud_meta['sizes'][$size]['name'] ) ? $cloud_meta['sizes'][$size]['name'] : $current_path;
853 }
854 }
855 }
856
857 //non media files
858 if($use_root === 0){
859 $table_name = $wpdb->prefix . 'sm_sync';
860 $non_media = $wpdb->get_var($wpdb->prepare("SELECT file FROM {$table_name} WHERE file like '%%%s';", $current_path));
861 if ( $non_media ) {
862 return $non_media;
863 }
864 }
865
866
867 $root_dir = $this->get( 'sm.root_dir' );
868 $root_dir_regex = '~^' . apply_filters("wp_stateless_handle_root_dir", $root_dir, true) . '/~';
869 /**
870 * Retrieve Y/M and other tags from current path
871 */
872 $path_elements = apply_filters( 'wp_stateless_unhandle_root_dir', $current_path );
873 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir, false, $path_elements);
874
875 $upload_dir = wp_upload_dir();
876 $current_path = str_replace( wp_normalize_path( trailingslashit( $upload_dir[ 'basedir' ] ) ), '', wp_normalize_path( $current_path ) );
877 $current_path = str_replace( wp_normalize_path( trailingslashit( $upload_dir[ 'baseurl' ] ) ), '', wp_normalize_path( $current_path ) );
878 $current_path = str_replace( trailingslashit( $this->get_gs_host() ), '', $current_path );
879
880 /**
881 * Using only filename. Other parts of path included to $root_dir.
882 */
883 $current_path = basename($current_path);
884
885 if(!$use_root){
886 // removing the root dir if already exists in the begaining.
887 return preg_replace($root_dir_regex, '', $current_path);
888 }
889
890 // skip adding root dir if it's already added.
891 if ( !empty( $root_dir ) && !preg_match($root_dir_regex, $current_path) ) {
892 return $root_dir . '/' . trim( $current_path, '/ ' );
893 }
894
895 return $current_path;
896 }
897
898 /**
899 * @param $content
900 * @return mixed
901 */
902 public function the_content_filter( $content ) {
903
904 if ( $upload_data = wp_upload_dir() ) {
905
906 if ( !empty( $upload_data['url'] ) && !empty( $content ) ) {
907 $url = preg_replace('/https?:\/\//','',$upload_data['url']);
908
909 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
910 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
911 $root_dir = !empty( $root_dir ) ? $root_dir . '/' : false;
912 $image_host = $this->get_gs_host();
913 $file_ext = $this->replaceable_file_types();
914 $content = preg_replace( '/(href|src)=(\'|")(https?:\/\/'.str_replace('/', '\/', $url).')\/(.+?)('.$file_ext.')(\'|")/i',
915 '$1=$2'.$image_host.'/'.($root_dir?$root_dir:'').'$4$5$6', $content);
916 }
917 }
918
919 return $content;
920 }
921
922 /**
923 * Return file types supported by File URL Replacement.
924 * @return string
925 */
926 public function replaceable_file_types(){
927 $types = $this->get('sm.body_rewrite_types');
928
929 // Removing extra space.
930 $types = trim($types);
931 $types = preg_replace("/\s{2,}/", ' ', $types);
932
933 $types_arr = explode(' ', $types);
934 return '\.' . implode('|\.', $types_arr);
935 }
936
937 /**
938 * Copied from https://developer.wordpress.org/reference/functions/get_metadata/
939 *
940 * @param $value null unless other filter hooked in this function.
941 * @param $object_id post id
942 * @param $meta_key
943 * @param $single
944 * @return mixed
945 */
946 public function post_metadata_filter($value, $object_id, $meta_key, $single){
947 if(empty($value)){
948 $meta_type = 'post';
949 $transient_key = "stateless_{$meta_type}_meta";
950
951 $meta_cache = wp_cache_get($object_id, $transient_key);
952 if(empty($meta_cache)){
953 $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
954
955 if ( !$meta_cache ) {
956 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
957 $meta_cache = $meta_cache[$object_id];
958 }
959
960 foreach($meta_cache as $key => $meta){
961 $meta_cache[$key] = array_map('maybe_unserialize', $meta_cache[$key]);
962 }
963
964 $meta_cache = $this->convert_to_gs_link($meta_cache);
965 wp_cache_set($object_id, $meta_cache, $transient_key);
966 }
967
968 if ( ! $meta_key ) {
969 return $meta_cache;
970 }
971
972 if ( isset($meta_cache[$meta_key]) ) {
973 return $meta_cache[$meta_key];
974 }
975
976 // in case no metadata is found return what was passed in $value.
977 // $value most of the time is null.
978 return $value;
979 }
980
981 return $this->convert_to_gs_link($value);
982
983 }
984
985 /**
986 * Replace all image link with gs link and return only if meta modified.
987 *
988 * @param $meta
989 * @param $return
990 * @return mixed or null when not changed.
991 */
992 public function convert_to_gs_link($meta, $return = false){
993 $updated = $meta;
994 if ( $meta && $upload_data = wp_upload_dir() ) {
995 if ( !empty( $upload_data['url'] ) && !empty( $meta ) ) {
996 $url = preg_replace('/https?:\/\//','',$upload_data['url']);
997 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
998 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
999 $root_dir = !empty( $root_dir ) ? $root_dir . '/': false;
1000 $image_host = $this->get_gs_host().'/'.($root_dir?$root_dir:'');
1001 $file_ext = $this->replaceable_file_types();
1002 $updated = $this->_convert_to_gs_link($meta, $image_host, $url, $file_ext);
1003 }
1004 }
1005
1006 if($updated == $meta && !$return){
1007 return null; // Not changed.
1008 }
1009 return $updated;
1010 }
1011
1012 /**
1013 * Replace all image link with gs link
1014 *
1015 * @param $meta
1016 * @param $image_host
1017 * @param $url
1018 * @param $file_ext
1019 * @return array|null|string|string[]
1020 */
1021 public function _convert_to_gs_link($meta, $image_host, $url, $file_ext){
1022 if(is_array($meta)){
1023 foreach ($meta as $key => $value) {
1024 $meta[$key] = $this->_convert_to_gs_link($value, $image_host, $url, $file_ext);
1025 }
1026 return $meta;
1027 } elseif (is_object($meta) && $meta instanceof \stdClass ) {
1028 foreach (get_object_vars($meta) as $key => $value) {
1029 $meta->{$key} = $this->_convert_to_gs_link($value, $image_host, $url, $file_ext);
1030 }
1031 return $meta;
1032 } elseif(is_string($meta)){
1033 return preg_replace( '/(https?:\/\/'.str_replace('/', '\/', $url).')\/(.+?)(' . $file_ext . ')/i', $image_host.'$2$3', $meta);
1034 }
1035
1036 return $meta;
1037 }
1038
1039 /**
1040 * @param $links
1041 * @param $file
1042 * @return mixed
1043 */
1044 public function plugin_action_links( $links, $file ) {
1045
1046 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless-media.php' ) ) {
1047 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
1048 array_unshift( $links, $settings_link );
1049 }
1050
1051 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless.php' ) ) {
1052 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
1053 array_unshift( $links, $settings_link );
1054 }
1055
1056 return $links;
1057 }
1058
1059 /**
1060 * Determines if plugin is loaded via mu-plugins
1061 * or Network Enabled.
1062 *
1063 * @param bool $is_multisite
1064 * @return bool
1065 * @author peshkov@UD
1066 */
1067 public function is_network_detected($is_multisite = false) {
1068 /* Plugin is loaded via mu-plugins. */
1069
1070 if( strpos( Utility::normalize_path( $this->root_path ), Utility::normalize_path( WPMU_PLUGIN_DIR ) ) !== false ) {
1071 return true;
1072 }
1073
1074 if( is_multisite() ) {
1075 if($is_multisite) return true;
1076 /* Looks through network enabled plugins to see if our one is there. */
1077 foreach (wp_get_active_network_plugins() as $path) {
1078 // Trying again using readlink in case it's a symlink file.
1079 // boot_file is already solved.
1080 // wp_normalize_path is helpfull in windows.
1081 if (wp_normalize_path($this->boot_file) == wp_normalize_path($path) || (is_link($path) AND $this->boot_file == readlink($path))) {
1082 return true;
1083 }
1084 }
1085 }
1086 return false;
1087 }
1088
1089 /**
1090 * Initialization.
1091 * Register scripts and styles
1092 */
1093 public function admin_init() {
1094 $this->show_notice_stateless_cache_busting();
1095 wp_register_style( 'wp-stateless', $this->path( 'static/styles/wp-stateless.css', 'url' ), array(), self::$version );
1096
1097 /**
1098 * select2 styles
1099 */
1100 wp_register_style( 'wp-stateless-select2', $this->path( 'static/styles/select2.min.css', 'url' ), array(), self::$version );
1101
1102 /* Attachment or upload page */
1103 wp_register_script( 'wp-stateless-uploads-js', $this->path( 'static/scripts/wp-stateless-uploads.js', 'url' ), array( 'jquery' ), self::$version );
1104
1105 /* Setup wizard styles. */
1106 wp_register_style( 'wp-stateless-setup-wizard', $this->path( 'static/styles/wp-stateless-setup-wizard.css', 'url' ), array(), self::$version );
1107
1108 wp_register_script( 'wp-stateless-select2', ud_get_stateless_media()->path( 'static/scripts/select2.min.js', 'url' ), array( 'jquery' ), self::$version, true );
1109
1110 /* Stateless settings page */
1111 wp_register_script( 'wp-stateless-settings', ud_get_stateless_media()->path( 'static/scripts/wp-stateless-settings.js', 'url' ), array(), self::$version );
1112 wp_localize_script( 'wp-stateless-settings', 'stateless_l10n', $this->get_l10n_data() );
1113
1114 wp_register_style( 'wp-stateless-settings', $this->path( 'static/styles/wp-stateless-settings.css', 'url' ), array(), self::$version );
1115
1116 // Sync tab
1117 if ( wp_script_is( 'jquery-ui-widget', 'registered' ) ){
1118 wp_register_script( 'jquery-ui-progressbar', ud_get_stateless_media()->path('static/scripts/jquery-ui/jquery.ui.progressbar.min.js', 'url'), array( 'jquery-ui-core', 'jquery-ui-widget' ), '1.8.6' );
1119 }
1120 else{
1121 wp_register_script( 'jquery-ui-progressbar', ud_get_stateless_media()->path( 'static/scripts/jquery-ui/jquery.ui.progressbar.min.1.7.2.js', 'url' ), array( 'jquery-ui-core' ), '1.7.2' );
1122 }
1123 wp_register_script( 'wp-stateless-angular', ud_get_stateless_media()->path( 'static/scripts/angular.min.js', 'url' ), array(), '1.5.0', true );
1124 wp_register_script( 'wp-stateless-angular-sanitize', ud_get_stateless_media()->path( 'static/scripts/angular-sanitize.min.js', 'url' ), array('wp-stateless-angular'), '1.5.0', true );
1125 wp_register_script( 'wp-stateless', ud_get_stateless_media()->path( 'static/scripts/wp-stateless.js', 'url' ), array( 'jquery-ui-core', 'wp-stateless-settings' ), self::$version, true );
1126
1127 wp_localize_script( 'wp-stateless', 'stateless_l10n', $this->get_l10n_data() );
1128 wp_localize_script('wp-stateless', 'wp_stateless_configs', array(
1129 'WP_DEBUG' => defined('WP_DEBUG') ? WP_DEBUG : false,
1130 ));
1131
1132 $settings = ud_get_stateless_media()->get('sm');
1133 $settings['wildcards'] = $this->settings->wildcards;
1134 $settings['network_admin'] = is_network_admin();
1135 $settings['is_multisite'] = is_multisite();
1136 if(defined('WP_STATELESS_MEDIA_JSON_KEY') && WP_STATELESS_MEDIA_JSON_KEY){
1137 $settings['key_json'] = "Currently configured via a constant.";
1138 }
1139 wp_localize_script('wp-stateless', 'wp_stateless_settings', $settings);
1140 wp_localize_script('wp-stateless', 'wp_stateless_compatibility', Module::get_modules());
1141 wp_register_style( 'jquery-ui-regenthumbs', ud_get_stateless_media()->path( 'static/scripts/jquery-ui/redmond/jquery-ui-1.7.2.custom.css', 'url' ), array(), '1.7.2' );
1142
1143 }
1144
1145 /**
1146 * Get_l10n_data
1147 *
1148 * @param string $value
1149 * @return mixed
1150 */
1151 public function get_l10n_data($value=''){
1152 include ud_get_stateless_media()->path( 'l10n.php', 'dir');
1153 return $l10n;
1154 }
1155
1156 /**
1157 * Admin Scripts
1158 *
1159 * @param $hook
1160 */
1161 public function admin_enqueue_scripts( $hook ) {
1162
1163 switch( $hook ) {
1164
1165 case 'options-media.php':
1166 wp_enqueue_style( 'wp-stateless');
1167 break;
1168
1169 case 'upload.php':
1170 wp_enqueue_style( 'wp-stateless');
1171 wp_enqueue_script( 'wp-stateless-uploads-js' );
1172
1173 break;
1174
1175 case 'post.php':
1176
1177 global $post;
1178
1179 if ( $post->post_type == 'attachment' ) {
1180 wp_enqueue_style( 'wp-stateless');
1181 wp_enqueue_script( 'wp-stateless-uploads-js' );
1182 }
1183
1184 break;
1185
1186 case 'media_page_stateless-setup':
1187 case 'settings_page_stateless-setup':
1188 wp_enqueue_style( 'wp-stateless');
1189 wp_enqueue_style( 'wp-stateless-setup-wizard' );
1190 break;
1191 case 'media_page_stateless-settings':
1192 case 'settings_page_stateless-settings':
1193 wp_enqueue_style( 'wp-stateless');
1194 wp_enqueue_style( 'wp-stateless-select2');
1195 wp_enqueue_script( 'wp-stateless-settings' );
1196 wp_enqueue_script( 'wp-stateless-select2' );
1197 wp_enqueue_style( 'wp-stateless-settings' );
1198
1199 // Sync tab
1200 wp_enqueue_script( 'jquery-ui-progressbar' );
1201 wp_enqueue_script( 'wp-stateless-angular' );
1202 wp_enqueue_script( 'wp-stateless-angular-sanitize' );
1203 wp_enqueue_script( 'wp-stateless' );
1204 wp_enqueue_style( 'jquery-ui-regenthumbs' );
1205
1206 $data = array(
1207 'key' => 'stateless-cache-busting',
1208 'class' => 'notice',
1209 'title' => sprintf( __( "Stateless and Ephemeral modes enables and requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1210 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1211 Since Stateless mode bypasses this check, there is a potential for files to be stored with the same file name. We enforce the Cache-Busting option to prevent this.
1212 Override with the <a href='%s' target='_blank'>%s</a> constant.", ud_get_stateless_media()->domain),"https://wp-stateless.github.io/docs/constants/#wp_stateless_media_cache_busting", "WP_STATELESS_MEDIA_CACHE_BUSTING" ),
1213 );
1214 echo "<script id='template-stateless-cache-busting' type='text/html'>";
1215 include ud_get_stateless_media()->path( '/static/views/error-notice.php', 'dir' );
1216 echo "</script>";
1217
1218 break;
1219 default: break;
1220 }
1221
1222 }
1223
1224 /**
1225 * Add Attributes to media HTML
1226 *
1227 * @author potanin@UD
1228 * @param $attr
1229 * @param $attachment
1230 * @param $size
1231 * @return mixed
1232 */
1233 public function wp_get_attachment_image_attributes( $attr, $attachment, $size = null ) {
1234
1235 $sm_cloud = get_post_meta( $attachment->ID, 'sm_cloud', true );
1236 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'name' ] ) ) {
1237 $attr[ 'class' ] = $attr[ 'class' ] . ' wp-stateless-item';
1238 $attr[ 'data-image-size' ] = is_array( $size ) ? implode( 'x', $size ) : $size;
1239 $attr[ 'data-stateless-media-bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
1240 $attr[ 'data-stateless-media-name' ] = $sm_cloud[ 'name' ];
1241 }
1242
1243 return $attr;
1244
1245 }
1246
1247 /**
1248 * Adds filter link to Media Library table.
1249 *
1250 * @param $views
1251 * @return mixed
1252 */
1253 public function views_upload( $views ) {
1254 $views['stateless'] = '<a href="#">' . __( 'Stateless Media' ) . '</a>';
1255 return $views;
1256 }
1257
1258 /**
1259 * Replace media URL
1260 *
1261 * @param bool $false
1262 * @param integer $id
1263 * @param string $size
1264 * @return mixed $false
1265 */
1266 public function image_downsize( $false = false, $id, $size ) {
1267
1268 if ( (!isset( $this->client ) || !$this->client || is_wp_error( $this->client )) && $this->get( 'sm.mode' ) !== 'stateless' ) {
1269 return $false;
1270 }
1271
1272 /**
1273 * Check if enabled
1274 */
1275 if ( !in_array( $this->get( 'sm.mode' ), array( 'cdn', 'stateless', 'ephemeral' ) )) {
1276 return $false;
1277 }
1278
1279 /** Start determine remote file */
1280 $img_url = wp_get_attachment_url($id);
1281 $meta = wp_get_attachment_metadata($id);
1282 $width = $height = 0;
1283 $is_intermediate = false;
1284
1285 //** try for a new style intermediate size */
1286 if ( $intermediate = image_get_intermediate_size( $id, $size ) ) {
1287 if(!empty( $intermediate['gs_link'] )){
1288 $img_url = $intermediate['gs_link'];
1289 }
1290 else if(!empty( $intermediate['url'] )){
1291 $img_url = $intermediate['url'];
1292 }
1293 else{
1294 $img_url = dirname($img_url) . $intermediate['file'];
1295 }
1296
1297 $width = $intermediate['width'];
1298 $height = $intermediate['height'];
1299 $is_intermediate = true;
1300 }
1301
1302 /**
1303 * maybe try to get images info from sm_cloud
1304 * this case may happen when no local files
1305 * @author korotkov@ud
1306 */
1307 if ( !$width && !$height ) {
1308 $sm_cloud = get_post_meta( $id, 'sm_cloud', true );
1309 if ( is_string($size) && !empty( $sm_cloud['sizes'] ) && !empty( $sm_cloud['sizes'][$size] ) ) {
1310 global $_wp_additional_image_sizes;
1311
1312 $img_url = !empty( $sm_cloud['sizes'][$size]['fileLink'] ) ? $sm_cloud['sizes'][$size]['fileLink'] : $img_url;
1313
1314 if ( !empty( $_wp_additional_image_sizes[ $size ] ) ) {
1315 $width = !empty( $_wp_additional_image_sizes[ $size ]['width'] ) ? $_wp_additional_image_sizes[ $size ]['width'] : $width;
1316 $height = !empty( $_wp_additional_image_sizes[ $size ]['height'] ) ? $_wp_additional_image_sizes[ $size ]['height'] : $height;
1317 }
1318
1319 $is_intermediate = true;
1320 }
1321 }
1322
1323 if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
1324
1325 //** any other type: use the real image */
1326 $width = $meta['width'];
1327 $height = $meta['height'];
1328 }
1329
1330
1331 if ( $img_url) {
1332
1333 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
1334 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
1335 $img_url = apply_filters('wp_stateless_bucket_link', $img_url);
1336 return array( $img_url, $width, $height, $is_intermediate );
1337 }
1338
1339
1340 /**
1341 * All other cases work as usually
1342 */
1343 return $false;
1344
1345 }
1346
1347 /**
1348 * Extends metadata by adding GS information.
1349 * Note: must not be called directly. It's used only on hook
1350 *
1351 * @param $metadata
1352 * @param $attachment_id
1353 * @return array|mixed
1354 */
1355 public function wp_get_attachment_metadata( $metadata, $attachment_id ) {
1356 global $default_dir;
1357 $default_dir = false;
1358 /* Determine if the media file has GS data at all. */
1359 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
1360 // If metadata not passed the get metadata from post meta.
1361 if(empty($metadata)){
1362 $metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
1363 }
1364
1365 if(empty($metadata)){
1366 $metadata = [];
1367 }
1368
1369 if( is_array( $metadata ) && is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1370 $metadata[ 'gs_link' ] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'fileLink' ]);
1371 $metadata[ 'gs_name' ] = isset( $sm_cloud[ 'name' ] ) ? $sm_cloud[ 'name' ] : false;
1372 $metadata[ 'gs_bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
1373 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
1374 foreach( $metadata[ 'sizes' ] as $k => $v ) {
1375 if( !empty( $sm_cloud[ 'sizes' ][ $k ][ 'name' ] ) ) {
1376 $metadata['sizes'][$k]['gs_name'] = $sm_cloud[ 'sizes' ][ $k ][ 'name' ];
1377 $metadata['sizes'][$k]['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'sizes' ][ $k ][ 'fileLink' ]);
1378 }
1379 }
1380 }
1381 }
1382 if(is_multisite() && !empty($metadata['file'])){
1383 if ( $this->get('sm.mode') == 'stateless' ) {
1384 $default_dir = true;
1385 $uploads = wp_get_upload_dir();
1386 $default_dir = false;
1387
1388 $file_path_fix = $uploads['basedir'] . "/{$metadata['file']}";
1389 if(file_exists($file_path_fix)) {
1390 $metadata[ 'file' ] = "{$metadata['file']}";
1391 }
1392 } else {
1393 $uploads = wp_get_upload_dir();
1394 $blog_id = get_current_blog_id();
1395 $file_path_fix = $uploads[ 'basedir' ] . "/sites/$blog_id/{$metadata['file']}";
1396 if( file_exists( $file_path_fix ) ) {
1397 $metadata[ 'file' ] = "sites/$blog_id/{$metadata['file']}";
1398 }
1399 }
1400 } elseif ( empty($sm_cloud) && $this->get('sm.mode') == 'stateless' ) {
1401 $default_dir = true;
1402 $uploads = wp_get_upload_dir();
1403 $default_dir = false;
1404
1405 $file_path_fix = $uploads['basedir'] . "/{$metadata['file']}";
1406 if(file_exists($file_path_fix)) {
1407 $metadata[ 'file' ] = "{$metadata['file']}";
1408 }
1409 }
1410
1411 return $metadata;
1412 }
1413
1414 /**
1415 *
1416 * @param $file
1417 * @param $attachment_id
1418 * @return string
1419 */
1420 public function get_attached_file($file, $attachment_id){
1421 global $default_dir;
1422 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', 1 );
1423 /* Determine if the media file has GS data at all. */
1424 if(is_multisite()){
1425 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
1426 $_file = get_post_meta( $attachment_id, '_wp_attached_file', true );
1427 if($_file){
1428 $blog_id = get_current_blog_id();
1429 $uploads = wp_get_upload_dir();
1430 $_file = apply_filters( 'wp_stateless_file_name', $_file, false);
1431 $file_path_fix = $uploads['basedir'] . "/sites/$blog_id/$_file";
1432 if(file_exists($file_path_fix)){
1433 $file = $file_path_fix;
1434 }
1435 }
1436 } elseif ( empty($sm_cloud) && $this->get('sm.mode') == 'stateless' ) {
1437 $_file = get_post_meta( $attachment_id, '_wp_attached_file', true );
1438 if( $_file ) {
1439 $default_dir = true;
1440 $uploads = wp_get_upload_dir();
1441 $default_dir = false;
1442 return $uploads[ 'basedir' ] . '/' . $_file;
1443 }
1444 }
1445 return $file;
1446 }
1447
1448 /**
1449 * Returns client object
1450 * or WP_Error on failure.
1451 *
1452 * @author peshkov@UD
1453 * @return object $this->client. \wpCloud\StatelessMedia\GS_Client or \WP_Error
1454 */
1455 public function get_client() {
1456
1457 if( null === $this->client ) {
1458
1459 $key_json = $this->get( 'sm.key_json' );
1460 if ( empty($key_json) ) {
1461 $key_json = get_site_option( 'sm_key_json' );
1462 }
1463
1464 /* Try to initialize GS Client */
1465 $this->client = GS_Client::get_instance( array(
1466 'bucket' => $this->get( 'sm.bucket' ),
1467 'key_json' => $key_json
1468 ) );
1469 }
1470
1471 return $this->client;
1472
1473 }
1474
1475 /**
1476 * Determines if we can connect to Google Storage Bucket.
1477 *
1478 * @author peshkov@UD
1479 */
1480 public function is_connected_to_gs() {
1481
1482 $trnst = get_transient( 'sm::is_connected_to_gs' );
1483
1484 if ( empty($trnst) || false === $trnst || !isset( $trnst[ 'hash' ] ) || $trnst[ 'hash' ] != md5( serialize( $this->get( 'sm' ) ) ) ) {
1485 $trnst = array(
1486 'success' => 'true',
1487 'error' => '',
1488 'hash' => md5( serialize( $this->get( 'sm' ) ) ),
1489 );
1490 $client = $this->get_client();
1491
1492 if ( is_wp_error( $client ) ) {
1493 $trnst[ 'success' ] = 'false';
1494 $trnst[ 'error' ] = $client->get_error_message();
1495 } else {
1496 $connected = $client->is_connected();
1497 if( $connected !== true ) {
1498 $trnst[ 'success' ] = 'false';
1499 $trnst[ 'error' ] = sprintf( __( 'Could not connect to Google Storage bucket. Please, be sure that bucket with name <b>%s</b> exists.', $this->domain ), $this->get( 'sm.bucket' ) );
1500
1501 if( is_callable(array($connected, 'getHandlerContext')) && $handlerContext = $connected->getHandlerContext() ){
1502 if(!empty($handlerContext['error'])){
1503 $handlerContext['error'];
1504 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($handlerContext['error']);
1505 }
1506 }
1507
1508 if( is_callable(array($connected, 'getErrors')) && $error = $connected->getErrors() ){
1509 $error = reset($error);
1510 if($error['reason'] == 'accessNotConfigured')
1511 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($error['message']);
1512 }
1513 }
1514 }
1515 set_transient( 'sm::is_connected_to_gs', $trnst, 4 * HOUR_IN_SECONDS );
1516 }
1517
1518 if( isset( $trnst[ 'success' ] ) && $trnst[ 'success' ] == 'false' ) {
1519 return new \WP_Error( 'error', ( !empty( $trnst[ 'error' ] ) ? $trnst[ 'error' ] : __( 'There is an Error on connection to Google Storage.', $this->domain ) ) );
1520 }
1521
1522 return true;
1523 }
1524
1525 /**
1526 * Flush all plugin transients
1527 *
1528 */
1529 public function flush_transients() {
1530 delete_transient( 'sm::is_connected_to_gs' );
1531 }
1532
1533 /**
1534 * Plugin Activation
1535 *
1536 */
1537 public function activate() {
1538 add_action( 'activated_plugin', array($this, 'redirect_to_splash'), 99 );
1539 $this->run_upgrade_process();
1540 }
1541
1542 /**
1543 * Run Install Process.
1544 * Triggered on plugins_loaded instead of register_activation_hook action.
1545 * Works on even manual plugin update.
1546 *
1547 * @author alim@UD
1548 */
1549 public function run_install_process()
1550 {
1551 // calling the upgrade function because it's same as this point for fresh install or updates.
1552 $this->run_upgrade_process();
1553 }
1554
1555 /**
1556 * Run Upgrade Process:
1557 * Triggered on plugins_loaded instead of register_activation_hook action.
1558 * Works on even manual plugin update.
1559 *
1560 * @author alim@UD
1561 */
1562 public function run_upgrade_process()
1563 {
1564 // Creating database on new installation.
1565 $this->create_db();
1566 /**
1567 * Maybe Upgrade current Version
1568 */
1569 Upgrader::call( $this->args[ 'version' ] );
1570 }
1571
1572 /**
1573 * Create database on plugin activation.
1574 * @param boolean $force - whether to create db even if option exists. For debug purpose only.
1575 */
1576 public function create_db($force = false) {
1577 global $wpdb;
1578 $sm_sync_db_version = get_option( 'sm_sync_db_version' );
1579
1580 if( $sm_sync_db_version && $force == false ) {
1581 return;
1582 }
1583
1584 $table_name = $wpdb->prefix . 'sm_sync';
1585 $charset_collate = $wpdb->get_charset_collate();
1586
1587 // `expire` timestamp NULL DEFAULT NULL,
1588 $sql = "CREATE TABLE $table_name (
1589 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT ,
1590 `file` varchar(255) NOT NULL ,
1591 `status` varchar(10) NOT NULL ,
1592 PRIMARY KEY (`id`)
1593 ) $charset_collate;";
1594
1595 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
1596 dbDelta( $sql );
1597
1598 add_option( 'sm_sync_db_version', $this->args[ 'version' ] );
1599 }
1600
1601 /**
1602 * Delete table when blog is deleted.
1603 *
1604 * @param $old_site
1605 */
1606 public function wp_delete_site($old_site){
1607 global $wpdb;
1608
1609 switch_to_blog( $old_site->id );
1610 $table_name = $wpdb->prefix . 'sm_sync';
1611
1612 $sql = "DROP TABLE IF EXISTS $table_name";
1613 $wpdb->query($sql);
1614 restore_current_blog();
1615 }
1616
1617 /**
1618 * Redirect_to_splash
1619 *
1620 * @param string $plugin
1621 */
1622 public function redirect_to_splash($plugin =''){
1623 // $this->settings = new Settings();
1624
1625 if(defined( 'WP_CLI' ) || $this->settings->get('sm.key_json') || isset($_POST['checked']) && count($_POST['checked']) > 1){
1626 return;
1627 }
1628
1629 if(
1630 !$this->settings->get('sm.key_json') &&
1631 defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true &&
1632 defined('WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL') && WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL == true
1633 ) {
1634 return;
1635 }
1636
1637 if( !$this->settings->get('sm.key_json') && defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true ) {
1638 $url = $this->get_settings_page_url('?page=stateless-settings');
1639 exit( wp_redirect($url));
1640 }
1641
1642 if( $plugin == plugin_basename( $this->boot_file ) ) {
1643 $url = $this->get_settings_page_url('?page=stateless-setup&step=splash-screen');
1644 if(json_decode($this->settings->get('sm.key_json'))){
1645 $url = $this->get_settings_page_url('?page=stateless-settings');
1646 }
1647 exit( wp_redirect($url));
1648 }
1649 }
1650
1651 /**
1652 * Plugin Deactivation
1653 *
1654 */
1655 public function deactivate() {}
1656
1657 /**
1658 * Show_notice_stateless_cache_busting
1659 *
1660 */
1661 public function show_notice_stateless_cache_busting(){
1662 $this->errors->add( array(
1663 'key' => 'stateless_cache_busting',
1664 'button' => 'View Settings',
1665 'button_link' => admin_url('upload.php?page=stateless-settings'),
1666 'title' => sprintf( __( "Stateless mode now requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1667 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1668 Since Stateless mode bypasses this check, there is a potential for files to be stored with the same file name. We enforce the Cache-Busting option to prevent this.
1669 Override with the <a href='%s' target='_blank'>%s</a> constant.", ud_get_stateless_media()->domain),"https://wp-stateless.github.io/docs/constants/#wp_stateless_media_cache_busting", "WP_STATELESS_MEDIA_CACHE_BUSTING" ),
1670 ), 'notice' );
1671 }
1672
1673 /**
1674 * Filter for wp_get_attachment_url();
1675 * @param string $url
1676 * @param string $post_id
1677 * @return mixed|null|string
1678 */
1679 public function wp_get_attachment_url( $url = '', $post_id = '' ) {
1680 global $default_dir;
1681 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
1682 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1683 $_url = parse_url($sm_cloud[ 'fileLink' ]);
1684 $url = !isset($_url['scheme']) ? ( 'https:' . $sm_cloud[ 'fileLink' ] ) : $sm_cloud[ 'fileLink' ];
1685 return apply_filters('wp_stateless_bucket_link', $url);
1686 }
1687 elseif(is_multisite() && empty($sm_cloud)){
1688 $_file = get_post_meta( $post_id, '_wp_attached_file', true );
1689 if($_file){
1690 if ( $this->get('sm.mode') == 'stateless' ) {
1691 $default_dir = true;
1692 $uploads = wp_get_upload_dir();
1693 $default_dir = false;
1694 return $uploads['baseurl'].'/'.$_file;
1695 } else {
1696 $uploads = wp_get_upload_dir();
1697 $default_dir = false;
1698 $_file = apply_filters( 'wp_stateless_file_name', $_file, false );
1699 $blog_id = get_current_blog_id();
1700 $file_path_fix = $uploads[ 'basedir' ] . "/sites/$blog_id/$_file";
1701
1702 if( file_exists( $file_path_fix ) ) {
1703 $url = $uploads[ 'baseurl' ] . "/sites/$blog_id/$_file";
1704 }
1705 }
1706 }
1707 } elseif ( empty($sm_cloud) && $this->get('sm.mode') == 'stateless' ) {
1708 $_file = get_post_meta( $post_id, '_wp_attached_file', true );
1709 if($_file){
1710 $default_dir = true;
1711 $uploads = wp_get_upload_dir();
1712 $default_dir = false;
1713
1714 return $uploads['baseurl'].'/'.$_file;
1715 }
1716 }
1717 return $url;
1718 }
1719
1720 /**
1721 * Filter for attachment_url_to_postid()
1722 *
1723 * @param int|false $post_id originally found post ID (or false if not found)
1724 * @param string $url the URL to find the post ID for
1725 * @return int|false found post ID from cloud storage URL
1726 */
1727 public function attachment_url_to_postid( $post_id, $url ) {
1728 global $wpdb;
1729
1730 if ( ! $post_id ) {
1731 $post_id = get_transient("stateless_url_to_postid_" . md5($url));
1732
1733 if(defined('WP_STATELESS_LEGACY_URL_TO_POSTID')){
1734 // User can use this constant if they change the Bucket Folder (root_dir) after uploading image.
1735 // This can be little slow at first run.
1736 if(empty($post_id)){
1737 $query = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'sm_cloud' AND meta_value LIKE '%s'";
1738 $post_id = $wpdb->get_var( $wpdb->prepare( $query, '%' . $url . '%' ) );
1739
1740 if($post_id){
1741 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1742 }
1743 }
1744 return $post_id;
1745 }
1746
1747 if(empty($post_id)){
1748 $gs_base_url = $this->get_gs_host();
1749 $root_dir = $this->get( 'sm.root_dir' );
1750 $path_elements = apply_filters( 'wp_stateless_unhandle_root_dir', $url );
1751 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir, false, $path_elements);
1752 $gs_url = $this->get_gs_host() . '/' . $root_dir;
1753 $site_url = parse_url($gs_url);
1754 $image_path = parse_url( $url );
1755
1756 //force the protocols to match if needed
1757 if( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
1758 $url = str_replace( $image_path['scheme'], $site_url['scheme'], $url );
1759 }
1760
1761 if( 0 === strpos( $url, $gs_url . '/' ) ) {
1762 $url = substr( $url, strlen( $gs_url . '/' ) );
1763 }
1764 else if( 0 === strpos( $url, $gs_base_url . '/' ) ) {
1765 // In case user added Bucket Folder (root_dir) after uploading image.
1766 $url = substr( $url, strlen( $gs_base_url . '/' ) );
1767 }
1768
1769 /**
1770 * If `uploads_use_yearmonth_folders` is set - adding year and month to url
1771 */
1772 $organize_media = get_option('uploads_use_yearmonth_folders');
1773 $path = '';
1774 if ( $organize_media == '1' && isset ($path_elements['%date_year/date_month%']) ) {
1775 $path .= $path_elements['%date_year/date_month%'].'/';
1776 }
1777 $url = $path . $url;
1778
1779 $sql = $wpdb->prepare(
1780 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
1781 $url
1782 );
1783 $post_id = $wpdb->get_var( $sql );
1784
1785 if($post_id){
1786 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1787 }
1788 }
1789 }
1790
1791 return $post_id;
1792 }
1793
1794 /**
1795 * Change Upload BaseURL when CDN Used.
1796 *
1797 * @param $data
1798 * @return mixed
1799 */
1800 public function upload_dir( $data ) {
1801 $data[ 'basedir' ] = $this->get_gs_host();
1802 $data[ 'baseurl' ] = $this->get_gs_host();
1803 $data[ 'url' ] = $data[ 'baseurl' ] . $data[ 'subdir' ];
1804
1805 return $data;
1806
1807 }
1808
1809 /**
1810 * Set Feature Flag constants by parsing composer.json
1811 *
1812 * @todo Make sure settings from DB can override these.
1813 *
1814 * @author potanin@UD
1815 * @return array|mixed|null|object
1816 */
1817 public function parse_feature_flags( ) {
1818
1819 try {
1820
1821 $_raw = file_get_contents( Utility::normalize_path( $this->root_path ) . 'composer.json' );
1822
1823 $_parsed = json_decode( $_raw );
1824
1825 // @todo Catch poorly formatted JSON.
1826 if( !is_object( $_parsed ) ) {
1827 // throw new Error( "unable to parse." );
1828 }
1829
1830 foreach( (array) $_parsed->extra->featureFlags as $_feature ) {
1831
1832 if( !defined( $_feature->constant ) ) {
1833 define( $_feature->constant, $_feature->enabled );
1834
1835 if( $_feature->enabled ) {
1836 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] enabled.' );
1837 } else {
1838 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] disabled.' );
1839 }
1840 }
1841
1842 }
1843
1844 } catch ( \Exception $e ) {
1845 Utility::log( 'Unable to parse [composer.json] feature flags. Error: [' . $e->getMessage() . ']' );
1846 // echo 'Caught exception: ', $e->getMessage(), "\n";
1847 }
1848
1849 return isset( $_parsed ) ? $_parsed : null;
1850
1851 }
1852
1853 /**
1854 * Determine if Utility class contains missed function
1855 * in other case, just return NULL to prevent ERRORS
1856 *
1857 * @author peshkov@UD
1858 * @param $name
1859 * @param $arguments
1860 * @return mixed|null
1861 */
1862 public function __call( $name, $arguments ) {
1863 if( is_callable( array( "wpCloud\\StatelessMedia\\Utility", $name ) ) ) {
1864 return call_user_func_array( array( "wpCloud\\StatelessMedia\\Utility", $name ), $arguments );
1865 } else {
1866 return NULL;
1867 }
1868 }
1869
1870 /**
1871 * Get all thumbnail sizes
1872 *
1873 * @global $_wp_additional_image_sizes
1874 * @uses get_intermediate_image_sizes()
1875 *
1876 * @param boolean [$unset_disabled = true]
1877 * @return array
1878 */
1879 function get_image_sizes( $unset_disabled = true ) {
1880 $wais = & $GLOBALS['_wp_additional_image_sizes'];
1881
1882 $sizes = array();
1883
1884 foreach ( get_intermediate_image_sizes() as $_size ) {
1885 if ( in_array( $_size, array('thumbnail', 'medium', 'medium_large', 'large') ) ) {
1886 $sizes[ $_size ] = array(
1887 'width' => get_option( "{$_size}_size_w" ),
1888 'height' => get_option( "{$_size}_size_h" ),
1889 'crop' => (bool) get_option( "{$_size}_crop" ),
1890 );
1891 }
1892 elseif ( isset( $wais[$_size] ) ) {
1893 $sizes[ $_size ] = array(
1894 'width' => $wais[ $_size ]['width'],
1895 'height' => $wais[ $_size ]['height'],
1896 'crop' => $wais[ $_size ]['crop'],
1897 );
1898 }
1899
1900 // size registered, but has 0 width and height
1901 if( $unset_disabled && ($sizes[ $_size ]['width'] == 0) && ($sizes[ $_size ]['height'] == 0) )
1902 unset( $sizes[ $_size ] );
1903 }
1904
1905 return $sizes;
1906 }
1907 }
1908 }
1909 }
1910