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

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