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

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