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

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

1,952 lines 70.8 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 * Delete original in the end of generating metadata
318 * it does not work with PDF
319 */
320 // add_filter('wp_generate_attachment_metadata', function ($metadata, $attachment_id, $state) {
321 // if (!in_array(ud_get_stateless_media()->get('sm.mode'), array('ephemeral'))) return $metadata;
322 // @unlink(get_attached_file($attachment_id, true));
323 // return $metadata;
324 // }, 99, 3);
325
326 /**
327 * Rewrite Image URLS
328 */
329 add_filter('image_downsize', array($this, 'image_downsize'), 99, 3);
330 add_filter('wp_calculate_image_srcset', array($this, 'wp_calculate_image_srcset'), 10, 5);
331
332 /**
333 * Trigger module initialization and registration.
334 */
335 do_action('sm::module::init', $this->get('sm'));
336
337 break;
338 case 'default':
339 break;
340 }
341 }
342
343 /**
344 * The default $editors value:
345 *
346 * array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' )
347 * @param $editors
348 * @return array
349 */
350 public function select_wp_image_editors($editors) {
351 return array('WP_Image_Editor_GD');
352 }
353
354 /**
355 * @param callable|null $httpHandler
356 * @return StorageClient
357 * @throws \Exception
358 */
359 public function init_gs_client(callable $httpHandler = null) {
360 // May be Loading Google SDK....
361 if (!class_exists('HttpHandlerFactory')) {
362 include_once(ud_get_stateless_media()->path('lib/Google/vendor/autoload.php', 'dir'));
363 }
364
365 $httpHandler = $httpHandler ? $httpHandler : HttpHandlerFactory::build();
366
367 $json_key = json_decode($this->settings->get('sm.key_json'), true);
368
369 if (!empty($json_key)) {
370 return new StorageClient(
371 [
372 'keyFile' => $json_key,
373 'httpHandler' => function ($request, $options) use ($httpHandler) {
374 $xGoogApiClientHeader = $request->getHeaderLine('x-goog-api-client');
375 $request = $request->withHeader('x-goog-api-client', $xGoogApiClientHeader);
376
377 return call_user_func_array($httpHandler, [$request, $options]);
378 },
379 'authHttpHandler' => HttpHandlerFactory::build(),
380 ]
381 );
382 }
383 }
384
385 /**
386 * Replacing root dir with GCS path
387 * @param $uploads
388 * @return array
389 */
390 public function filter_upload_dir($uploads) {
391 global $default_dir;
392 if ($default_dir) return $uploads;
393 //Bucket
394 $bucket = $this->get('sm.bucket');
395
396 //Bucket folder path
397 $root_dir = $this->get('sm.root_dir');
398 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
399
400 /**
401 * Subdir not uses on Stateless mode
402 */
403 $uploads['subdir'] = '';
404
405 $basedir = rtrim(sprintf('gs://%s/%s', $bucket, $root_dir), '/');
406 $baseurl = rtrim(sprintf('https://storage.googleapis.com/%s/%s', $bucket, $root_dir), '/');
407
408 $uploads = array(
409 'url' => rtrim($baseurl . $uploads['subdir'], '/'),
410 'path' => $basedir . $uploads['subdir'],
411 'subdir' => $uploads['subdir'],
412 'basedir' => $basedir,
413 'baseurl' => $baseurl,
414 'error' => false,
415 );
416 return $uploads;
417 }
418
419 /**
420 * Rebuild srcset from gs_link.
421 * Using calculations returned from WordPress wp_calculate_image_srcset()
422 *
423 * @param $sources
424 * @param $size_array
425 * @param $image_src
426 * @param $image_meta
427 * @param $attachment_id
428 * @return array
429 */
430 public function wp_calculate_image_srcset($sources, $size_array, $image_src, $image_meta, $attachment_id) {
431 $sm_mode = $this->get('sm.mode');
432
433 /**
434 * In Backup mode using local URL
435 */
436 if ("backup" == $sm_mode) {
437 return $sources;
438 }
439
440 if (empty($image_meta['gs_link'])) {
441 $image_meta = wp_get_attachment_metadata($attachment_id);
442 }
443
444 if (is_array($sources) && !empty($image_meta['gs_link'])) {
445 $gs_name = $image_meta['gs_name'];
446 // getting position of root_dir in gs_name.
447 $root_dir_pos = strpos($gs_name, $image_meta['file']);
448 // removing rood_dir from gs_name so we can compare to replace url with gs_link.
449 if ($root_dir_pos !== false) {
450 $gs_name = substr($gs_name, $root_dir_pos);
451 }
452
453 if (!isset($gs_name) || empty($gs_name)) {
454 return [];
455 }
456
457 foreach ($sources as $width => &$image) {
458
459 // If srcset includes original image src, replace it
460 if (substr_compare($image['url'], $gs_name, -strlen($gs_name)) === 0) {
461 $image['url'] = $image_meta['gs_link'];
462 // Replace all sizes
463 } elseif (isset($image_meta['sizes']) && is_array($image_meta['sizes'])) {
464 $found = false;
465 foreach ($image_meta['sizes'] as $key => $meta) {
466 if (!isset($meta['gs_name']) || empty($meta['gs_name'])) {
467 continue;
468 }
469
470 $thumb_gs_name = $meta['gs_name'];
471 // removing rood_dir from gs_name
472 if ($root_dir_pos !== false) {
473 $thumb_gs_name = substr($thumb_gs_name, $root_dir_pos);
474 }
475
476 if (substr_compare($image['url'], $thumb_gs_name, -strlen($thumb_gs_name)) === 0) {
477 $image['url'] = $meta['gs_link'];
478 $found = true;
479 break;
480 }
481 }
482
483 // if no size found and mode is ephemeral or stateless and nothing to show for srcset item - unset that item
484 if (!$found && ($sm_mode === 'ephemeral' || $sm_mode === 'stateless')) {
485 $image = null;
486 }
487 } else {
488 // if mode is stateless and nothing to show for srcset item - unset that item
489 if ($sm_mode === 'ephemeral' || $sm_mode === 'stateless') {
490 $image = null;
491 }
492 }
493 }
494 } elseif (is_array($sources) && $sm_mode === 'stateless') {
495 foreach ($sources as $width => &$image) {
496 // Set default src
497 $image['url'] = $image_src;
498 }
499 }
500
501 return array_filter($sources);
502 }
503
504 /**
505 * Return gs host.
506 * If custom domain is set it's return bucket name as host,
507 * else return storage.googleapis.com as host and append bucket name at the end.
508 *
509 * @param array $sm
510 * @return mixed|void
511 */
512 public function get_gs_host($sm = array()) {
513 $sm = $sm ? $sm : $this->get('sm');
514 $image_host = 'https://storage.googleapis.com/';
515 $image_host .= $sm['bucket'];
516
517 $custom_domain = $sm['custom_domain'];
518 $is_ssl = strpos($custom_domain, 'https://');
519 $custom_domain = str_replace(array('http://', 'https://'), '', $custom_domain);
520 $custom_domain = trim($custom_domain, '/');
521
522 // checking whether the provided domain is valid.
523 // if the custom domain is same as the bucket name
524 // or the custom domain is using https.
525 if (!empty($sm['bucket']) && !empty($custom_domain) && $custom_domain !== 'storage.googleapis.com' && ($is_ssl === 0 || $custom_domain == $sm['bucket'])) {
526 $image_host = $is_ssl === 0 ? 'https://' : 'http://'; // bucketname will be host
527 $image_host .= $custom_domain;
528 }
529
530 return apply_filters('get_gs_host', $image_host, $image_host, $sm['bucket'], $is_ssl, $sm);
531 }
532
533 /**
534 * Filter for wp_stateless_bucket_link if custom domain is set.
535 * It's get attachment url and remove "storage.googleapis.com" from url.
536 * So that custom url can be used.
537 *
538 * @param $fileLink
539 * @return mixed|string
540 */
541 public function wp_stateless_bucket_link($fileLink) {
542 $bucketname = $this->get('sm.bucket');
543 $custom_domain = $this->get('sm.custom_domain');
544 $is_ssl = strpos($custom_domain, 'https://') === 0;
545 $fileLink_is_ssl = strpos($fileLink, 'https://') === 0;
546 $custom_domain = str_replace(array('http://', 'https://'), '', $custom_domain);
547 $custom_domain = trim($custom_domain, '/');
548
549 if (!empty($bucketname) && $custom_domain !== 'storage.googleapis.com' && $custom_domain == $bucketname && strpos($fileLink, $bucketname) > 8) {
550 $fileLink = ($is_ssl ? 'https://' : 'http://') . substr($fileLink, strpos($fileLink, $bucketname));
551 } elseif ($custom_domain !== 'storage.googleapis.com' && $custom_domain == $bucketname && $fileLink_is_ssl !== $is_ssl) {
552 if ($is_ssl)
553 $fileLink = str_replace(array('http://', 'https://'), 'https://', $fileLink);
554 else
555 $fileLink = str_replace(array('http://', 'https://'), 'http://', $fileLink);
556 }
557 return $fileLink;
558 }
559
560 /**
561 * Return settings page url.
562 *
563 * @param string $path
564 * @return string
565 */
566 public function get_settings_page_url($path = '') {
567 $url = get_admin_url(get_current_blog_id(), (is_network_admin() ? 'network/settings.php' : 'upload.php'));
568 return $url . $path;
569 }
570
571 /**
572 * Get new blog settings once switched blog.
573 * @param $new_blog
574 * @param $prev_blog_id
575 */
576 public function on_switch_blog($new_blog, $prev_blog_id) {
577 $this->settings->refresh();
578 }
579
580 /**
581 * Get settings handler.
582 * Filling array if some settings missing.
583 *
584 * @param $settings
585 * @return
586 */
587 public function get_settings($settings) {
588
589 $settings_list = array(
590 'mode',
591 'body_rewrite',
592 'body_rewrite_types',
593 'bucket',
594 'root_dir',
595 'key_json',
596 'cache_control',
597 'delete_remote',
598 'custom_domain',
599 'organize_media',
600 'hashify_file_name'
601 );
602
603 foreach ($settings_list as $setting) {
604
605 /** If setting is already exist, just skip it */
606 if (isset($settings[$setting])) {
607 continue;
608 }
609
610 $value = $this->get('sm.' . $setting);
611
612 /** Decode json to array */
613 if ($value && is_string($value) && $setting === 'key_json') {
614 $value = json_decode($value, true);
615 $setting = 'key';
616 }
617
618 $settings[$setting] = $value;
619 }
620
621 return $settings;
622 }
623
624 /**
625 * Remove all settings.
626 * @param bool $network
627 */
628 public function reset($network = false) {
629 $this->settings->reset($network);
630 }
631
632 /**
633 * @param $actions
634 * @param $post
635 * @param $detached
636 * @return mixed
637 */
638 public function add_custom_row_actions($actions, $post, $detached) {
639
640 if (!current_user_can('upload_files')) return $actions;
641
642 $sm_cloud = get_post_meta($post->ID, 'sm_cloud', 1);
643 $sm_mode = $this->get('sm.mode');
644 if (!empty($sm_cloud) && $sm_mode === 'stateless') return $actions;
645
646 if ($post && 'attachment' == $post->post_type && 'image/' == substr($post->post_mime_type, 0, 6)) {
647 $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>';
648 }
649
650 if ($post && 'attachment' == $post->post_type && 'image/' != substr($post->post_mime_type, 0, 6)) {
651 $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>';
652 }
653
654 return $actions;
655 }
656
657 /**
658 * Define REST API.
659 *
660 * @author korotkov@UD
661 */
662 public function api_init() {
663
664 $route_namespace = 'wp-stateless/v1';
665 $api_namespace = 'wpCloud\StatelessMedia\API';
666
667 register_rest_route($route_namespace, '/status', array(
668 'methods' => \WP_REST_Server::READABLE,
669 'callback' => array($api_namespace, 'status'),
670 'permission_callback' => '__return_true'
671 ));
672
673 register_rest_route($route_namespace, '/getSettings', array(
674 'methods' => \WP_REST_Server::READABLE,
675 'callback' => array($api_namespace, 'getSettings'),
676 'permission_callback' => array($api_namespace, 'authCheck')
677 ));
678
679 register_rest_route($route_namespace, '/updateSettings', array(
680 'methods' => \WP_REST_Server::CREATABLE,
681 'callback' => array($api_namespace, 'updateSettings'),
682 'permission_callback' => array($api_namespace, 'authCheck')
683 ));
684
685 register_rest_route($route_namespace, '/sync/getProcesses', array(
686 'methods' => \WP_REST_Server::READABLE,
687 'callback' => array($api_namespace, 'syncGetProcesses'),
688 'permission_callback' => array($api_namespace, 'authCheck')
689 ));
690
691 register_rest_route($route_namespace, '/sync/getProcess/(?P<id>\S+)', array(
692 'methods' => \WP_REST_Server::READABLE,
693 'callback' => array($api_namespace, 'syncGetProcess'),
694 'permission_callback' => array($api_namespace, 'authCheck')
695 ));
696
697 register_rest_route($route_namespace, '/sync/run', array(
698 'methods' => \WP_REST_Server::CREATABLE,
699 'callback' => array($api_namespace, 'syncRun'),
700 'permission_callback' => array($api_namespace, 'authCheck')
701 ));
702
703 register_rest_route($route_namespace, '/sync/stop', array(
704 'methods' => \WP_REST_Server::CREATABLE,
705 'callback' => array($api_namespace, 'syncStop'),
706 'permission_callback' => array($api_namespace, 'authCheck')
707 ));
708 }
709
710 /**
711 * Metabox for media modal page
712 * @param $form_fields
713 * @param $post
714 * @return array
715 */
716 public function attachment_modal_meta_box_callback($form_fields, $post) {
717
718 //do not show on media edit page, only on modal
719 if (isset($_GET['post'])) {
720 return $form_fields;
721 }
722
723 $link = get_edit_post_link($post->ID);
724
725 $form_field['label'] = '';
726 $form_field['input'] = 'html';
727 $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));
728 $form_field['show_in_modal'] = true;
729
730 $form_fields['sm_html'] = $form_field;
731 return $form_fields;
732 }
733
734 /**
735 * Metabox for media edit page
736 * @param $meta_boxes
737 * @return array
738 */
739 public function attachment_meta_box_callback($meta_boxes) {
740 $post_id = false;
741 if (isset($_GET['post'])) {
742 $post_id = intval($_GET['post']);
743 } elseif (isset($_POST['post_ID'])) {
744 $post_id = intval($_POST['post_ID']);
745 } elseif (isset($_GET['item'])) {
746 $post_id = intval($_GET['item']);
747 } elseif (isset($_POST['item'])) {
748 $post_id = intval($_POST['item']);
749 }
750
751 return $this->_prepare_data_for_metabox($meta_boxes, $post_id);
752 }
753
754 /**
755 * Prepare data for metabox fields
756 * @param $meta_boxes
757 * @param $post_id
758 * @return array
759 */
760 private function _prepare_data_for_metabox($meta_boxes, $post_id) {
761 $post = get_post($post_id);
762 $sm_cloud = get_post_meta($post_id, 'sm_cloud', 1);
763 $sm_mode = $this->get('sm.mode');
764
765 if (empty($post)) {
766 return $meta_boxes;
767 }
768
769 $sizes = $this->get_image_sizes();
770
771 $fields = array();
772
773 if ($sm_mode !== 'stateless' || empty($sm_cloud)) {
774 $fields[] = array(
775 'name' => __('Regenerate', ud_get_stateless_media()->domain),
776 'id' => 'storage_bucket_url',
777 'type' => 'custom_html',
778 'std' => $this->_prepare_generate_link($post, false, '', true, $sm_cloud),
779 'tab' => 'thumbnails',
780 );
781 }
782
783 if (is_array($sm_cloud) && !empty($sm_cloud['fileLink'])) {
784
785 $fields[] = array(
786 'type' => 'heading',
787 'name' => 'Files',
788 'tab' => 'thumbnails',
789 );
790
791 $fields[] = array(
792 'name' => __('Original', ud_get_stateless_media()->domain),
793 'id' => 'storage_bucket_url',
794 'type' => 'custom_html',
795 'media_modal' => true,
796 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="' . esc_attr($sm_cloud['fileLink']) . '" />
797 <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>',
798 'tab' => 'thumbnails',
799 );
800
801 if (!empty($sm_cloud['sizes']) && is_array($sm_cloud['sizes'])) {
802 foreach ($sm_cloud['sizes'] as $size_label => $size) {
803
804 $fields[] = array(
805 'name' => __(sprintf("%s x %s", ($size['width'] ?: $sizes[$size_label]['width']), ($size['height'] ?: $sizes[$size_label]['height'])), ud_get_stateless_media()->domain),
806 'id' => 'storage_bucket_url' . $size_label,
807 'type' => 'custom_html',
808 'media_modal' => true,
809 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="' . esc_attr($size['fileLink']) . '" />
810 <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>',
811 'tab' => 'thumbnails',
812 );
813 }
814 }
815
816 if (!empty($sm_cloud['cacheControl'])) {
817 $fields[] = array(
818 'name' => __('Cache Control', ud_get_stateless_media()->domain),
819 'id' => 'cache_control',
820 'type' => 'custom_html',
821 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="' . $sm_cloud['cacheControl'] . '" /></label>',
822 'tab' => 'meta',
823 );
824 }
825
826 if (!empty($sm_cloud['bucket'])) {
827 $fields[] = array(
828 'name' => __('Storage Bucket', ud_get_stateless_media()->domain),
829 'id' => 'storage_bukcet',
830 'type' => 'custom_html',
831 'std' => '<label><input type="text" class="widefat urlfield" readonly="readonly" value="gs://' . esc_attr($sm_cloud['bucket']) . '" />
832 <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>',
833 'tab' => 'meta',
834 );
835 }
836 }
837
838 $meta_boxes[] = apply_filters('sm::attachment::meta', array(
839 'id' => 'sm-attachment-metabox',
840 'title' => __('Stateless', ud_get_stateless_media()->domain),
841 'post_types' => 'attachment',
842 //'media_modal' => true,
843 //set context `side` for left column
844 'context' => 'normal',
845 'priority' => 'low',
846 'tabs' => array(
847 'thumbnails' => array(
848 'label' => __('Thumbnails', ud_get_stateless_media()->domain),
849 'icon' => 'dashicons-format-gallery',
850 ),
851 'meta' => array(
852 'label' => __('Meta', ud_get_stateless_media()->domain),
853 'icon' => 'dashicons-admin-site',
854 ),
855 ),
856 // Tab style: 'default', 'box' or 'left'. Optional
857 'tab_style' => 'left',
858 // Show meta box wrapper around tabs? true (default) or false. Optional
859 'tab_wrapper' => true,
860 'fields' => $fields
861 ), $post->ID);
862
863 return $meta_boxes;
864 }
865
866 /**
867 * Preparing link for sync
868 * @param $post
869 * @param bool $use_icon
870 * @param string $size
871 * @param bool $button
872 * @param array $sm_cloud
873 * @return string
874 */
875 private function _prepare_generate_link($post, $use_icon = false, $size = '', $button = false, $sm_cloud = array()) {
876 $sync = '';
877
878 $sm_mode = $this->get('sm.mode');
879
880 if (current_user_can('upload_files') && $sm_mode !== 'disabled' && ($sm_mode !== 'stateless' || empty($sm_cloud))) {
881 if ($post && 'attachment' == $post->post_type && 'image/' == substr($post->post_mime_type, 0, 6)) {
882 $sync = '<a href="javascript:;" data-type="image" data-id="' . $post->ID . '" data-size="' . $size . '" data-reload_page="' . $button . '"
883 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>';
884 }
885 if ($post && 'attachment' == $post->post_type && 'image/' != substr($post->post_mime_type, 0, 6)) {
886 $sync = '<a href="javascript:;" data-type="other" data-id="' . $post->ID . '" data-size="' . $size . '" data-reload_page="' . $button . '"
887 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>';
888 }
889 } elseif ($button && $sm_mode !== 'stateless') {
890 $sync = __('You do not have access to sync or Stateless mode is Disabled', ud_get_stateless_media()->domain);
891 }
892 return $sync;
893 }
894
895 /**
896 * @param $current_path
897 * @param $use_root boolean: whether to use the root dir or not.
898 * 0 will be passed from various compatibilities so that the root dir is not used.
899 * false will passed from some compatibilities to use the value as local path.
900 * @param $attachment_id
901 * @param $size
902 * @return string
903 */
904 public function handle_root_dir($current_path, $use_root = true, $attachment_id = '', $size = '') {
905 global $wpdb;
906
907 //non media files
908 if ($use_root === 0) {
909 $table_name = $wpdb->prefix . 'sm_sync';
910 $non_media = $wpdb->get_var($wpdb->prepare("SELECT file FROM {$table_name} WHERE file like '%%%s';", $current_path));
911 if ($non_media) {
912 return $non_media;
913 }
914 }
915
916 $root_dir = $this->get('sm.root_dir');
917 $root_dir_regex = '~^' . apply_filters("wp_stateless_handle_root_dir", $root_dir, true) . '/~';
918 /**
919 * Retrieve Y/M and other tags from current path
920 */
921 $path_elements = apply_filters('wp_stateless_unhandle_root_dir', $current_path);
922 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir, false, $path_elements);
923
924 $upload_dir = wp_upload_dir();
925 $current_path = str_replace(wp_normalize_path(trailingslashit($upload_dir['basedir'])), '', wp_normalize_path($current_path));
926 $current_path = str_replace(wp_normalize_path(trailingslashit($upload_dir['baseurl'])), '', wp_normalize_path($current_path));
927 $current_path = str_replace(trailingslashit($this->get_gs_host()), '', $current_path);
928
929 /**
930 * Using only filename. Other parts of path included to $root_dir.
931 * excluding compatibility.
932 */
933 if ($use_root) {
934 $current_path = basename($current_path);
935 }
936
937 if (!$use_root) {
938 // removing the root dir if already exists in the beginning.
939 $raw_name = preg_replace($root_dir_regex, '', $current_path);
940
941 if ($raw_name && is_multisite() && ($blog_id = get_current_blog_id()) != 1) {
942 $folder = "sites/{$blog_id}/";
943 if (strpos($raw_name, $folder) === 0) return $raw_name;
944
945 return "$folder$raw_name";
946 }
947
948 return $raw_name;
949 }
950
951 // skip adding root dir if it's already added.
952 if (!empty($root_dir) && !preg_match($root_dir_regex, $current_path)) {
953 return $root_dir . '/' . trim($current_path, '/ ');
954 }
955
956 return $current_path;
957 }
958
959 /**
960 * @param $content
961 * @return mixed
962 */
963 public function the_content_filter($content) {
964
965 if ($upload_data = wp_upload_dir()) {
966
967 if (!empty($upload_data['url']) && !empty($content)) {
968 $url = preg_replace('/https?:\/\//', '', $upload_data['url']);
969
970 $root_dir = trim($this->get('sm.root_dir'), '/ '); // Remove any forward slash and empty space.
971 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
972 $root_dir = !empty($root_dir) ? $root_dir . '/' : false;
973 $image_host = $this->get_gs_host();
974 $file_ext = $this->replaceable_file_types();
975 $content = preg_replace(
976 '/(href|src)=(\'|")(https?:\/\/' . str_replace('/', '\/', $url) . ')\/(.+?)(' . $file_ext . ')(\'|")/i',
977 '$1=$2' . $image_host . '/' . ($root_dir ? $root_dir : '') . '$4$5$6',
978 $content
979 );
980 }
981 }
982
983 return $content;
984 }
985
986 /**
987 * Return file types supported by File URL Replacement.
988 * @return string
989 */
990 public function replaceable_file_types() {
991 $types = $this->get('sm.body_rewrite_types');
992
993 // Removing extra space.
994 $types = trim($types);
995 $types = preg_replace("/\s{2,}/", ' ', $types);
996
997 $types_arr = explode(' ', $types);
998 return '\.' . implode('|\.', $types_arr);
999 }
1000
1001 /**
1002 * Copied from https://developer.wordpress.org/reference/functions/get_metadata/
1003 *
1004 * @param $value null unless other filter hooked in this function.
1005 * @param $object_id post id
1006 * @param $meta_key
1007 * @param $single
1008 * @return mixed
1009 */
1010 public function post_metadata_filter($value, $object_id, $meta_key, $single) {
1011 if (empty($value)) {
1012 $meta_type = 'post';
1013 $transient_key = "stateless_{$meta_type}_meta";
1014
1015 $meta_cache = wp_cache_get($object_id, $transient_key);
1016 if (empty($meta_cache)) {
1017 $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
1018
1019 if (!$meta_cache) {
1020 $meta_cache = update_meta_cache($meta_type, array($object_id));
1021 $meta_cache = $meta_cache[$object_id];
1022 }
1023
1024 foreach ($meta_cache as $key => $meta) {
1025 $meta_cache[$key] = array_map('maybe_unserialize', $meta_cache[$key]);
1026 }
1027
1028 $meta_cache = $this->convert_to_gs_link($meta_cache);
1029 wp_cache_set($object_id, $meta_cache, $transient_key);
1030 }
1031
1032 if (!$meta_key) {
1033 return $meta_cache;
1034 }
1035
1036 if (isset($meta_cache[$meta_key])) {
1037 return $meta_cache[$meta_key];
1038 }
1039
1040 // in case no metadata is found return what was passed in $value.
1041 // $value most of the time is null.
1042 return $value;
1043 }
1044
1045 return $this->convert_to_gs_link($value);
1046 }
1047
1048 /**
1049 * Replace all image link with gs link and return only if meta modified.
1050 *
1051 * @param $meta
1052 * @param $return
1053 * @return mixed or null when not changed.
1054 */
1055 public function convert_to_gs_link($meta, $return = false) {
1056 $updated = $meta;
1057 if ($meta && $upload_data = wp_upload_dir()) {
1058 if (!empty($upload_data['url']) && !empty($meta)) {
1059 $url = preg_replace('/https?:\/\//', '', $upload_data['url']);
1060 $root_dir = trim($this->get('sm.root_dir'), '/ '); // Remove any forward slash and empty space.
1061 $root_dir = apply_filters("wp_stateless_handle_root_dir", $root_dir);
1062 $root_dir = !empty($root_dir) ? $root_dir . '/' : false;
1063 $image_host = $this->get_gs_host() . '/' . ($root_dir ? $root_dir : '');
1064 $file_ext = $this->replaceable_file_types();
1065 $updated = $this->_convert_to_gs_link($meta, $image_host, $url, $file_ext);
1066 }
1067 }
1068
1069 if ($updated == $meta && !$return) {
1070 return null; // Not changed.
1071 }
1072 return $updated;
1073 }
1074
1075 /**
1076 * Replace all image link with gs link
1077 *
1078 * @param $meta
1079 * @param $image_host
1080 * @param $url
1081 * @param $file_ext
1082 * @return array|null|string|string[]
1083 */
1084 public function _convert_to_gs_link($meta, $image_host, $url, $file_ext) {
1085 if (is_array($meta)) {
1086 foreach ($meta as $key => $value) {
1087 $meta[$key] = $this->_convert_to_gs_link($value, $image_host, $url, $file_ext);
1088 }
1089 return $meta;
1090 } elseif (is_object($meta) && $meta instanceof \stdClass) {
1091 foreach (get_object_vars($meta) as $key => $value) {
1092 $meta->{$key} = $this->_convert_to_gs_link($value, $image_host, $url, $file_ext);
1093 }
1094 return $meta;
1095 } elseif (is_string($meta)) {
1096 return preg_replace('/(https?:\/\/' . str_replace('/', '\/', $url) . ')\/(.+?)(' . $file_ext . ')/i', $image_host . '$2$3', $meta);
1097 }
1098
1099 return $meta;
1100 }
1101
1102 /**
1103 * @param $links
1104 * @param $file
1105 * @return mixed
1106 */
1107 public function plugin_action_links($links, $file) {
1108
1109 if ($file == plugin_basename(dirname(__DIR__) . '/wp-stateless-media.php')) {
1110 $settings_link = '<a href="' . '' . '">' . __('Settings', 'ssd') . '</a>';
1111 array_unshift($links, $settings_link);
1112 }
1113
1114 if ($file == plugin_basename(dirname(__DIR__) . '/wp-stateless.php')) {
1115 $settings_link = '<a href="' . '' . '">' . __('Settings', 'ssd') . '</a>';
1116 array_unshift($links, $settings_link);
1117 }
1118
1119 return $links;
1120 }
1121
1122 /**
1123 * Determines if plugin is loaded via mu-plugins
1124 * or Network Enabled.
1125 *
1126 * @param bool $is_multisite
1127 * @return bool
1128 * @author peshkov@UD
1129 */
1130 public function is_network_detected($is_multisite = false) {
1131 /* Plugin is loaded via mu-plugins. */
1132
1133 if (strpos(Utility::normalize_path($this->root_path), Utility::normalize_path(WPMU_PLUGIN_DIR)) !== false) {
1134 return true;
1135 }
1136
1137 if (is_multisite()) {
1138 if ($is_multisite) return true;
1139 /* Looks through network enabled plugins to see if our one is there. */
1140 foreach (wp_get_active_network_plugins() as $path) {
1141 // Trying again using readlink in case it's a symlink file.
1142 // boot_file is already solved.
1143 // wp_normalize_path is helpfull in windows.
1144 if (wp_normalize_path($this->boot_file) == wp_normalize_path($path) || (is_link($path) && $this->boot_file == readlink($path))) {
1145 return true;
1146 }
1147 }
1148 }
1149 return false;
1150 }
1151
1152 /**
1153 * Initialization.
1154 * Register scripts and styles
1155 */
1156 public function admin_init() {
1157 $this->show_notice_stateless_cache_busting();
1158 wp_register_style('wp-stateless', $this->path('static/styles/wp-stateless.css', 'url'), array(), self::$version);
1159
1160 /**
1161 * select2 styles
1162 */
1163 wp_register_style('wp-stateless-select2', $this->path('static/styles/select2.min.css', 'url'), array(), self::$version);
1164
1165 /* Attachment or upload page */
1166 wp_register_script('wp-stateless-uploads-js', $this->path('static/scripts/wp-stateless-uploads.js', 'url'), array('jquery'), self::$version);
1167
1168 /* Setup wizard styles. */
1169 wp_register_style('wp-stateless-setup-wizard', $this->path('static/styles/wp-stateless-setup-wizard.css', 'url'), array(), self::$version);
1170
1171 wp_register_script('wp-stateless-select2', ud_get_stateless_media()->path('static/scripts/select2.min.js', 'url'), array('jquery'), self::$version, true);
1172
1173 /* Stateless settings page */
1174 wp_register_script('wp-stateless-settings', ud_get_stateless_media()->path('static/scripts/wp-stateless-settings.js', 'url'), array(), self::$version);
1175 wp_localize_script('wp-stateless-settings', 'stateless_l10n', $this->get_l10n_data());
1176
1177 wp_register_style('wp-stateless-settings', $this->path('static/styles/wp-stateless-settings.css', 'url'), array(), self::$version);
1178
1179 // Sync tab
1180 wp_register_script('wp-stateless-angular', ud_get_stateless_media()->path('static/scripts/angular.min.js', 'url'), array(), '1.8.0', true);
1181 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);
1182 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);
1183
1184 wp_localize_script('wp-stateless', 'stateless_l10n', $this->get_l10n_data());
1185 wp_localize_script('wp-stateless', 'wp_stateless_configs', array(
1186 'WP_DEBUG' => defined('WP_DEBUG') ? WP_DEBUG : false,
1187 'REST_API_TOKEN' => Utility::generate_jwt_token(['user_id' => get_current_user_id()], DAY_IN_SECONDS)
1188 ));
1189
1190 $settings = ud_get_stateless_media()->get('sm');
1191 $settings['wildcards'] = $this->settings->wildcards;
1192 $settings['network_admin'] = is_network_admin();
1193 $settings['is_multisite'] = is_multisite();
1194 if (defined('WP_STATELESS_MEDIA_JSON_KEY') && WP_STATELESS_MEDIA_JSON_KEY) {
1195 $settings['key_json'] = "Currently configured via a constant.";
1196 }
1197 wp_localize_script('wp-stateless', 'wp_stateless_settings', $settings);
1198 wp_localize_script('wp-stateless', 'wp_stateless_compatibility', Module::get_modules());
1199 }
1200
1201 /**
1202 * Get_l10n_data
1203 *
1204 * @param string $value
1205 * @return mixed
1206 */
1207 public function get_l10n_data($value = '') {
1208 include ud_get_stateless_media()->path('l10n.php', 'dir');
1209 return $l10n;
1210 }
1211
1212 /**
1213 * Admin Scripts
1214 *
1215 * @param $hook
1216 */
1217 public function admin_enqueue_scripts($hook) {
1218
1219 switch ($hook) {
1220
1221 case 'options-media.php':
1222 wp_enqueue_style('wp-stateless');
1223 break;
1224
1225 case 'upload.php':
1226 wp_enqueue_style('wp-stateless');
1227 wp_enqueue_script('wp-stateless-uploads-js');
1228
1229 break;
1230
1231 case 'post.php':
1232
1233 global $post;
1234
1235 if ($post->post_type == 'attachment') {
1236 wp_enqueue_style('wp-stateless');
1237 wp_enqueue_script('wp-stateless-uploads-js');
1238 }
1239
1240 break;
1241
1242 case 'media_page_stateless-setup':
1243 case 'settings_page_stateless-setup':
1244 wp_enqueue_style('wp-stateless');
1245 wp_enqueue_style('wp-stateless-setup-wizard');
1246 break;
1247 case 'media_page_stateless-settings':
1248 case 'settings_page_stateless-settings':
1249 wp_enqueue_style('wp-stateless');
1250 wp_enqueue_style('wp-stateless-select2');
1251 wp_enqueue_script('wp-stateless-settings');
1252 wp_enqueue_script('wp-stateless-select2');
1253 wp_enqueue_style('wp-stateless-settings');
1254
1255 // Sync tab
1256 wp_enqueue_script('wp-stateless-angular');
1257 wp_enqueue_script('wp-stateless-angular-sanitize');
1258 wp_enqueue_script('wp-stateless');
1259
1260 wp_enqueue_style('wp-pointer');
1261 wp_enqueue_script('wp-pointer');
1262
1263 $data = array(
1264 'key' => 'stateless-cache-busting',
1265 'class' => 'notice',
1266 'title' => sprintf(__("Stateless and Ephemeral modes enables and requires the Cache-Busting option.", ud_get_stateless_media()->domain)),
1267 'message' => sprintf(__("WordPress looks at local files to prevent files with the same filenames.
1268 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.
1269 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"),
1270 );
1271 echo "<script id='template-stateless-cache-busting' type='text/html'>";
1272 include ud_get_stateless_media()->path('/static/views/error-notice.php', 'dir');
1273 echo "</script>";
1274
1275 break;
1276 default:
1277 break;
1278 }
1279 }
1280
1281 /**
1282 * Add Attributes to media HTML
1283 *
1284 * @author potanin@UD
1285 * @param $attr
1286 * @param $attachment
1287 * @param $size
1288 * @return mixed
1289 */
1290 public function wp_get_attachment_image_attributes($attr, $attachment, $size = null) {
1291
1292 $sm_cloud = get_post_meta($attachment->ID, 'sm_cloud', true);
1293 if (is_array($sm_cloud) && !empty($sm_cloud['name'])) {
1294 $attr['class'] = $attr['class'] . ' wp-stateless-item';
1295 $attr['data-image-size'] = is_array($size) ? implode('x', $size) : $size;
1296 $attr['data-stateless-media-bucket'] = isset($sm_cloud['bucket']) ? $sm_cloud['bucket'] : false;
1297 $attr['data-stateless-media-name'] = $sm_cloud['name'];
1298 }
1299
1300 return $attr;
1301 }
1302
1303 /**
1304 * Adds filter link to Media Library table.
1305 *
1306 * @param $views
1307 * @return mixed
1308 */
1309 public function views_upload($views) {
1310 $views['stateless'] = '<a href="#">' . __('Stateless Media') . '</a>';
1311 return $views;
1312 }
1313
1314 /**
1315 * Replace media URL
1316 *
1317 * @param bool $false
1318 * @param integer $id
1319 * @param string $size
1320 * @return mixed $false
1321 */
1322 public function image_downsize($false, $id, $size) {
1323
1324 if ((!isset($this->client) || !$this->client || is_wp_error($this->client)) && $this->get('sm.mode') !== 'stateless') {
1325 return $false;
1326 }
1327
1328 /**
1329 * Check if enabled
1330 */
1331 if (!in_array($this->get('sm.mode'), array('cdn', 'stateless', 'ephemeral'))) {
1332 return $false;
1333 }
1334
1335 /** Start determine remote file */
1336 $img_url = wp_get_attachment_url($id);
1337 $meta = wp_get_attachment_metadata($id);
1338 $width = $height = 0;
1339 $is_intermediate = false;
1340
1341 //** try for a new style intermediate size */
1342 if ($intermediate = image_get_intermediate_size($id, $size)) {
1343 if (!empty($intermediate['gs_link'])) {
1344 $img_url = $intermediate['gs_link'];
1345 } else if (!empty($intermediate['url'])) {
1346 $img_url = $intermediate['url'];
1347 } else {
1348 $img_url = dirname($img_url) . $intermediate['file'];
1349 }
1350
1351 $width = $intermediate['width'];
1352 $height = $intermediate['height'];
1353 $is_intermediate = true;
1354 }
1355
1356 /**
1357 * maybe try to get images info from sm_cloud
1358 * this case may happen when no local files
1359 * @author korotkov@ud
1360 */
1361 if (!$width && !$height) {
1362 $sm_cloud = get_post_meta($id, 'sm_cloud', true);
1363 if (is_string($size) && !empty($sm_cloud['sizes']) && !empty($sm_cloud['sizes'][$size])) {
1364 global $_wp_additional_image_sizes;
1365
1366 $img_url = !empty($sm_cloud['sizes'][$size]['fileLink']) ? $sm_cloud['sizes'][$size]['fileLink'] : $img_url;
1367
1368 if (!empty($_wp_additional_image_sizes[$size])) {
1369 $width = !empty($_wp_additional_image_sizes[$size]['width']) ? $_wp_additional_image_sizes[$size]['width'] : $width;
1370 $height = !empty($_wp_additional_image_sizes[$size]['height']) ? $_wp_additional_image_sizes[$size]['height'] : $height;
1371 }
1372
1373 $is_intermediate = true;
1374 }
1375 }
1376
1377 if (!$width && !$height && isset($meta['width'], $meta['height'])) {
1378
1379 //** any other type: use the real image */
1380 $width = $meta['width'];
1381 $height = $meta['height'];
1382 }
1383
1384
1385 if ($img_url) {
1386
1387 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
1388 list($width, $height) = image_constrain_size_for_editor($width, $height, $size);
1389 $img_url = apply_filters('wp_stateless_bucket_link', $img_url);
1390 return array($img_url, $width, $height, $is_intermediate);
1391 }
1392
1393
1394 /**
1395 * All other cases work as usually
1396 */
1397 return $false;
1398 }
1399
1400 /**
1401 * Extends metadata by adding GS information.
1402 * Note: must not be called directly. It's used only on hook
1403 *
1404 * @param $metadata
1405 * @param $attachment_id
1406 * @return array|mixed
1407 */
1408 public function wp_get_attachment_metadata($metadata, $attachment_id) {
1409 global $default_dir;
1410 $default_dir = false;
1411 /* Determine if the media file has GS data at all. */
1412 $sm_cloud = get_post_meta($attachment_id, 'sm_cloud', true);
1413 // If metadata not passed the get metadata from post meta.
1414 if (empty($metadata)) {
1415 $metadata = get_post_meta($attachment_id, '_wp_attachment_metadata', true);
1416 }
1417
1418 if (empty($metadata)) {
1419 $metadata = [];
1420 }
1421
1422 if (is_array($metadata) && is_array($sm_cloud) && !empty($sm_cloud['fileLink'])) {
1423 $metadata['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud['fileLink']);
1424 $metadata['gs_name'] = isset($sm_cloud['name']) ? $sm_cloud['name'] : false;
1425 $metadata['gs_bucket'] = isset($sm_cloud['bucket']) ? $sm_cloud['bucket'] : false;
1426 if (!empty($metadata['sizes']) && is_array($metadata['sizes'])) {
1427 foreach ($metadata['sizes'] as $k => $v) {
1428 if (!empty($sm_cloud['sizes'][$k]['name'])) {
1429 $metadata['sizes'][$k]['gs_name'] = $sm_cloud['sizes'][$k]['name'];
1430 $metadata['sizes'][$k]['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud['sizes'][$k]['fileLink']);
1431 }
1432 }
1433 }
1434 }
1435 if (is_multisite() && !empty($metadata['file'])) {
1436 if ($this->get('sm.mode') == 'stateless') {
1437 $default_dir = true;
1438 $uploads = wp_get_upload_dir();
1439 $default_dir = false;
1440
1441 $file_path_fix = $uploads['basedir'] . "/{$metadata['file']}";
1442 if (file_exists($file_path_fix)) {
1443 $metadata['file'] = "{$metadata['file']}";
1444 }
1445 } else {
1446 $uploads = wp_get_upload_dir();
1447 $blog_id = get_current_blog_id();
1448 $file_path_fix = $uploads['basedir'] . "/sites/$blog_id/{$metadata['file']}";
1449 if (file_exists($file_path_fix)) {
1450 $metadata['file'] = "sites/$blog_id/{$metadata['file']}";
1451 }
1452 }
1453 } elseif (empty($sm_cloud) && $this->get('sm.mode') == 'stateless') {
1454 $default_dir = true;
1455 $uploads = wp_get_upload_dir();
1456 $default_dir = false;
1457
1458 $file_path_fix = $uploads['basedir'] . "/{$metadata['file']}";
1459 if (file_exists($file_path_fix)) {
1460 $metadata['file'] = "{$metadata['file']}";
1461 }
1462 }
1463
1464 return $metadata;
1465 }
1466
1467 /**
1468 *
1469 * @param $file
1470 * @param $attachment_id
1471 * @return string
1472 */
1473 public function get_attached_file($file, $attachment_id) {
1474 global $default_dir;
1475
1476 $sm_cloud = get_post_meta($attachment_id, 'sm_cloud', true);
1477 $_file = get_post_meta($attachment_id, '_wp_attached_file', true);
1478
1479 /* Determine if the media file has GS data at all. */
1480 if (is_multisite() && $_file) {
1481 $blog_id = get_current_blog_id();
1482 $uploads = wp_get_upload_dir();
1483 $_file = apply_filters('wp_stateless_file_name', $_file, false);
1484 $file_path_fix = $uploads['basedir'] . "/sites/$blog_id/$_file";
1485
1486 if (file_exists($file_path_fix)) {
1487 return $file_path_fix;
1488 }
1489 } elseif (empty($sm_cloud) && $this->get('sm.mode') == 'stateless' && $_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