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-bootstrap.php

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

1,913 lines 70.0 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', 'stateless' ) ) ) {
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 * excluding compatibility.
883 */
884 if($use_root){
885 $current_path = basename($current_path);
886 }
887
888 if(!$use_root){
889 // removing the root dir if already exists in the begaining.
890 return preg_replace($root_dir_regex, '', $current_path);
891 }
892
893 // skip adding root dir if it's already added.
894 if ( !empty( $root_dir ) && !preg_match($root_dir_regex, $current_path) ) {
895 return $root_dir . '/' . trim( $current_path, '/ ' );
896 }
897
898 return $current_path;
899 }
900
901 /**
902 * @param $content
903 * @return mixed
904 */
905 public function the_content_filter( $content ) {
906
907 if ( $upload_data = wp_upload_dir() ) {
908
909 if ( !empty( $upload_data['url'] ) && !empty( $content ) ) {
910 $url = preg_replace('/https?:\/\//','',$upload_data['url']);
911
912 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
913 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
914 $root_dir = !empty( $root_dir ) ? $root_dir . '/' : false;
915 $image_host = $this->get_gs_host();
916 $file_ext = $this->replaceable_file_types();
917 $content = preg_replace( '/(href|src)=(\'|")(https?:\/\/'.str_replace('/', '\/', $url).')\/(.+?)('.$file_ext.')(\'|")/i',
918 '$1=$2'.$image_host.'/'.($root_dir?$root_dir:'').'$4$5$6', $content);
919 }
920 }
921
922 return $content;
923 }
924
925 /**
926 * Return file types supported by File URL Replacement.
927 * @return string
928 */
929 public function replaceable_file_types(){
930 $types = $this->get('sm.body_rewrite_types');
931
932 // Removing extra space.
933 $types = trim($types);
934 $types = preg_replace("/\s{2,}/", ' ', $types);
935
936 $types_arr = explode(' ', $types);
937 return '\.' . implode('|\.', $types_arr);
938 }
939
940 /**
941 * Copied from https://developer.wordpress.org/reference/functions/get_metadata/
942 *
943 * @param $value null unless other filter hooked in this function.
944 * @param $object_id post id
945 * @param $meta_key
946 * @param $single
947 * @return mixed
948 */
949 public function post_metadata_filter($value, $object_id, $meta_key, $single){
950 if(empty($value)){
951 $meta_type = 'post';
952 $transient_key = "stateless_{$meta_type}_meta";
953
954 $meta_cache = wp_cache_get($object_id, $transient_key);
955 if(empty($meta_cache)){
956 $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
957
958 if ( !$meta_cache ) {
959 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
960 $meta_cache = $meta_cache[$object_id];
961 }
962
963 foreach($meta_cache as $key => $meta){
964 $meta_cache[$key] = array_map('maybe_unserialize', $meta_cache[$key]);
965 }
966
967 $meta_cache = $this->convert_to_gs_link($meta_cache);
968 wp_cache_set($object_id, $meta_cache, $transient_key);
969 }
970
971 if ( ! $meta_key ) {
972 return $meta_cache;
973 }
974
975 if ( isset($meta_cache[$meta_key]) ) {
976 return $meta_cache[$meta_key];
977 }
978
979 // in case no metadata is found return what was passed in $value.
980 // $value most of the time is null.
981 return $value;
982 }
983
984 return $this->convert_to_gs_link($value);
985
986 }
987
988 /**
989 * Replace all image link with gs link and return only if meta modified.
990 *
991 * @param $meta
992 * @param $return
993 * @return mixed or null when not changed.
994 */
995 public function convert_to_gs_link($meta, $return = false){
996 $updated = $meta;
997 if ( $meta && $upload_data = wp_upload_dir() ) {
998 if ( !empty( $upload_data['url'] ) && !empty( $meta ) ) {
999 $url = preg_replace('/https?:\/\//','',$upload_data['url']);
1000 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
1001 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
1002 $root_dir = !empty( $root_dir ) ? $root_dir . '/': false;
1003 $image_host = $this->get_gs_host().'/'.($root_dir?$root_dir:'');
1004 $file_ext = $this->replaceable_file_types();
1005 $updated = $this->_convert_to_gs_link($meta, $image_host, $url, $file_ext);
1006 }
1007 }
1008
1009 if($updated == $meta && !$return){
1010 return null; // Not changed.
1011 }
1012 return $updated;
1013 }
1014
1015 /**
1016 * Replace all image link with gs link
1017 *
1018 * @param $meta
1019 * @param $image_host
1020 * @param $url
1021 * @param $file_ext
1022 * @return array|null|string|string[]
1023 */
1024 public function _convert_to_gs_link($meta, $image_host, $url, $file_ext){
1025 if(is_array($meta)){
1026 foreach ($meta as $key => $value) {
1027 $meta[$key] = $this->_convert_to_gs_link($value, $image_host, $url, $file_ext);
1028 }
1029 return $meta;
1030 } elseif (is_object($meta) && $meta instanceof \stdClass ) {
1031 foreach (get_object_vars($meta) as $key => $value) {
1032 $meta->{$key} = $this->_convert_to_gs_link($value, $image_host, $url, $file_ext);
1033 }
1034 return $meta;
1035 } elseif(is_string($meta)){
1036 return preg_replace( '/(https?:\/\/'.str_replace('/', '\/', $url).')\/(.+?)(' . $file_ext . ')/i', $image_host.'$2$3', $meta);
1037 }
1038
1039 return $meta;
1040 }
1041
1042 /**
1043 * @param $links
1044 * @param $file
1045 * @return mixed
1046 */
1047 public function plugin_action_links( $links, $file ) {
1048
1049 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless-media.php' ) ) {
1050 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
1051 array_unshift( $links, $settings_link );
1052 }
1053
1054 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless.php' ) ) {
1055 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
1056 array_unshift( $links, $settings_link );
1057 }
1058
1059 return $links;
1060 }
1061
1062 /**
1063 * Determines if plugin is loaded via mu-plugins
1064 * or Network Enabled.
1065 *
1066 * @param bool $is_multisite
1067 * @return bool
1068 * @author peshkov@UD
1069 */
1070 public function is_network_detected($is_multisite = false) {
1071 /* Plugin is loaded via mu-plugins. */
1072
1073 if( strpos( Utility::normalize_path( $this->root_path ), Utility::normalize_path( WPMU_PLUGIN_DIR ) ) !== false ) {
1074 return true;
1075 }
1076
1077 if( is_multisite() ) {
1078 if($is_multisite) return true;
1079 /* Looks through network enabled plugins to see if our one is there. */
1080 foreach (wp_get_active_network_plugins() as $path) {
1081 // Trying again using readlink in case it's a symlink file.
1082 // boot_file is already solved.
1083 // wp_normalize_path is helpfull in windows.
1084 if (wp_normalize_path($this->boot_file) == wp_normalize_path($path) || (is_link($path) AND $this->boot_file == readlink($path))) {
1085 return true;
1086 }
1087 }
1088 }
1089 return false;
1090 }
1091
1092 /**
1093 * Initialization.
1094 * Register scripts and styles
1095 */
1096 public function admin_init() {
1097 $this->show_notice_stateless_cache_busting();
1098 wp_register_style( 'wp-stateless', $this->path( 'static/styles/wp-stateless.css', 'url' ), array(), self::$version );
1099
1100 /**
1101 * select2 styles
1102 */
1103 wp_register_style( 'wp-stateless-select2', $this->path( 'static/styles/select2.min.css', 'url' ), array(), self::$version );
1104
1105 /* Attachment or upload page */
1106 wp_register_script( 'wp-stateless-uploads-js', $this->path( 'static/scripts/wp-stateless-uploads.js', 'url' ), array( 'jquery' ), self::$version );
1107
1108 /* Setup wizard styles. */
1109 wp_register_style( 'wp-stateless-setup-wizard', $this->path( 'static/styles/wp-stateless-setup-wizard.css', 'url' ), array(), self::$version );
1110
1111 wp_register_script( 'wp-stateless-select2', ud_get_stateless_media()->path( 'static/scripts/select2.min.js', 'url' ), array( 'jquery' ), self::$version, true );
1112
1113 /* Stateless settings page */
1114 wp_register_script( 'wp-stateless-settings', ud_get_stateless_media()->path( 'static/scripts/wp-stateless-settings.js', 'url' ), array(), self::$version );
1115 wp_localize_script( 'wp-stateless-settings', 'stateless_l10n', $this->get_l10n_data() );
1116
1117 wp_register_style( 'wp-stateless-settings', $this->path( 'static/styles/wp-stateless-settings.css', 'url' ), array(), self::$version );
1118
1119 // Sync tab
1120 if ( wp_script_is( 'jquery-ui-widget', 'registered' ) ){
1121 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' );
1122 }
1123 else{
1124 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' );
1125 }
1126 wp_register_script( 'wp-stateless-angular', ud_get_stateless_media()->path( 'static/scripts/angular.min.js', 'url' ), array(), '1.5.0', true );
1127 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 );
1128 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 );
1129
1130 wp_localize_script( 'wp-stateless', 'stateless_l10n', $this->get_l10n_data() );
1131 wp_localize_script('wp-stateless', 'wp_stateless_configs', array(
1132 'WP_DEBUG' => defined('WP_DEBUG') ? WP_DEBUG : false,
1133 ));
1134
1135 $settings = ud_get_stateless_media()->get('sm');
1136 $settings['wildcards'] = $this->settings->wildcards;
1137 $settings['network_admin'] = is_network_admin();
1138 $settings['is_multisite'] = is_multisite();
1139 if(defined('WP_STATELESS_MEDIA_JSON_KEY') && WP_STATELESS_MEDIA_JSON_KEY){
1140 $settings['key_json'] = "Currently configured via a constant.";
1141 }
1142 wp_localize_script('wp-stateless', 'wp_stateless_settings', $settings);
1143 wp_localize_script('wp-stateless', 'wp_stateless_compatibility', Module::get_modules());
1144 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' );
1145
1146 }
1147
1148 /**
1149 * Get_l10n_data
1150 *
1151 * @param string $value
1152 * @return mixed
1153 */
1154 public function get_l10n_data($value=''){
1155 include ud_get_stateless_media()->path( 'l10n.php', 'dir');
1156 return $l10n;
1157 }
1158
1159 /**
1160 * Admin Scripts
1161 *
1162 * @param $hook
1163 */
1164 public function admin_enqueue_scripts( $hook ) {
1165
1166 switch( $hook ) {
1167
1168 case 'options-media.php':
1169 wp_enqueue_style( 'wp-stateless');
1170 break;
1171
1172 case 'upload.php':
1173 wp_enqueue_style( 'wp-stateless');
1174 wp_enqueue_script( 'wp-stateless-uploads-js' );
1175
1176 break;
1177
1178 case 'post.php':
1179
1180 global $post;
1181
1182 if ( $post->post_type == 'attachment' ) {
1183 wp_enqueue_style( 'wp-stateless');
1184 wp_enqueue_script( 'wp-stateless-uploads-js' );
1185 }
1186
1187 break;
1188
1189 case 'media_page_stateless-setup':
1190 case 'settings_page_stateless-setup':
1191 wp_enqueue_style( 'wp-stateless');
1192 wp_enqueue_style( 'wp-stateless-setup-wizard' );
1193 break;
1194 case 'media_page_stateless-settings':
1195 case 'settings_page_stateless-settings':
1196 wp_enqueue_style( 'wp-stateless');
1197 wp_enqueue_style( 'wp-stateless-select2');
1198 wp_enqueue_script( 'wp-stateless-settings' );
1199 wp_enqueue_script( 'wp-stateless-select2' );
1200 wp_enqueue_style( 'wp-stateless-settings' );
1201
1202 // Sync tab
1203 wp_enqueue_script( 'jquery-ui-progressbar' );
1204 wp_enqueue_script( 'wp-stateless-angular' );
1205 wp_enqueue_script( 'wp-stateless-angular-sanitize' );
1206 wp_enqueue_script( 'wp-stateless' );
1207 wp_enqueue_style( 'jquery-ui-regenthumbs' );
1208
1209 $data = array(
1210 'key' => 'stateless-cache-busting',
1211 'class' => 'notice',
1212 'title' => sprintf( __( "Stateless and Ephemeral modes enables and requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1213 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1214 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.
1215 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" ),
1216 );
1217 echo "<script id='template-stateless-cache-busting' type='text/html'>";
1218 include ud_get_stateless_media()->path( '/static/views/error-notice.php', 'dir' );
1219 echo "</script>";
1220
1221 break;
1222 default: break;
1223 }
1224
1225 }
1226
1227 /**
1228 * Add Attributes to media HTML
1229 *
1230 * @author potanin@UD
1231 * @param $attr
1232 * @param $attachment
1233 * @param $size
1234 * @return mixed
1235 */
1236 public function wp_get_attachment_image_attributes( $attr, $attachment, $size = null ) {
1237
1238 $sm_cloud = get_post_meta( $attachment->ID, 'sm_cloud', true );
1239 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'name' ] ) ) {
1240 $attr[ 'class' ] = $attr[ 'class' ] . ' wp-stateless-item';
1241 $attr[ 'data-image-size' ] = is_array( $size ) ? implode( 'x', $size ) : $size;
1242 $attr[ 'data-stateless-media-bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
1243 $attr[ 'data-stateless-media-name' ] = $sm_cloud[ 'name' ];
1244 }
1245
1246 return $attr;
1247
1248 }
1249
1250 /**
1251 * Adds filter link to Media Library table.
1252 *
1253 * @param $views
1254 * @return mixed
1255 */
1256 public function views_upload( $views ) {
1257 $views['stateless'] = '<a href="#">' . __( 'Stateless Media' ) . '</a>';
1258 return $views;
1259 }
1260
1261 /**
1262 * Replace media URL
1263 *
1264 * @param bool $false
1265 * @param integer $id
1266 * @param string $size
1267 * @return mixed $false
1268 */
1269 public function image_downsize( $false = false, $id, $size ) {
1270
1271 if ( (!isset( $this->client ) || !$this->client || is_wp_error( $this->client )) && $this->get( 'sm.mode' ) !== 'stateless' ) {
1272 return $false;
1273 }
1274
1275 /**
1276 * Check if enabled
1277 */
1278 if ( !in_array( $this->get( 'sm.mode' ), array( 'cdn', 'stateless', 'ephemeral' ) )) {
1279 return $false;
1280 }
1281
1282 /** Start determine remote file */
1283 $img_url = wp_get_attachment_url($id);
1284 $meta = wp_get_attachment_metadata($id);
1285 $width = $height = 0;
1286 $is_intermediate = false;
1287
1288 //** try for a new style intermediate size */
1289 if ( $intermediate = image_get_intermediate_size( $id, $size ) ) {
1290 if(!empty( $intermediate['gs_link'] )){
1291 $img_url = $intermediate['gs_link'];
1292 }
1293 else if(!empty( $intermediate['url'] )){
1294 $img_url = $intermediate['url'];
1295 }
1296 else{
1297 $img_url = dirname($img_url) . $intermediate['file'];
1298 }
1299
1300 $width = $intermediate['width'];
1301 $height = $intermediate['height'];
1302 $is_intermediate = true;
1303 }
1304
1305 /**
1306 * maybe try to get images info from sm_cloud
1307 * this case may happen when no local files
1308 * @author korotkov@ud
1309 */
1310 if ( !$width && !$height ) {
1311 $sm_cloud = get_post_meta( $id, 'sm_cloud', true );
1312 if ( is_string($size) && !empty( $sm_cloud['sizes'] ) && !empty( $sm_cloud['sizes'][$size] ) ) {
1313 global $_wp_additional_image_sizes;
1314
1315 $img_url = !empty( $sm_cloud['sizes'][$size]['fileLink'] ) ? $sm_cloud['sizes'][$size]['fileLink'] : $img_url;
1316
1317 if ( !empty( $_wp_additional_image_sizes[ $size ] ) ) {
1318 $width = !empty( $_wp_additional_image_sizes[ $size ]['width'] ) ? $_wp_additional_image_sizes[ $size ]['width'] : $width;
1319 $height = !empty( $_wp_additional_image_sizes[ $size ]['height'] ) ? $_wp_additional_image_sizes[ $size ]['height'] : $height;
1320 }
1321
1322 $is_intermediate = true;
1323 }
1324 }
1325
1326 if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
1327
1328 //** any other type: use the real image */
1329 $width = $meta['width'];
1330 $height = $meta['height'];
1331 }
1332
1333
1334 if ( $img_url) {
1335
1336 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
1337 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
1338 $img_url = apply_filters('wp_stateless_bucket_link', $img_url);
1339 return array( $img_url, $width, $height, $is_intermediate );
1340 }
1341
1342
1343 /**
1344 * All other cases work as usually
1345 */
1346 return $false;
1347
1348 }
1349
1350 /**
1351 * Extends metadata by adding GS information.
1352 * Note: must not be called directly. It's used only on hook
1353 *
1354 * @param $metadata
1355 * @param $attachment_id
1356 * @return array|mixed
1357 */
1358 public function wp_get_attachment_metadata( $metadata, $attachment_id ) {
1359 global $default_dir;
1360 $default_dir = false;
1361 /* Determine if the media file has GS data at all. */
1362 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
1363 // If metadata not passed the get metadata from post meta.
1364 if(empty($metadata)){
1365 $metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
1366 }
1367
1368 if(empty($metadata)){
1369 $metadata = [];
1370 }
1371
1372 if( is_array( $metadata ) && is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1373 $metadata[ 'gs_link' ] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'fileLink' ]);
1374 $metadata[ 'gs_name' ] = isset( $sm_cloud[ 'name' ] ) ? $sm_cloud[ 'name' ] : false;
1375 $metadata[ 'gs_bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
1376 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
1377 foreach( $metadata[ 'sizes' ] as $k => $v ) {
1378 if( !empty( $sm_cloud[ 'sizes' ][ $k ][ 'name' ] ) ) {
1379 $metadata['sizes'][$k]['gs_name'] = $sm_cloud[ 'sizes' ][ $k ][ 'name' ];
1380 $metadata['sizes'][$k]['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'sizes' ][ $k ][ 'fileLink' ]);
1381 }
1382 }
1383 }
1384 }
1385 if(is_multisite() && !empty($metadata['file'])){
1386 if ( $this->get('sm.mode') == 'stateless' ) {
1387 $default_dir = true;
1388 $uploads = wp_get_upload_dir();
1389 $default_dir = false;
1390
1391 $file_path_fix = $uploads['basedir'] . "/{$metadata['file']}";
1392 if(file_exists($file_path_fix)) {
1393 $metadata[ 'file' ] = "{$metadata['file']}";
1394 }
1395 } else {
1396 $uploads = wp_get_upload_dir();
1397 $blog_id = get_current_blog_id();
1398 $file_path_fix = $uploads[ 'basedir' ] . "/sites/$blog_id/{$metadata['file']}";
1399 if( file_exists( $file_path_fix ) ) {
1400 $metadata[ 'file' ] = "sites/$blog_id/{$metadata['file']}";
1401 }
1402 }
1403 } elseif ( empty($sm_cloud) && $this->get('sm.mode') == 'stateless' ) {
1404 $default_dir = true;
1405 $uploads = wp_get_upload_dir();
1406 $default_dir = false;
1407
1408 $file_path_fix = $uploads['basedir'] . "/{$metadata['file']}";
1409 if(file_exists($file_path_fix)) {
1410 $metadata[ 'file' ] = "{$metadata['file']}";
1411 }
1412 }
1413
1414 return $metadata;
1415 }
1416
1417 /**
1418 *
1419 * @param $file
1420 * @param $attachment_id
1421 * @return string
1422 */
1423 public function get_attached_file($file, $attachment_id){
1424 global $default_dir;
1425 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', 1 );
1426 /* Determine if the media file has GS data at all. */
1427 if(is_multisite()){
1428 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
1429 $_file = get_post_meta( $attachment_id, '_wp_attached_file', true );
1430 if($_file){
1431 $blog_id = get_current_blog_id();
1432 $uploads = wp_get_upload_dir();
1433 $_file = apply_filters( 'wp_stateless_file_name', $_file, false);
1434 $file_path_fix = $uploads['basedir'] . "/sites/$blog_id/$_file";
1435 if(file_exists($file_path_fix)){
1436 $file = $file_path_fix;
1437 }
1438 }
1439 } elseif ( empty($sm_cloud) && $this->get('sm.mode') == 'stateless' ) {
1440 $_file = get_post_meta( $attachment_id, '_wp_attached_file', true );
1441 if( $_file ) {
1442 $default_dir = true;
1443 $uploads = wp_get_upload_dir();
1444 $default_dir = false;
1445 return $uploads[ 'basedir' ] . '/' . $_file;
1446 }
1447 }
1448 return $file;
1449 }
1450
1451 /**
1452 * Returns client object
1453 * or WP_Error on failure.
1454 *
1455 * @author peshkov@UD
1456 * @return object $this->client. \wpCloud\StatelessMedia\GS_Client or \WP_Error
1457 */
1458 public function get_client() {
1459
1460 if( null === $this->client ) {
1461
1462 $key_json = $this->get( 'sm.key_json' );
1463 if ( empty($key_json) ) {
1464 $key_json = get_site_option( 'sm_key_json' );
1465 }
1466
1467 /* Try to initialize GS Client */
1468 $this->client = GS_Client::get_instance( array(
1469 'bucket' => $this->get( 'sm.bucket' ),
1470 'key_json' => $key_json
1471 ) );
1472 }
1473
1474 return $this->client;
1475
1476 }
1477
1478 /**
1479 * Determines if we can connect to Google Storage Bucket.
1480 *
1481 * @author peshkov@UD
1482 */
1483 public function is_connected_to_gs() {
1484
1485 $trnst = get_transient( 'sm::is_connected_to_gs' );
1486
1487 if ( empty($trnst) || false === $trnst || !isset( $trnst[ 'hash' ] ) || $trnst[ 'hash' ] != md5( serialize( $this->get( 'sm' ) ) ) ) {
1488 $trnst = array(
1489 'success' => 'true',
1490 'error' => '',
1491 'hash' => md5( serialize( $this->get( 'sm' ) ) ),
1492 );
1493 $client = $this->get_client();
1494
1495 if ( is_wp_error( $client ) ) {
1496 $trnst[ 'success' ] = 'false';
1497 $trnst[ 'error' ] = $client->get_error_message();
1498 } else {
1499 $connected = $client->is_connected();
1500 if( $connected !== true ) {
1501 $trnst[ 'success' ] = 'false';
1502 $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' ) );
1503
1504 if( is_callable(array($connected, 'getHandlerContext')) && $handlerContext = $connected->getHandlerContext() ){
1505 if(!empty($handlerContext['error'])){
1506 $handlerContext['error'];
1507 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($handlerContext['error']);
1508 }
1509 }
1510
1511 if( is_callable(array($connected, 'getErrors')) && $error = $connected->getErrors() ){
1512 $error = reset($error);
1513 if($error['reason'] == 'accessNotConfigured')
1514 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($error['message']);
1515 }
1516 }
1517 }
1518 set_transient( 'sm::is_connected_to_gs', $trnst, 4 * HOUR_IN_SECONDS );
1519 }
1520
1521 if( isset( $trnst[ 'success' ] ) && $trnst[ 'success' ] == 'false' ) {
1522 return new \WP_Error( 'error', ( !empty( $trnst[ 'error' ] ) ? $trnst[ 'error' ] : __( 'There is an Error on connection to Google Storage.', $this->domain ) ) );
1523 }
1524
1525 return true;
1526 }
1527
1528 /**
1529 * Flush all plugin transients
1530 *
1531 */
1532 public function flush_transients() {
1533 delete_transient( 'sm::is_connected_to_gs' );
1534 }
1535
1536 /**
1537 * Plugin Activation
1538 *
1539 */
1540 public function activate() {
1541 add_action( 'activated_plugin', array($this, 'redirect_to_splash'), 99 );
1542 $this->run_upgrade_process();
1543 }
1544
1545 /**
1546 * Run Install Process.
1547 * Triggered on plugins_loaded instead of register_activation_hook action.
1548 * Works on even manual plugin update.
1549 *
1550 * @author alim@UD
1551 */
1552 public function run_install_process()
1553 {
1554 // calling the upgrade function because it's same as this point for fresh install or updates.
1555 $this->run_upgrade_process();
1556 }
1557
1558 /**
1559 * Run Upgrade Process:
1560 * Triggered on plugins_loaded instead of register_activation_hook action.
1561 * Works on even manual plugin update.
1562 *
1563 * @author alim@UD
1564 */
1565 public function run_upgrade_process()
1566 {
1567 // Creating database on new installation.
1568 $this->create_db();
1569 /**
1570 * Maybe Upgrade current Version
1571 */
1572 Upgrader::call( $this->args[ 'version' ] );
1573 }
1574
1575 /**
1576 * Create database on plugin activation.
1577 * @param boolean $force - whether to create db even if option exists. For debug purpose only.
1578 */
1579 public function create_db($force = false) {
1580 global $wpdb;
1581 $sm_sync_db_version = get_option( 'sm_sync_db_version' );
1582
1583 if( $sm_sync_db_version && $force == false ) {
1584 return;
1585 }
1586
1587 $table_name = $wpdb->prefix . 'sm_sync';
1588 $charset_collate = $wpdb->get_charset_collate();
1589
1590 // `expire` timestamp NULL DEFAULT NULL,
1591 $sql = "CREATE TABLE $table_name (
1592 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT ,
1593 `file` varchar(255) NOT NULL ,
1594 `status` varchar(10) NOT NULL ,
1595 PRIMARY KEY (`id`)
1596 ) $charset_collate;";
1597
1598 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
1599 dbDelta( $sql );
1600
1601 add_option( 'sm_sync_db_version', $this->args[ 'version' ] );
1602 }
1603
1604 /**
1605 * Delete table when blog is deleted.
1606 *
1607 * @param $old_site
1608 */
1609 public function wp_delete_site($old_site){
1610 global $wpdb;
1611
1612 switch_to_blog( $old_site->id );
1613 $table_name = $wpdb->prefix . 'sm_sync';
1614
1615 $sql = "DROP TABLE IF EXISTS $table_name";
1616 $wpdb->query($sql);
1617 restore_current_blog();
1618 }
1619
1620 /**
1621 * Redirect_to_splash
1622 *
1623 * @param string $plugin
1624 */
1625 public function redirect_to_splash($plugin =''){
1626 // $this->settings = new Settings();
1627
1628 if(defined( 'WP_CLI' ) || $this->settings->get('sm.key_json') || isset($_POST['checked']) && count($_POST['checked']) > 1){
1629 return;
1630 }
1631
1632 if(
1633 !$this->settings->get('sm.key_json') &&
1634 defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true &&
1635 defined('WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL') && WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL == true
1636 ) {
1637 return;
1638 }
1639
1640 if( !$this->settings->get('sm.key_json') && defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true ) {
1641 $url = $this->get_settings_page_url('?page=stateless-settings');
1642 exit( wp_redirect($url));
1643 }
1644
1645 if( $plugin == plugin_basename( $this->boot_file ) ) {
1646 $url = $this->get_settings_page_url('?page=stateless-setup&step=splash-screen');
1647 if(json_decode($this->settings->get('sm.key_json'))){
1648 $url = $this->get_settings_page_url('?page=stateless-settings');
1649 }
1650 exit( wp_redirect($url));
1651 }
1652 }
1653
1654 /**
1655 * Plugin Deactivation
1656 *
1657 */
1658 public function deactivate() {}
1659
1660 /**
1661 * Show_notice_stateless_cache_busting
1662 *
1663 */
1664 public function show_notice_stateless_cache_busting(){
1665 $this->errors->add( array(
1666 'key' => 'stateless_cache_busting',
1667 'button' => 'View Settings',
1668 'button_link' => admin_url('upload.php?page=stateless-settings'),
1669 'title' => sprintf( __( "Stateless mode now requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1670 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1671 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.
1672 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" ),
1673 ), 'notice' );
1674 }
1675
1676 /**
1677 * Filter for wp_get_attachment_url();
1678 * @param string $url
1679 * @param string $post_id
1680 * @return mixed|null|string
1681 */
1682 public function wp_get_attachment_url( $url = '', $post_id = '' ) {
1683 global $default_dir;
1684 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
1685 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1686 $_url = parse_url($sm_cloud[ 'fileLink' ]);
1687 $url = !isset($_url['scheme']) ? ( 'https:' . $sm_cloud[ 'fileLink' ] ) : $sm_cloud[ 'fileLink' ];
1688 return apply_filters('wp_stateless_bucket_link', $url);
1689 }
1690 elseif(is_multisite() && empty($sm_cloud)){
1691 $_file = get_post_meta( $post_id, '_wp_attached_file', true );
1692 if($_file){
1693 if ( $this->get('sm.mode') == 'stateless' ) {
1694 $default_dir = true;
1695 $uploads = wp_get_upload_dir();
1696 $default_dir = false;
1697 return $uploads['baseurl'].'/'.$_file;
1698 } else {
1699 $uploads = wp_get_upload_dir();
1700 $default_dir = false;
1701 $_file = apply_filters( 'wp_stateless_file_name', $_file, false );
1702 $blog_id = get_current_blog_id();
1703 $file_path_fix = $uploads[ 'basedir' ] . "/sites/$blog_id/$_file";
1704
1705 if( file_exists( $file_path_fix ) ) {
1706 $url = $uploads[ 'baseurl' ] . "/sites/$blog_id/$_file";
1707 }
1708 }
1709 }
1710 } elseif ( empty($sm_cloud) && $this->get('sm.mode') == 'stateless' ) {
1711 $_file = get_post_meta( $post_id, '_wp_attached_file', true );
1712 if($_file){
1713 $default_dir = true;
1714 $uploads = wp_get_upload_dir();
1715 $default_dir = false;
1716
1717 return $uploads['baseurl'].'/'.$_file;
1718 }
1719 }
1720 return $url;
1721 }
1722
1723 /**
1724 * Filter for attachment_url_to_postid()
1725 *
1726 * @param int|false $post_id originally found post ID (or false if not found)
1727 * @param string $url the URL to find the post ID for
1728 * @return int|false found post ID from cloud storage URL
1729 */
1730 public function attachment_url_to_postid( $post_id, $url ) {
1731 global $wpdb;
1732
1733 if ( ! $post_id ) {
1734 $post_id = get_transient("stateless_url_to_postid_" . md5($url));
1735
1736 if(defined('WP_STATELESS_LEGACY_URL_TO_POSTID')){
1737 // User can use this constant if they change the Bucket Folder (root_dir) after uploading image.
1738 // This can be little slow at first run.
1739 if(empty($post_id)){
1740 $query = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'sm_cloud' AND meta_value LIKE '%s'";
1741 $post_id = $wpdb->get_var( $wpdb->prepare( $query, '%' . $url . '%' ) );
1742
1743 if($post_id){
1744 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1745 }
1746 }
1747 return $post_id;
1748 }
1749
1750 if(empty($post_id)){
1751 $gs_base_url = $this->get_gs_host();
1752 $root_dir = $this->get( 'sm.root_dir' );
1753 $path_elements = apply_filters( 'wp_stateless_unhandle_root_dir', $url );
1754 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir, false, $path_elements);
1755 $gs_url = $this->get_gs_host() . '/' . $root_dir;
1756 $site_url = parse_url($gs_url);
1757 $image_path = parse_url( $url );
1758
1759 //force the protocols to match if needed
1760 if( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
1761 $url = str_replace( $image_path['scheme'], $site_url['scheme'], $url );
1762 }
1763
1764 if( 0 === strpos( $url, $gs_url . '/' ) ) {
1765 $url = substr( $url, strlen( $gs_url . '/' ) );
1766 }
1767 else if( 0 === strpos( $url, $gs_base_url . '/' ) ) {
1768 // In case user added Bucket Folder (root_dir) after uploading image.
1769 $url = substr( $url, strlen( $gs_base_url . '/' ) );
1770 }
1771
1772 /**
1773 * If `uploads_use_yearmonth_folders` is set - adding year and month to url
1774 */
1775 $organize_media = get_option('uploads_use_yearmonth_folders');
1776 $path = '';
1777 if ( $organize_media == '1' && isset ($path_elements['%date_year/date_month%']) ) {
1778 $path .= $path_elements['%date_year/date_month%'].'/';
1779 }
1780 $url = $path . $url;
1781
1782 $sql = $wpdb->prepare(
1783 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
1784 $url
1785 );
1786 $post_id = $wpdb->get_var( $sql );
1787
1788 if($post_id){
1789 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1790 }
1791 }
1792 }
1793
1794 return $post_id;
1795 }
1796
1797 /**
1798 * Change Upload BaseURL when CDN Used.
1799 *
1800 * @param $data
1801 * @return mixed
1802 */
1803 public function upload_dir( $data ) {
1804 $data[ 'basedir' ] = $this->get_gs_host();
1805 $data[ 'baseurl' ] = $this->get_gs_host();
1806 $data[ 'url' ] = $data[ 'baseurl' ] . $data[ 'subdir' ];
1807
1808 return $data;
1809
1810 }
1811
1812 /**
1813 * Set Feature Flag constants by parsing composer.json
1814 *
1815 * @todo Make sure settings from DB can override these.
1816 *
1817 * @author potanin@UD
1818 * @return array|mixed|null|object
1819 */
1820 public function parse_feature_flags( ) {
1821
1822 try {
1823
1824 $_raw = file_get_contents( Utility::normalize_path( $this->root_path ) . 'composer.json' );
1825
1826 $_parsed = json_decode( $_raw );
1827
1828 // @todo Catch poorly formatted JSON.
1829 if( !is_object( $_parsed ) ) {
1830 // throw new Error( "unable to parse." );
1831 }
1832
1833 foreach( (array) $_parsed->extra->featureFlags as $_feature ) {
1834
1835 if( !defined( $_feature->constant ) ) {
1836 define( $_feature->constant, $_feature->enabled );
1837
1838 if( $_feature->enabled ) {
1839 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] enabled.' );
1840 } else {
1841 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] disabled.' );
1842 }
1843 }
1844
1845 }
1846
1847 } catch ( \Exception $e ) {
1848 Utility::log( 'Unable to parse [composer.json] feature flags. Error: [' . $e->getMessage() . ']' );
1849 // echo 'Caught exception: ', $e->getMessage(), "\n";
1850 }
1851
1852 return isset( $_parsed ) ? $_parsed : null;
1853
1854 }
1855
1856 /**
1857 * Determine if Utility class contains missed function
1858 * in other case, just return NULL to prevent ERRORS
1859 *
1860 * @author peshkov@UD
1861 * @param $name
1862 * @param $arguments
1863 * @return mixed|null
1864 */
1865 public function __call( $name, $arguments ) {
1866 if( is_callable( array( "wpCloud\\StatelessMedia\\Utility", $name ) ) ) {
1867 return call_user_func_array( array( "wpCloud\\StatelessMedia\\Utility", $name ), $arguments );
1868 } else {
1869 return NULL;
1870 }
1871 }
1872
1873 /**
1874 * Get all thumbnail sizes
1875 *
1876 * @global $_wp_additional_image_sizes
1877 * @uses get_intermediate_image_sizes()
1878 *
1879 * @param boolean [$unset_disabled = true]
1880 * @return array
1881 */
1882 function get_image_sizes( $unset_disabled = true ) {
1883 $wais = & $GLOBALS['_wp_additional_image_sizes'];
1884
1885 $sizes = array();
1886
1887 foreach ( get_intermediate_image_sizes() as $_size ) {
1888 if ( in_array( $_size, array('thumbnail', 'medium', 'medium_large', 'large') ) ) {
1889 $sizes[ $_size ] = array(
1890 'width' => get_option( "{$_size}_size_w" ),
1891 'height' => get_option( "{$_size}_size_h" ),
1892 'crop' => (bool) get_option( "{$_size}_crop" ),
1893 );
1894 }
1895 elseif ( isset( $wais[$_size] ) ) {
1896 $sizes[ $_size ] = array(
1897 'width' => $wais[ $_size ]['width'],
1898 'height' => $wais[ $_size ]['height'],
1899 'crop' => $wais[ $_size ]['crop'],
1900 );
1901 }
1902
1903 // size registered, but has 0 width and height
1904 if( $unset_disabled && ($sizes[ $_size ]['width'] == 0) && ($sizes[ $_size ]['height'] == 0) )
1905 unset( $sizes[ $_size ] );
1906 }
1907
1908 return $sizes;
1909 }
1910 }
1911 }
1912 }
1913