PluginProbe
WP-Stateless – Google Cloud Storage / 3.0
WP-Stateless – Google Cloud Storage v3.0
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / lib / classes / class-bootstrap.php

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

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