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

1,489 lines 54.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Bootstrap
4 *
5 * @since 0.2.0
6 */
7 namespace wpCloud\StatelessMedia {
8
9 if( !class_exists( 'wpCloud\StatelessMedia\Bootstrap' ) ) {
10
11 final class Bootstrap extends \UsabilityDynamics\WP\Bootstrap_Plugin {
12
13 /**
14 * Google Storage Client
15 * Use $this->get_client()
16 *
17 * @var \wpCloud\StatelessMedia\GS_CLient
18 */
19 private $client;
20
21 /**
22 * Plugin core version.
23 *
24 * @static
25 * @property $version
26 * @type {Object}
27 */
28 public static $version = '2.1';
29
30 /**
31 * Singleton Instance Reference.
32 *
33 * @protected
34 * @static
35 * @property $instance
36 * @type \wpCloud\StatelessMedia\Bootstrap object
37 */
38 protected static $instance = null;
39
40
41 /**
42 * Constructor
43 * Attention: MUST NOT BE CALLED DIRECTLY! USE get_instance() INSTEAD!
44 *
45 * @author peshkov@UD
46 */
47 protected function __construct( $args ) {
48 parent::__construct( $args );
49
50 //** Define our Admin Notices handler object */
51 $this->errors = new Errors( array_merge( $args, array(
52 'type' => $this->type
53 ) ) );
54
55 // Initialize compatibility modules.
56 add_action( 'plugins_loaded', function(){
57 new Module();
58 });
59 }
60
61 /**
62 * Instantiate class.
63 */
64 public function init() {
65
66 /**
67 * Copied from wp-property
68 * Duplicates UsabilityDynamics\WP\Bootstrap_Plugin::load_textdomain();
69 *
70 * There is a bug with localization in lib-wp-bootstrap 1.1.3 and lower.
71 * So we load textdomain here again, in case old version lib-wp-bootstrap is being loaded
72 * by another plugin.
73 *
74 * @since 1.9.1
75 */
76 load_plugin_textdomain($this->domain, false, dirname(plugin_basename($this->boot_file)) . '/static/languages/');
77
78 // Parse feature falgs, set constants.
79 $this->parse_feature_flags();
80
81 new SyncNonMedia();
82
83 /**
84 * Define settings and UI.
85 *
86 * Example:
87 *
88 * Get option
89 * $this->get( 'sm.client_id' )
90 *
91 * Manually Update/Add option
92 * $this->set( 'sm.client_id', 'zxcvv12adffse' );
93 */
94 $this->settings = new Settings();
95
96 // Invoke REST API
97 add_action( 'rest_api_init', array( $this, 'api_init' ) );
98
99 /**
100 * Register SM metaboxes
101 */
102 add_action( 'admin_init', array( $this, 'register_metaboxes' ) );
103
104 /**
105 * Init hook
106 */
107 add_action( 'admin_init', array( $this, 'admin_init' ) );
108
109 /**
110 * Add custom actions to media rows
111 */
112 add_filter( 'media_row_actions', array( $this, 'add_custom_row_actions' ), 10, 3 );
113
114 /**
115 * Handle switch blog properly.
116 */
117 add_action( 'switch_blog', array( $this, 'on_switch_blog' ), 10, 2 );
118
119 /**
120 * Filter for getting stateless settings
121 */
122 add_filter( 'stateless::get_settings', array( $this, 'get_settings' ), 10);
123
124 /**
125 * Init AJAX jobs
126 */
127 new Ajax();
128
129 /**
130 * Load WP-CLI Commands
131 */
132 if( defined( 'WP_CLI' ) && WP_CLI ) {
133 include_once($this->path('lib/cli/class-sm-cli-command.php', 'dir'));
134 }
135
136 $this->is_network_detected();
137
138 /**
139 * Add scripts
140 */
141 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
142
143 /**
144 * Hashify file name if option is enabled
145 */
146 if ( $this->get( 'sm.hashify_file_name' ) == 'true' ) {
147 add_filter('sanitize_file_name', array( 'wpCloud\StatelessMedia\Utility', 'randomize_filename' ), 10);
148 }
149
150 /* Initialize plugin only if Mode is not 'disabled'. */
151 if ( $this->get( 'sm.mode' ) !== 'disabled' ) {
152
153 /**
154 * Override Cache Control is option is enabled
155 */
156 $cacheControl = trim($this->get( 'sm.cache_control' ));
157 if ( !empty($cacheControl) ) {
158 add_filter( 'sm:item:cacheControl', array( $this, 'override_cache_control' ) );
159 }
160
161 /**
162 * Determine if we have issues with connection to Google Storage Bucket
163 * if SM is not disabled.
164 */
165 $is_connected = $this->is_connected_to_gs();
166
167 if ( is_wp_error( $is_connected ) ) {
168 $this->errors->add( $is_connected->get_error_message() );
169 }
170
171 if ( $googleSDKVersionConflictError = get_transient( "wp_stateless_google_sdk_conflict" ) ) {
172 $this->errors->add( $googleSDKVersionConflictError, 'warning' );
173 }
174
175 // To prevent fatal errors for users who use PHP 5.5 or less.
176 if( version_compare(PHP_VERSION, '5.5', '<') ) {
177 $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>' ) );
178 }
179
180 /** Temporary fix to WP 4.4 srcset feature **/
181 //add_filter( 'max_srcset_image_width', function(){return 1;} );
182
183 /**
184 * Carry on only if we do not have errors.
185 */
186 if( !$this->has_errors() ) {
187
188 if( $this->get( 'sm.mode' ) === 'cdn' || $this->get( 'sm.mode' ) === 'stateless' ) {
189 add_filter( 'wp_get_attachment_image_attributes', array( $this, 'wp_get_attachment_image_attributes' ), 20, 3 );
190 add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 20, 2 );
191 add_filter( 'attachment_url_to_postid', array( $this, 'attachment_url_to_postid' ), 20, 2 );
192
193 if ( $this->get( 'sm.body_rewrite' ) == 'true' || $this->get( 'sm.body_rewrite' ) == 'enable_editor' ) {
194 add_filter( 'the_content', array( $this, 'the_content_filter' ), 99 );
195 }
196
197 if ( $this->get( 'sm.body_rewrite' ) == 'true' || $this->get( 'sm.body_rewrite' ) == 'enable_meta' ) {
198 add_filter( 'get_post_metadata', array( $this, 'post_metadata_filter' ), 2, 4 );
199 }
200
201 add_filter( 'wp_stateless_bucket_link', array( $this, 'wp_stateless_bucket_link' ) );
202 }
203
204 if ( $root_dir = $this->get( 'sm.root_dir' ) ) {
205 if ( trim( $root_dir, '/ ' ) !== '' ) { // Remove any forward slash and empty space.
206 add_filter( 'wp_stateless_file_name', array( $this, 'handle_root_dir' ) );
207 }
208 }
209
210 /**
211 * Rewrite Image URLS
212 */
213 add_filter( 'image_downsize', array( $this, 'image_downsize' ), 99, 3 );
214 add_filter( 'wp_calculate_image_srcset', array($this, 'wp_calculate_image_srcset'), 10, 5 );
215
216 /**
217 * Extends metadata by adding GS information.
218 */
219 add_filter( 'wp_get_attachment_metadata', array( $this, 'wp_get_attachment_metadata' ), 10, 2 );
220
221 /**
222 * Add/Edit Media
223 *
224 * Once added or edited we can get into Attachment ID then get all image sizes and sync them with GS
225 * We can't use this. That's prevent removing this filter.
226 */
227 add_filter( 'wp_update_attachment_metadata', array( 'wpCloud\StatelessMedia\Utility', 'add_media' ), 999, 2 );
228
229 /**
230 * Upload the full size image first.
231 *
232 */
233 if(!defined('WP_STATELESS_MEDIA_DISABLE_FULL_IMAGE_FIRST') || WP_STATELESS_MEDIA_DISABLE_FULL_IMAGE_FIRST != true){
234 add_filter( 'intermediate_image_sizes_advanced', array( $this, 'before_intermediate_image_sizes' ), 10, 2 );
235 }
236
237 /**
238 * Add Media
239 *
240 * Once added we can get into Attachment ID then get all image sizes and sync them with GS
241 */
242 // add_filter( 'wp_generate_attachment_metadata', array( $this, 'add_media' ), 100, 2 );
243
244 if ( $this->get( 'sm.delete_remote' ) == 'true' ) {
245 /**
246 * On physical file deletion we remove any from GS
247 */
248 add_filter('delete_attachment', array($this, 'remove_media'));
249 }
250
251 // Trigger module initialization and registration.
252 do_action('sm::module::init', $this->get( 'sm' ));
253 }
254
255 }
256 }
257
258 /**
259 * Rebuild srcset from gs_link.
260 * Using calculations returned from WordPress wp_calculate_image_srcset()
261 *
262 */
263 public function wp_calculate_image_srcset($sources, $size_array, $image_src, $image_meta, $attachment_id){
264 if (empty($image_meta['gs_link'])) {
265 $image_meta = wp_get_attachment_metadata($attachment_id);
266 }
267
268 if(is_array($sources) && !empty( $image_meta['gs_link'] )){
269 $gs_name = $image_meta['gs_name'];
270 // getting position of root_dir in gs_name.
271 $root_dir_pos = strpos($gs_name, $image_meta['file']);
272 // removing rood_dir from gs_name so we can compare to replace url with gs_link.
273 if($root_dir_pos !== false){
274 $gs_name = substr($gs_name, $root_dir_pos);
275 }
276
277 if ( !isset($gs_name) || empty($gs_name) ) {
278 return [];
279 }
280
281 foreach ($sources as $width => &$image) {
282
283 // If srcset includes original image src, replace it
284 if (substr_compare($image['url'], $gs_name, -strlen($gs_name)) === 0) {
285 $image['url'] = $image_meta['gs_link'];
286 // Replace all sizes
287 } elseif (isset($image_meta['sizes']) && is_array($image_meta['sizes'])) {
288 $found = false;
289 foreach ($image_meta['sizes'] as $key => $meta) {
290 if (!isset($meta['gs_name']) || empty($meta['gs_name'])) {
291 continue;
292 }
293
294 $thumb_gs_name = $meta['gs_name'];
295 // removing rood_dir from gs_name
296 if($root_dir_pos !== false){
297 $thumb_gs_name = substr($thumb_gs_name, $root_dir_pos);
298 }
299
300 if (substr_compare($image['url'], $thumb_gs_name, -strlen($thumb_gs_name)) === 0) {
301 $image['url'] = $meta['gs_link'];
302 $found = true;
303 break;
304 }
305 }
306
307 // if no size found and mode is stateless and nothing to show for srcset item - unset that item
308 if (!$found && $this->get( 'sm.mode' ) === 'stateless') {
309 $image = null;
310 }
311 } else {
312 // if mode is stateless and nothing to show for srcset item - unset that item
313 if ( $this->get( 'sm.mode' ) === 'stateless' ) {
314 $image = null;
315 }
316 }
317 }
318 }
319
320 return array_filter( $sources );
321 }
322
323 /**
324 * Return gs host.
325 * If custom domain is set it's return bucket name as host,
326 * else return storage.googleapis.com as host and append bucket name at the end.
327 *
328 * @param array $sm
329 * @return mixed|void
330 */
331 public function get_gs_host($sm = array()) {
332 $sm = $sm?$sm: $this->get( 'sm');
333 $image_host = 'https://storage.googleapis.com/';
334 $image_host .= $sm['bucket'];
335
336 $custom_domain = $sm['custom_domain'];
337 $is_ssl = strpos($custom_domain, 'https://');
338 $custom_domain = str_replace(array('http://', 'https://'), '', $custom_domain);
339 $custom_domain = trim($custom_domain, '/');
340
341 // checking whether the provided domain is valid.
342 // if the custom domain is same as the bucket name
343 // or the custom domain is using https.
344 if ( !empty($sm['bucket']) && !empty($custom_domain) && $custom_domain !== 'storage.googleapis.com' && ( $is_ssl === 0 || $custom_domain == $sm['bucket'] ) ) {
345 $image_host = $is_ssl === 0 ? 'https://' : 'http://'; // bucketname will be host
346 $image_host .= $custom_domain;
347 }
348
349 return apply_filters( 'get_gs_host', $image_host, $image_host, $sm['bucket'], $is_ssl, $sm );
350 }
351
352 /**
353 * Filter for wp_stateless_bucket_link if custom domain is set.
354 * It's get attachment url and remove "storage.googleapis.com" from url.
355 * So that custom url can be used.
356 *
357 * @param $fileLink
358 * @return mixed|string
359 */
360 public function wp_stateless_bucket_link($fileLink) {
361 $bucketname = $this->get( 'sm.bucket' );
362 $custom_domain = $this->get( 'sm.custom_domain' );
363 $is_ssl = strpos($custom_domain, 'https://') === 0;
364 $fileLink_is_ssl = strpos($fileLink, 'https://') === 0;
365 $custom_domain = str_replace(array('http://', 'https://'), '', $custom_domain);
366 $custom_domain = trim($custom_domain, '/');
367
368 if ( $custom_domain !== 'storage.googleapis.com' && $custom_domain == $bucketname && strpos($fileLink, $bucketname) > 8 ) {
369 $fileLink = ($is_ssl ? 'https://' : 'http://') . substr($fileLink, strpos($fileLink, $bucketname));
370 }
371 elseif( $custom_domain !== 'storage.googleapis.com' && $custom_domain == $bucketname && $fileLink_is_ssl !== $is_ssl){
372 if($is_ssl)
373 $fileLink = str_replace(array('http://', 'https://'), 'https://', $fileLink);
374 else
375 $fileLink = str_replace(array('http://', 'https://'), 'http://', $fileLink);
376 }
377 return $fileLink;
378 }
379
380 /**
381 * Return settings page url.
382 *
383 * @param string $path
384 * @return string
385 */
386 public function get_settings_page_url( $path = '' ) {
387 $url = get_admin_url( get_current_blog_id(), ( is_network_admin() ? 'network/settings.php' : 'upload.php' ) );
388 return $url . $path;
389 }
390
391 /**
392 * Get new blog settings once switched blog.
393 * @param $new_blog
394 * @param $prev_blog_id
395 */
396 public function on_switch_blog( $new_blog, $prev_blog_id ) {
397 $this->settings->refresh();
398 }
399
400 /**
401 * Get settings handler.
402 * Filling array if some settings missing.
403 *
404 * @param $settings
405 * @return
406 */
407 public function get_settings($settings) {
408
409 $settings_list = array(
410 'mode',
411 'body_rewrite',
412 'body_rewrite_types',
413 'bucket',
414 'root_dir',
415 'key_json',
416 'cache_control',
417 'delete_remote',
418 'custom_domain',
419 'organize_media',
420 'hashify_file_name'
421 );
422
423 foreach ($settings_list as $setting) {
424
425 /** If setting is already exist, just skip it */
426 if( isset( $settings[ $setting ] ) ) {
427 continue;
428 }
429
430 $value = $this->get( 'sm.' . $setting );
431
432 /** Decode json to array */
433 if( $value && is_string( $value ) && $setting === 'key_json' ) {
434 $value = json_decode( $value, true );
435 $setting = 'key';
436 }
437
438 $settings[ $setting ] = $value;
439 }
440
441 return $settings;
442 }
443
444 /**
445 * Remove all settings.
446 */
447 public function reset($network = false) {
448 $this->settings->reset($network);
449 }
450
451 /**
452 * @param $actions
453 * @param $post
454 * @param $detached
455 * @return mixed
456 */
457 public function add_custom_row_actions( $actions, $post, $detached ) {
458
459 if ( !current_user_can( 'upload_files' ) ) return $actions;
460
461 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
462 $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>';
463 }
464
465 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
466 $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>';
467 }
468
469 return $actions;
470
471 }
472
473 /**
474 * Register metaboxes
475 */
476 public function register_metaboxes() {
477 add_meta_box(
478 'sm-attachment-metabox',
479 __( 'Google Cloud Storage', ud_get_stateless_media()->domain ),
480 array($this, 'attachment_meta_box_callback'),
481 'attachment',
482 'side',
483 'low'
484 );
485 }
486
487 /**
488 * Define REST API.
489 *
490 * // https://usabilitydynamics-sandbox-uds-io-stateless-testing.c.rabbit.ci/wp-json/wp-stateless/v1
491 *
492 * @author potanin@UD
493 */
494 public function api_init() {
495
496 $route_namespace = 'wp-stateless/v1';
497 $api_namespace = 'wpCloud\StatelessMedia\API';
498
499 register_rest_route( $route_namespace, '/status', array(
500 'methods' => 'GET',
501 'callback' => array( $api_namespace, 'status' ),
502 ) );
503
504 register_rest_route( $route_namespace, '/jobs', array(
505 'methods' => 'GET',
506 'callback' => array( $api_namespace, 'jobs' ),
507 ) );
508
509 /**
510 * Return stateless settings.
511 *
512 * Request parameter: none
513 *
514 * Response:
515 * ok: Whether API is up or not
516 * message: Describe what is done or error message on error.
517 * settings: array of stateless settings
518 *
519 */
520 register_rest_route( $route_namespace, '/getSettings', array( 'methods' => 'GET', 'callback' => array( $api_namespace, 'getSettings' ), ) );
521
522 /**
523 * Essentially for scrolling through media library to build our index.
524 *
525 * Request parameter: none
526 *
527 * Response:
528 * ok: Whether API is up or not
529 * message: Describe what is done or error message on error.
530 * settings: array of media files
531 *
532 */
533 register_rest_route( $route_namespace, '/getMediaLibrary', array( 'methods' => 'GET', 'callback' => array( $api_namespace, 'getMediaLibrary' ), ) );
534
535 /**
536 * Get detailed information of media file
537 *
538 * Request parameter: none
539 *
540 * Response:
541 * ok: Whether API is up or not
542 * message: Describe what is done or error message on error.
543 * settings: media file array
544 *
545 */
546 register_rest_route( $route_namespace, '/getMediaItem', array( 'methods' => 'GET', 'callback' => array( $api_namespace, 'getMediaItem' ), ) );
547
548 }
549
550 /**
551 * @param $post
552 */
553 public function attachment_meta_box_callback( $post ) {
554 ob_start();
555
556 $sm_cloud = get_post_meta( $post->ID, 'sm_cloud', 1 );
557
558 if ( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) { ?>
559
560 <?php if( !empty( $sm_cloud[ 'cacheControl' ] ) ) { ?>
561 <div class="misc-pub-cache-control hidden">
562 <?php _e( 'Cache Control:', ud_get_stateless_media()->domain ); ?> <strong><span><?php echo $sm_cloud[ 'cacheControl' ]; ?></span> </strong>
563 </div>
564 <?php } ?>
565
566 <div class="misc-pub-gs-file-link" style="margin-bottom: 15px;">
567 <label>
568 <?php _e( 'Storage Bucket URL:', ud_get_stateless_media()->domain ); ?> <a href="<?php echo $sm_cloud[ 'fileLink' ]; ?>" target="_blank" class="sm-view-link"><?php _e( '[view]' ); ?></a>
569 <input type="text" class="widefat urlfield" readonly="readonly" value="<?php echo esc_attr($sm_cloud[ 'fileLink' ]); ?>" />
570 </label>
571 </div>
572
573 <?php
574
575 if ( !empty( $sm_cloud[ 'bucket' ] ) ) {
576 ?>
577 <div class="misc-pub-gs-bucket" style="margin-bottom: 15px;">
578 <label>
579 <?php _e( 'Storage Bucket:', ud_get_stateless_media()->domain ); ?>
580 <input type="text" class="widefat urlfield" readonly="readonly" value="gs://<?php echo esc_attr($sm_cloud[ 'bucket' ]); ?>" />
581 </label>
582 </div>
583 <?php
584 }
585
586 if ( current_user_can( 'upload_files' ) ) {
587 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
588 ?>
589 <a href="javascript:;" data-type="image" data-id="<?php echo $post->ID; ?>"
590 class="button-secondary sm_inline_sync"><?php _e('Regenerate and Sync with GCS', ud_get_stateless_media()->domain); ?></a>
591 <?php
592 }
593
594 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
595 ?>
596 <a href="javascript:;" data-type="other" data-id="<?php echo $post->ID; ?>"
597 class="button-secondary sm_inline_sync"><?php _e('Sync with GCS', ud_get_stateless_media()->domain); ?></a>
598 <?php
599 }
600 }
601 }
602
603 echo apply_filters( 'sm::attachment::meta', ob_get_clean(), $post->ID );
604 }
605
606 /**
607 * @param $current_path
608 * @return string
609 */
610 public function handle_root_dir( $current_path ) {
611 $root_dir = $this->get( 'sm.root_dir' );
612 $root_dir = trim( $root_dir, '/ ' ); // Remove any forward slash and empty space.
613
614 // skip adding root dir if it's already added.
615 if ( !empty( $root_dir ) && strpos($current_path, $root_dir) !== 0 ) {
616 return $root_dir . '/' . $current_path;
617 }
618
619 return $current_path;
620 }
621
622 /**
623 * @param $content
624 * @return mixed
625 */
626 public function the_content_filter( $content ) {
627
628 if ( $upload_data = wp_upload_dir() ) {
629
630 if ( !empty( $upload_data['baseurl'] ) && !empty( $content ) ) {
631 $baseurl = preg_replace('/https?:\/\//','',$upload_data['baseurl']);
632 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
633 $root_dir = !empty( $root_dir ) ? $root_dir . '/' : false;
634 $image_host = $this->get_gs_host();
635 $file_ext = $this->replaceable_file_types();
636 $content = preg_replace( '/(href|src)=(\'|")(https?:\/\/'.str_replace('/', '\/', $baseurl).')\/(.+?)('.$file_ext.')(\'|")/i',
637 '$1=$2'.$image_host.'/'.($root_dir?$root_dir:'').'$4$5$6', $content);
638 }
639 }
640
641 return $content;
642 }
643
644 /**
645 * Return file types supported by File URL Replacement.
646 *
647 */
648 public function replaceable_file_types(){
649 $types = $this->get('sm.body_rewrite_types');
650
651 // Removing extra space.
652 $types = trim($types);
653 $types = preg_replace("/\s{2,}/", ' ', $types);
654
655 $types_arr = explode(' ', $types);
656 return '\.' . implode('|\.', $types_arr);
657 }
658
659 /**
660 * Copied from https://developer.wordpress.org/reference/functions/get_metadata/
661 *
662 * @param $value null unless other filter hooked in this function.
663 * @param $object_id post id
664 * @param $meta_key
665 * @param $single
666 * @return mixed
667 */
668 public function post_metadata_filter($value, $object_id, $meta_key, $single){
669 if(empty($value)){
670 $meta_type = 'post';
671 $transient_key = "stateless_{$meta_type}_meta";
672
673 $meta_cache = wp_cache_get($object_id, $transient_key);
674 if(empty($meta_cache)){
675 $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
676
677 if ( !$meta_cache ) {
678 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
679 $meta_cache = $meta_cache[$object_id];
680 }
681
682 foreach($meta_cache as $key => $meta){
683 $meta_cache[$key] = array_map('maybe_unserialize', $meta_cache[$key]);
684 }
685
686 $meta_cache = $this->convert_to_gs_link($meta_cache);
687 wp_cache_set($object_id, $meta_cache, $transient_key);
688 }
689
690 if ( ! $meta_key ) {
691 return $meta_cache;
692 }
693
694 if ( isset($meta_cache[$meta_key]) ) {
695 return $meta_cache[$meta_key];
696 }
697
698 // in case no metadata is found return what was passed in $value.
699 // $value most of the time is null.
700 return $value;
701 }
702
703 return $this->convert_to_gs_link($value);
704
705 }
706
707 /**
708 * Replace all image link with gs link and return only if meta modified.
709 *
710 * @param $meta
711 * @return mixed or null when not changed.
712 */
713 public function convert_to_gs_link($meta){
714 $updated = $meta;
715 if ( $meta && $upload_data = wp_upload_dir() ) {
716 if ( !empty( $upload_data['baseurl'] ) && !empty( $meta ) ) {
717 $baseurl = preg_replace('/https?:\/\//','',$upload_data['baseurl']);
718 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
719 $root_dir = !empty( $root_dir ) ? $root_dir . '/': false;
720 $image_host = $this->get_gs_host().'/'.($root_dir?$root_dir:'');
721 $file_ext = $this->replaceable_file_types();
722 $updated = $this->_convert_to_gs_link($meta, $image_host, $baseurl, $file_ext);
723 }
724 }
725
726 if($updated == $meta){
727 return null; // Not changed.
728 }
729 return $updated;
730 }
731
732 /**
733 * Replace all image link with gs link
734 *
735 * @param $meta
736 * @return mixed
737 */
738 public function _convert_to_gs_link($meta, $image_host, $baseurl, $file_ext){
739 if(is_array($meta)){
740 foreach ($meta as $key => $value) {
741 $meta[$key] = $this->_convert_to_gs_link($value, $image_host, $baseurl, $file_ext);
742 }
743 return $meta;
744 } elseif (is_object($meta) && $meta instanceof \stdClass ) {
745 foreach (get_object_vars($meta) as $key => $value) {
746 $meta->{$key} = $this->_convert_to_gs_link($value, $image_host, $baseurl, $file_ext);
747 }
748 return $meta;
749 } elseif(is_string($meta)){
750 return preg_replace( '/(https?:\/\/'.str_replace('/', '\/', $baseurl).')\/(.+?)(' . $file_ext . ')/i', $image_host.'$2$3', $meta);
751 }
752
753 return $meta;
754 }
755
756
757 /**
758 * @param $links
759 * @param $file
760 * @return mixed
761 */
762 public function plugin_action_links( $links, $file ) {
763
764 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless-media.php' ) ) {
765 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
766 array_unshift( $links, $settings_link );
767 }
768
769 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless.php' ) ) {
770 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
771 array_unshift( $links, $settings_link );
772 }
773
774 return $links;
775 }
776
777 /**
778 * Determines if plugin is loaded via mu-plugins
779 * or Network Enabled.
780 *
781 * @author peshkov@UD
782 */
783 public function is_network_detected() {
784 /* Plugin is loaded via mu-plugins. */
785
786 if( strpos( Utility::normalize_path( $this->root_path ), Utility::normalize_path( WPMU_PLUGIN_DIR ) ) !== false ) {
787 return true;
788 }
789
790 if( is_multisite() ) {
791 /* Looks through network enabled plugins to see if our one is there. */
792 foreach (wp_get_active_network_plugins() as $path) {
793 // Trying again using readlink in case it's a symlink file.
794 // boot_file is already solved.
795 // wp_normalize_path is helpfull in windows.
796 if (wp_normalize_path($this->boot_file) == wp_normalize_path($path) || (is_link($path) AND $this->boot_file == readlink($path))) {
797 return true;
798 }
799 }
800 }
801 return false;
802 }
803
804 /**
805 * Initialization.
806 * Register scripts and styles
807 */
808 public function admin_init() {
809 $this->show_notice_stateless_cache_busting();
810 wp_register_style( 'wp-stateless', $this->path( 'static/styles/wp-stateless.css', 'url' ), array(), self::$version );
811
812 /* Attachment or upload page */
813 wp_register_script( 'wp-stateless-uploads-js', $this->path( 'static/scripts/wp-stateless-uploads.js', 'url' ), array( 'jquery' ), self::$version );
814
815 /* Setup wizard styles and scripts. */
816 wp_register_style( 'wp-stateless-bootstrap', $this->path( 'static/styles/bootstrap.min.css', 'url' ), array(), '3.3.7' );
817 wp_register_style( 'bootstrap-grid-v4', $this->path( 'static/styles/bootstrap-grid.min.css', 'url' ), array(), '3.3.7' );
818 wp_register_style( 'wp-stateless-setup-wizard', $this->path( 'static/styles/wp-stateless-setup-wizard.css', 'url' ), array(), self::$version );
819
820 wp_register_script( 'async.min', ud_get_stateless_media()->path( 'static/scripts/async.js', 'url' ), array(), ud_get_stateless_media()->version );
821 wp_register_script( 'jquery.history', ud_get_stateless_media()->path( 'static/scripts/jquery.history.js', 'url' ), array( 'jquery' ), ud_get_stateless_media()->version, true );
822 wp_register_script( 'wp-stateless-validation', ud_get_stateless_media()->path( 'static/scripts/jquery.validation.js', 'url' ), array( 'jquery' ), ud_get_stateless_media()->version, true );
823 wp_register_script( 'wp-stateless-loading', ud_get_stateless_media()->path( 'static/scripts/jquery.loading.js', 'url' ), array( 'jquery' ), ud_get_stateless_media()->version, true );
824 wp_register_script( 'wp-stateless-comboBox', ud_get_stateless_media()->path( 'static/scripts/jquery.wp-stateless-combo-box.js', 'url' ), array( 'jquery' ), ud_get_stateless_media()->version, true );
825 wp_register_script( 'wp-stateless-setup', ud_get_stateless_media()->path( 'static/scripts/wp-stateless-setup.js', 'url' ), array( 'jquery-ui-core', 'wp-api', 'jquery.history' ), ud_get_stateless_media()->version, true );
826 wp_localize_script( 'wp-stateless-setup', 'stateless_l10n', $this->get_l10n_data() );
827
828 wp_register_script( 'wp-stateless-setup-wizard-js', ud_get_stateless_media()->path( 'static/scripts/wp-stateless-setup-wizard.js', 'url' ), array( 'jquery', 'wp-api', 'async.min', 'wp-stateless-setup', 'wp-stateless-comboBox', 'wp-stateless-validation', 'wp-stateless-loading' ), ud_get_stateless_media()->version, true );
829
830
831 /* Stateless settings page */
832 wp_register_script( 'wp-stateless-settings', ud_get_stateless_media()->path( 'static/scripts/wp-stateless-settings.js', 'url' ), array(), ud_get_stateless_media()->version );
833 wp_localize_script( 'wp-stateless-settings', 'stateless_l10n', $this->get_l10n_data() );
834
835 wp_register_style( 'wp-stateless-settings', $this->path( 'static/styles/wp-stateless-settings.css', 'url' ), array(), self::$version );
836
837 // Sync tab
838 if ( wp_script_is( 'jquery-ui-widget', 'registered' ) ){
839 wp_register_script( 'jquery-ui-progressbar', ud_get_stateless_media()->path('static/scripts/jquery-ui/jquery.ui.progressbar.min.js', 'url'), array( 'jquery-ui-core', 'jquery-ui-widget' ), '1.8.6' );
840 }
841 else{
842 wp_register_script( 'jquery-ui-progressbar', ud_get_stateless_media()->path( 'static/scripts/jquery-ui/jquery.ui.progressbar.min.1.7.2.js', 'url' ), array( 'jquery-ui-core' ), '1.7.2' );
843 }
844 wp_register_script( 'wp-stateless-angular', ud_get_stateless_media()->path( 'static/scripts/angular.min.js', 'url' ), array(), '1.5.0', true );
845 wp_register_script( 'wp-stateless', ud_get_stateless_media()->path( 'static/scripts/wp-stateless.js', 'url' ), array( 'jquery-ui-core', 'wp-stateless-settings' ), ud_get_stateless_media()->version, true );
846
847 wp_localize_script( 'wp-stateless', 'stateless_l10n', $this->get_l10n_data() );
848 wp_localize_script('wp-stateless', 'wp_stateless_configs', array(
849 'WP_DEBUG' => defined('WP_DEBUG') ? WP_DEBUG : false,
850 ));
851
852 $settings = ud_get_stateless_media()->get('sm');
853 if(defined('WP_STATELESS_MEDIA_JSON_KEY') && WP_STATELESS_MEDIA_JSON_KEY){
854 $settings['key_json'] = "Currently configured via a constant.";
855 }
856 wp_localize_script('wp-stateless', 'wp_stateless_settings', $settings);
857 wp_localize_script('wp-stateless', 'wp_stateless_compatibility', Module::get_modules());
858 wp_register_style( 'jquery-ui-regenthumbs', ud_get_stateless_media()->path( 'static/scripts/jquery-ui/redmond/jquery-ui-1.7.2.custom.css', 'url' ), array(), '1.7.2' );
859
860 }
861
862 /**
863 * Get_l10n_data
864 *
865 * @param string $value
866 * @return mixed
867 */
868 public function get_l10n_data($value=''){
869 include ud_get_stateless_media()->path( 'l10n.php', 'dir');
870 return $l10n;
871 }
872
873 /**
874 * Admin Scripts
875 *
876 * @param $hook
877 */
878 public function admin_enqueue_scripts( $hook ) {
879
880 switch( $hook ) {
881
882 case 'options-media.php':
883 //wp_enqueue_script( 'wp-api' );
884
885 wp_enqueue_style( 'wp-stateless');
886 wp_enqueue_script( 'wp-stateless-setup' );
887 break;
888
889 case 'upload.php':
890
891 wp_enqueue_style( 'wp-stateless');
892 wp_enqueue_script( 'wp-stateless-uploads-js' );
893
894 break;
895
896 case 'post.php':
897
898 global $post;
899
900 if ( $post->post_type == 'attachment' ) {
901 wp_enqueue_style( 'wp-stateless');
902 wp_enqueue_script( 'wp-stateless-uploads-js' );
903 }
904
905 break;
906
907 case 'media_page_stateless-setup':
908 wp_enqueue_style( 'wp-stateless');
909 wp_enqueue_style( 'wp-stateless-bootstrap' );
910 wp_enqueue_style( 'wp-stateless-setup-wizard' );
911
912 wp_enqueue_script( 'async.min' );
913 wp_enqueue_script( 'jquery.history' );
914 wp_enqueue_script( 'wp-stateless-validation' );
915 wp_enqueue_script( 'wp-stateless-loading' );
916 wp_enqueue_script( 'wp-stateless-comboBox' );
917 wp_enqueue_script( 'wp-stateless-setup' );
918 wp_enqueue_script( 'wp-stateless-setup-wizard-js' );
919 break;
920 case 'media_page_stateless-settings':
921 wp_enqueue_style( 'wp-stateless');
922 wp_enqueue_script( 'wp-stateless-settings' );
923 wp_enqueue_style( 'bootstrap-grid-v4' );
924 wp_enqueue_style( 'wp-stateless-settings' );
925
926 // Sync tab
927 wp_enqueue_script( 'jquery-ui-progressbar' );
928 wp_enqueue_script( 'wp-stateless-angular' );
929 wp_enqueue_script( 'wp-stateless' );
930 wp_enqueue_style( 'jquery-ui-regenthumbs' );
931
932 $data = array(
933 'key' => 'stateless-cache-busting',
934 'class' => 'notice',
935 'title' => sprintf( __( "Stateless mode enables and requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
936 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
937 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.
938 Override with the <a href='%s' target='_blank'>%s</a> constant.", ud_get_stateless_media()->domain),"https://github.com/wpCloud/wp-stateless/wiki/Constants#wp_stateless_media_cache_busting", "WP_STATELESS_MEDIA_CACHE_BUSTING" ),
939 );
940 echo "<script id='template-stateless-cache-busting' type='text/html'>";
941 include ud_get_stateless_media()->path( '/static/views/error-notice.php', 'dir' );
942 echo "</script>";
943 default: break;
944 }
945
946 }
947
948 /**
949 * Add Attributes to media HTML
950 *
951 * @author potanin@UD
952 * @param $attr
953 * @param $attachment
954 * @param $size
955 * @return mixed
956 */
957 public function wp_get_attachment_image_attributes( $attr, $attachment, $size = null ) {
958
959 $sm_cloud = get_post_meta( $attachment->ID, 'sm_cloud', true );
960 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'name' ] ) ) {
961 $attr[ 'class' ] = $attr[ 'class' ] . ' wp-stateless-item';
962 $attr[ 'data-image-size' ] = is_array( $size ) ? implode( 'x', $size ) : $size;
963 $attr[ 'data-stateless-media-bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
964 $attr[ 'data-stateless-media-name' ] = $sm_cloud[ 'name' ];
965 }
966
967 return $attr;
968
969 }
970
971 /**
972 * Adds filter link to Media Library table.
973 *
974 * @param $views
975 * @return mixed
976 */
977 public function views_upload( $views ) {
978 $views['stateless'] = '<a href="#">' . __( 'Stateless Media' ) . '</a>';
979 return $views;
980 }
981
982 /**
983 * Replace media URL
984 *
985 * @param bool $false
986 * @param integer $id
987 * @param string $size
988 * @return mixed $false
989 */
990 public function image_downsize( $false = false, $id, $size ) {
991
992 if ( !isset( $this->client ) || !$this->client || is_wp_error( $this->client ) ) {
993 return $false;
994 }
995
996 /**
997 * Check if enabled
998 */
999 if ( $this->get( 'sm.mode' ) !== 'cdn' && $this->get( 'sm.mode' ) !== 'stateless' ) {
1000 return $false;
1001 }
1002
1003 /** Start determine remote file */
1004 $img_url = wp_get_attachment_url($id);
1005 $meta = wp_get_attachment_metadata($id);
1006 $width = $height = 0;
1007 $is_intermediate = false;
1008
1009 //** try for a new style intermediate size */
1010 if ( $intermediate = image_get_intermediate_size( $id, $size ) ) {
1011 if(!empty( $intermediate['gs_link'] )){
1012 $img_url = $intermediate['gs_link'];
1013 }
1014 else if(!empty( $intermediate['url'] )){
1015 $img_url = $intermediate['url'];
1016 }
1017 else{
1018 $img_url = dirname($img_url) . $intermediate['file'];
1019 }
1020
1021 $width = $intermediate['width'];
1022 $height = $intermediate['height'];
1023 $is_intermediate = true;
1024 }
1025
1026 /**
1027 * maybe try to get images info from sm_cloud
1028 * this case may happen when no local files
1029 * @author korotkov@ud
1030 */
1031 if ( !$width && !$height ) {
1032 $sm_cloud = get_post_meta( $id, 'sm_cloud', true );
1033 if ( is_string($size) && !empty( $sm_cloud['sizes'] ) && !empty( $sm_cloud['sizes'][$size] ) ) {
1034 global $_wp_additional_image_sizes;
1035
1036 $img_url = !empty( $sm_cloud['sizes'][$size]['fileLink'] ) ? $sm_cloud['sizes'][$size]['fileLink'] : $img_url;
1037
1038 if ( !empty( $_wp_additional_image_sizes[ $size ] ) ) {
1039 $width = !empty( $_wp_additional_image_sizes[ $size ]['width'] ) ? $_wp_additional_image_sizes[ $size ]['width'] : $width;
1040 $height = !empty( $_wp_additional_image_sizes[ $size ]['height'] ) ? $_wp_additional_image_sizes[ $size ]['height'] : $height;
1041 }
1042
1043 $is_intermediate = true;
1044 }
1045 }
1046
1047 if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
1048
1049 //** any other type: use the real image */
1050 $width = $meta['width'];
1051 $height = $meta['height'];
1052 }
1053
1054
1055 if ( $img_url) {
1056
1057 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
1058 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
1059 $img_url = apply_filters('wp_stateless_bucket_link', $img_url);
1060 return array( $img_url, $width, $height, $is_intermediate );
1061 }
1062
1063
1064 /**
1065 * All other cases work as usually
1066 */
1067 return $false;
1068
1069 }
1070
1071 /**
1072 * Extends metadata by adding GS information.
1073 * Note: must not be called directly. It's used only on hook
1074 *
1075 * @param $metadata
1076 * @param $attachment_id
1077 * @return array|mixed
1078 */
1079 public function wp_get_attachment_metadata( $metadata, $attachment_id ) {
1080 /* Determine if the media file has GS data at all. */
1081 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
1082 // If metadata not passed the get metadata from post meta.
1083 if(empty($metadata)){
1084 $metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
1085 }
1086
1087 if( is_array( $metadata ) && is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1088 $metadata[ 'gs_link' ] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'fileLink' ]);
1089 $metadata[ 'gs_name' ] = isset( $sm_cloud[ 'name' ] ) ? $sm_cloud[ 'name' ] : false;
1090 $metadata[ 'gs_bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
1091 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
1092 foreach( $metadata[ 'sizes' ] as $k => $v ) {
1093 if( !empty( $sm_cloud[ 'sizes' ][ $k ][ 'name' ] ) ) {
1094 $metadata['sizes'][$k]['gs_name'] = $sm_cloud[ 'sizes' ][ $k ][ 'name' ];
1095 $metadata['sizes'][$k]['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'sizes' ][ $k ][ 'fileLink' ]);
1096 }
1097 }
1098 }
1099 }
1100
1101 return $metadata;
1102 }
1103
1104 /**
1105 * Returns client object
1106 * or WP_Error on failure.
1107 *
1108 * @author peshkov@UD
1109 * @return object $this->client. \wpCloud\StatelessMedia\GS_Client or \WP_Error
1110 */
1111 public function get_client() {
1112
1113 if( null === $this->client ) {
1114
1115 $key_json = get_site_option( 'sm_key_json' );
1116 if ( empty($key_json) ) {
1117 $key_json = $this->get( 'sm.key_json' );
1118 }
1119
1120 /* Try to initialize GS Client */
1121 $this->client = GS_Client::get_instance( array(
1122 'bucket' => $this->get( 'sm.bucket' ),
1123 'key_json' => $key_json
1124 ) );
1125 }
1126
1127 return $this->client;
1128
1129 }
1130
1131 /**
1132 * Determines if we can connect to Google Storage Bucket.
1133 *
1134 * @author peshkov@UD
1135 */
1136 public function is_connected_to_gs() {
1137
1138 $trnst = get_transient( 'sm::is_connected_to_gs' );
1139
1140 if ( empty($trnst) || false === $trnst || !isset( $trnst[ 'hash' ] ) || $trnst[ 'hash' ] != md5( serialize( $this->get( 'sm' ) ) ) ) {
1141 $trnst = array(
1142 'success' => 'true',
1143 'error' => '',
1144 'hash' => md5( serialize( $this->get( 'sm' ) ) ),
1145 );
1146 $client = $this->get_client();
1147
1148 if ( is_wp_error( $client ) ) {
1149 $trnst[ 'success' ] = 'false';
1150 $trnst[ 'error' ] = $client->get_error_message();
1151 } else {
1152 $connected = $client->is_connected();
1153 if( $connected !== true ) {
1154 $trnst[ 'success' ] = 'false';
1155 $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' ) );
1156
1157 if( is_callable(array($connected, 'getHandlerContext')) && $handlerContext = $connected->getHandlerContext() ){
1158 if(!empty($handlerContext['error'])){
1159 $handlerContext['error'];
1160 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($handlerContext['error']);
1161 }
1162 }
1163
1164 if( is_callable(array($connected, 'getErrors')) && $error = $connected->getErrors() ){
1165 $error = reset($error);
1166 if($error['reason'] == 'accessNotConfigured')
1167 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($error['message']);
1168 }
1169 }
1170 }
1171 set_transient( 'sm::is_connected_to_gs', $trnst, 4 * HOUR_IN_SECONDS );
1172 }
1173
1174 if( isset( $trnst[ 'success' ] ) && $trnst[ 'success' ] == 'false' ) {
1175 return new \WP_Error( 'error', ( !empty( $trnst[ 'error' ] ) ? $trnst[ 'error' ] : __( 'There is an Error on connection to Google Storage.', $this->domain ) ) );
1176 }
1177
1178 return true;
1179 }
1180
1181 /**
1182 * Flush all plugin transients
1183 *
1184 */
1185 public function flush_transients() {
1186 delete_transient( 'sm::is_connected_to_gs' );
1187 }
1188
1189 /**
1190 * Plugin Activation
1191 *
1192 */
1193 public function activate() {
1194 add_action( 'activated_plugin', array($this, 'redirect_to_splash'), 99 );
1195 $this->run_upgrade_process();
1196 }
1197
1198 /**
1199 * Run Install Process.
1200 * Triggered on plugins_loaded instead of register_activation_hook action.
1201 * Works on even manual plugin update.
1202 *
1203 * @author alim@UD
1204 */
1205 public function run_install_process()
1206 {
1207 // calling the upgrade function because it's same as this point for fresh install or updates.
1208 $this->run_upgrade_process();
1209 }
1210
1211 /**
1212 * Run Upgrade Process:
1213 * Triggered on plugins_loaded instead of register_activation_hook action.
1214 * Works on even manual plugin update.
1215 *
1216 * @author alim@UD
1217 */
1218 public function run_upgrade_process()
1219 {
1220 // Creating database on new installation.
1221 $this->create_db();
1222 /**
1223 * Maybe Upgrade current Version
1224 */
1225 Upgrader::call( $this->args[ 'version' ] );
1226 }
1227
1228 /**
1229 * Create database on plugin activation.
1230 * @param boolean $force - whether to create db even if option exists. For debug purpose only.
1231 */
1232 public function create_db($force = false) {
1233 global $wpdb;
1234 $sm_sync_db_version = get_option( 'sm_sync_db_version' );
1235
1236 if( $sm_sync_db_version && $force == false ) {
1237 return;
1238 }
1239
1240 $table_name = $wpdb->prefix . 'sm_sync';
1241 $charset_collate = $wpdb->get_charset_collate();
1242
1243 // `expire` timestamp NULL DEFAULT NULL,
1244 $sql = "CREATE TABLE $table_name (
1245 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT ,
1246 `file` varchar(255) NOT NULL ,
1247 `status` varchar(10) NOT NULL ,
1248 PRIMARY KEY (`id`)
1249 ) $charset_collate;";
1250
1251 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
1252 dbDelta( $sql );
1253
1254 add_option( 'sm_sync_db_version', $this->args[ 'version' ] );
1255 }
1256
1257 /**
1258 * Redirect_to_splash
1259 *
1260 * @param string $plugin
1261 */
1262 public function redirect_to_splash($plugin =''){
1263 $this->settings = new Settings();
1264
1265 if(defined( 'WP_CLI' ) || $this->settings->get('sm.key_json') || isset($_POST['checked']) && count($_POST['checked']) > 1){
1266 return;
1267 }
1268
1269 if(
1270 !$this->settings->get('sm.key_json') &&
1271 defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true &&
1272 defined('WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL') && WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL == true
1273 ) {
1274 return;
1275 }
1276
1277 if( !$this->settings->get('sm.key_json') && defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true ) {
1278 $url = $this->get_settings_page_url('?page=stateless-settings');
1279 exit( wp_redirect($url));
1280 }
1281
1282 if( $plugin == plugin_basename( $this->boot_file ) ) {
1283 $url = $this->get_settings_page_url('?page=stateless-setup&step=splash-screen');
1284 if(json_decode($this->settings->get('sm.key_json'))){
1285 $url = $this->get_settings_page_url('?page=stateless-settings');
1286 }
1287 exit( wp_redirect($url));
1288 }
1289 }
1290
1291 /**
1292 * Plugin Deactivation
1293 *
1294 */
1295 public function deactivate() {}
1296
1297 /**
1298 * Show_notice_stateless_cache_busting
1299 *
1300 */
1301 public function show_notice_stateless_cache_busting(){
1302 $this->errors->add( array(
1303 'key' => 'stateless_cache_busting',
1304 'button' => 'View Settings',
1305 'button_link' => admin_url('upload.php?page=stateless-settings'),
1306 'title' => sprintf( __( "Stateless mode now requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1307 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1308 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.
1309 Override with the <a href='%s' target='_blank'>%s</a> constant.", ud_get_stateless_media()->domain),"https://github.com/wpCloud/wp-stateless/wiki/Constants#wp_stateless_media_cache_busting", "WP_STATELESS_MEDIA_CACHE_BUSTING" ),
1310 ), 'notice' );
1311 }
1312
1313 /**
1314 * Filter for wp_get_attachment_url();
1315 *
1316 * @param string $url
1317 * @param string $post_id
1318 * @return mixed|null|string
1319 */
1320 public function wp_get_attachment_url( $url = '', $post_id = '' ) {
1321 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
1322 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1323 $_url = parse_url($sm_cloud[ 'fileLink' ]);
1324 $url = !isset($_url['scheme']) ? ( 'https:' . $sm_cloud[ 'fileLink' ] ) : $sm_cloud[ 'fileLink' ];
1325 return apply_filters('wp_stateless_bucket_link', $url);
1326 }
1327 return $url;
1328 }
1329
1330 /**
1331 * Filter for attachment_url_to_postid()
1332 *
1333 * @param int|false $post_id originally found post ID (or false if not found)
1334 * @param string $url the URL to find the post ID for
1335 * @return int|false found post ID from cloud storage URL
1336 */
1337 public function attachment_url_to_postid( $post_id, $url ) {
1338 global $wpdb;
1339
1340 if ( ! $post_id ) {
1341 $post_id = get_transient("stateless_url_to_postid_" . md5($url));
1342
1343 if(defined('WP_STATELESS_LEGACY_URL_TO_POSTID')){
1344 // User can use this constant if they change the Bucket Folder (root_dir) after uploading image.
1345 // This can be little slow at first run.
1346 if(empty($post_id)){
1347 $query = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'sm_cloud' AND meta_value LIKE '%s'";
1348 $post_id = $wpdb->get_var( $wpdb->prepare( $query, '%' . $url . '%' ) );
1349
1350 if($post_id){
1351 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1352 }
1353 }
1354 return $post_id;
1355 }
1356
1357 if(empty($post_id)){
1358 $gs_base_url = $this->get_gs_host();
1359 $gs_url = $this->get_gs_host() . '/' . $this->get( 'sm.root_dir' );
1360 $site_url = parse_url($gs_url);
1361 $image_path = parse_url( $url );
1362
1363 //force the protocols to match if needed
1364 if( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
1365 $url = str_replace( $image_path['scheme'], $site_url['scheme'], $url );
1366 }
1367
1368 if( 0 === strpos( $url, $gs_url . '/' ) ) {
1369 $url = substr( $url, strlen( $gs_url . '/' ) );
1370 }
1371 else if( 0 === strpos( $url, $gs_base_url . '/' ) ) {
1372 // In case user added Bucket Folder (root_dir) after uploading image.
1373 $url = substr( $url, strlen( $gs_base_url . '/' ) );
1374 }
1375
1376 $sql = $wpdb->prepare(
1377 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
1378 $url
1379 );
1380 $post_id = $wpdb->get_var( $sql );
1381
1382 if($post_id){
1383 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1384 }
1385 }
1386 }
1387
1388 return $post_id;
1389 }
1390
1391 /**
1392 * Upload the full size image first.
1393 *
1394 */
1395 public function before_intermediate_image_sizes($sizes, $metadata = array()){
1396 if(empty($metadata)){
1397 return $sizes;
1398 }
1399
1400 try{
1401 $attachment_id = attachment_url_to_postid($metadata['file']);
1402 $this->add_media(null, $attachment_id, false, array('no_thumb' => true));
1403 }
1404 catch(Exception $e){
1405
1406 }
1407
1408 return $sizes;
1409 }
1410
1411 /**
1412 * Change Upload BaseURL when CDN Used.
1413 *
1414 * @param $data
1415 * @return mixed
1416 */
1417 public function upload_dir( $data ) {
1418 $data[ 'basedir' ] = $this->get_gs_host();
1419 $data[ 'baseurl' ] = $this->get_gs_host();
1420 $data[ 'url' ] = $data[ 'baseurl' ] . $data[ 'subdir' ];
1421
1422 return $data;
1423
1424 }
1425
1426 /**
1427 * Set Feature Flag constants by parsing composer.json
1428 *
1429 * @todo Make sure settings from DB can override these.
1430 *
1431 * @author potanin@UD
1432 * @return array|mixed|null|object
1433 */
1434 public function parse_feature_flags( ) {
1435
1436 try {
1437
1438 $_raw = file_get_contents( Utility::normalize_path( $this->root_path ) . 'composer.json' );
1439
1440 $_parsed = json_decode( $_raw );
1441
1442 // @todo Catch poorly formatted JSON.
1443 if( !is_object( $_parsed ) ) {
1444 // throw new Error( "unable to parse." );
1445 }
1446
1447 foreach( (array) $_parsed->extra->featureFlags as $_feature ) {
1448
1449 if( !defined( $_feature->constant ) ) {
1450 define( $_feature->constant, $_feature->enabled );
1451
1452 if( $_feature->enabled ) {
1453 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] enabled.' );
1454 } else {
1455 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] disabled.' );
1456 }
1457 }
1458
1459 }
1460
1461 } catch ( Exception $e ) {
1462 Utility::log( 'Unable to parse [composer.json] feature flags. Error: [' . $e->getMessage() . ']' );
1463 // echo 'Caught exception: ', $e->getMessage(), "\n";
1464 }
1465
1466 return isset( $_parsed ) ? $_parsed : null;
1467
1468 }
1469
1470 /**
1471 * Determine if Utility class contains missed function
1472 * in other case, just return NULL to prevent ERRORS
1473 *
1474 * @author peshkov@UD
1475 * @param $name
1476 * @param $arguments
1477 * @return mixed|null
1478 */
1479 public function __call( $name, $arguments ) {
1480 if( is_callable( array( "wpCloud\\StatelessMedia\\Utility", $name ) ) ) {
1481 return call_user_func_array( array( "wpCloud\\StatelessMedia\\Utility", $name ), $arguments );
1482 } else {
1483 return NULL;
1484 }
1485 }
1486 }
1487 }
1488 }
1489