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

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