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

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