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

1,492 lines 54.6 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 case 'settings_page_stateless-setup':
909 wp_enqueue_style( 'wp-stateless');
910 wp_enqueue_style( 'wp-stateless-bootstrap' );
911 wp_enqueue_style( 'wp-stateless-setup-wizard' );
912
913 wp_enqueue_script( 'async.min' );
914 wp_enqueue_script( 'jquery.history' );
915 wp_enqueue_script( 'wp-stateless-validation' );
916 wp_enqueue_script( 'wp-stateless-loading' );
917 wp_enqueue_script( 'wp-stateless-comboBox' );
918 wp_enqueue_script( 'wp-stateless-setup' );
919 wp_enqueue_script( 'wp-stateless-setup-wizard-js' );
920 break;
921 case 'media_page_stateless-settings':
922 case 'settings_page_stateless-settings':
923 wp_enqueue_style( 'wp-stateless');
924 wp_enqueue_script( 'wp-stateless-settings' );
925 wp_enqueue_style( 'bootstrap-grid-v4' );
926 wp_enqueue_style( 'wp-stateless-settings' );
927
928 // Sync tab
929 wp_enqueue_script( 'jquery-ui-progressbar' );
930 wp_enqueue_script( 'wp-stateless-angular' );
931 wp_enqueue_script( 'wp-stateless' );
932 wp_enqueue_style( 'jquery-ui-regenthumbs' );
933
934 $data = array(
935 'key' => 'stateless-cache-busting',
936 'class' => 'notice',
937 'title' => sprintf( __( "Stateless mode enables and requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
938 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
939 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.
940 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" ),
941 );
942 echo "<script id='template-stateless-cache-busting' type='text/html'>";
943 include ud_get_stateless_media()->path( '/static/views/error-notice.php', 'dir' );
944 echo "</script>";
945 break;
946 default: break;
947 }
948
949 }
950
951 /**
952 * Add Attributes to media HTML
953 *
954 * @author potanin@UD
955 * @param $attr
956 * @param $attachment
957 * @param $size
958 * @return mixed
959 */
960 public function wp_get_attachment_image_attributes( $attr, $attachment, $size = null ) {
961
962 $sm_cloud = get_post_meta( $attachment->ID, 'sm_cloud', true );
963 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'name' ] ) ) {
964 $attr[ 'class' ] = $attr[ 'class' ] . ' wp-stateless-item';
965 $attr[ 'data-image-size' ] = is_array( $size ) ? implode( 'x', $size ) : $size;
966 $attr[ 'data-stateless-media-bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
967 $attr[ 'data-stateless-media-name' ] = $sm_cloud[ 'name' ];
968 }
969
970 return $attr;
971
972 }
973
974 /**
975 * Adds filter link to Media Library table.
976 *
977 * @param $views
978 * @return mixed
979 */
980 public function views_upload( $views ) {
981 $views['stateless'] = '<a href="#">' . __( 'Stateless Media' ) . '</a>';
982 return $views;
983 }
984
985 /**
986 * Replace media URL
987 *
988 * @param bool $false
989 * @param integer $id
990 * @param string $size
991 * @return mixed $false
992 */
993 public function image_downsize( $false = false, $id, $size ) {
994
995 if ( !isset( $this->client ) || !$this->client || is_wp_error( $this->client ) ) {
996 return $false;
997 }
998
999 /**
1000 * Check if enabled
1001 */
1002 if ( $this->get( 'sm.mode' ) !== 'cdn' && $this->get( 'sm.mode' ) !== 'stateless' ) {
1003 return $false;
1004 }
1005
1006 /** Start determine remote file */
1007 $img_url = wp_get_attachment_url($id);
1008 $meta = wp_get_attachment_metadata($id);
1009 $width = $height = 0;
1010 $is_intermediate = false;
1011
1012 //** try for a new style intermediate size */
1013 if ( $intermediate = image_get_intermediate_size( $id, $size ) ) {
1014 if(!empty( $intermediate['gs_link'] )){
1015 $img_url = $intermediate['gs_link'];
1016 }
1017 else if(!empty( $intermediate['url'] )){
1018 $img_url = $intermediate['url'];
1019 }
1020 else{
1021 $img_url = dirname($img_url) . $intermediate['file'];
1022 }
1023
1024 $width = $intermediate['width'];
1025 $height = $intermediate['height'];
1026 $is_intermediate = true;
1027 }
1028
1029 /**
1030 * maybe try to get images info from sm_cloud
1031 * this case may happen when no local files
1032 * @author korotkov@ud
1033 */
1034 if ( !$width && !$height ) {
1035 $sm_cloud = get_post_meta( $id, 'sm_cloud', true );
1036 if ( is_string($size) && !empty( $sm_cloud['sizes'] ) && !empty( $sm_cloud['sizes'][$size] ) ) {
1037 global $_wp_additional_image_sizes;
1038
1039 $img_url = !empty( $sm_cloud['sizes'][$size]['fileLink'] ) ? $sm_cloud['sizes'][$size]['fileLink'] : $img_url;
1040
1041 if ( !empty( $_wp_additional_image_sizes[ $size ] ) ) {
1042 $width = !empty( $_wp_additional_image_sizes[ $size ]['width'] ) ? $_wp_additional_image_sizes[ $size ]['width'] : $width;
1043 $height = !empty( $_wp_additional_image_sizes[ $size ]['height'] ) ? $_wp_additional_image_sizes[ $size ]['height'] : $height;
1044 }
1045
1046 $is_intermediate = true;
1047 }
1048 }
1049
1050 if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
1051
1052 //** any other type: use the real image */
1053 $width = $meta['width'];
1054 $height = $meta['height'];
1055 }
1056
1057
1058 if ( $img_url) {
1059
1060 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
1061 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
1062 $img_url = apply_filters('wp_stateless_bucket_link', $img_url);
1063 return array( $img_url, $width, $height, $is_intermediate );
1064 }
1065
1066
1067 /**
1068 * All other cases work as usually
1069 */
1070 return $false;
1071
1072 }
1073
1074 /**
1075 * Extends metadata by adding GS information.
1076 * Note: must not be called directly. It's used only on hook
1077 *
1078 * @param $metadata
1079 * @param $attachment_id
1080 * @return array|mixed
1081 */
1082 public function wp_get_attachment_metadata( $metadata, $attachment_id ) {
1083 /* Determine if the media file has GS data at all. */
1084 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
1085 // If metadata not passed the get metadata from post meta.
1086 if(empty($metadata)){
1087 $metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
1088 }
1089
1090 if( is_array( $metadata ) && is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1091 $metadata[ 'gs_link' ] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'fileLink' ]);
1092 $metadata[ 'gs_name' ] = isset( $sm_cloud[ 'name' ] ) ? $sm_cloud[ 'name' ] : false;
1093 $metadata[ 'gs_bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
1094 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
1095 foreach( $metadata[ 'sizes' ] as $k => $v ) {
1096 if( !empty( $sm_cloud[ 'sizes' ][ $k ][ 'name' ] ) ) {
1097 $metadata['sizes'][$k]['gs_name'] = $sm_cloud[ 'sizes' ][ $k ][ 'name' ];
1098 $metadata['sizes'][$k]['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'sizes' ][ $k ][ 'fileLink' ]);
1099 }
1100 }
1101 }
1102 }
1103
1104 return $metadata;
1105 }
1106
1107 /**
1108 * Returns client object
1109 * or WP_Error on failure.
1110 *
1111 * @author peshkov@UD
1112 * @return object $this->client. \wpCloud\StatelessMedia\GS_Client or \WP_Error
1113 */
1114 public function get_client() {
1115
1116 if( null === $this->client ) {
1117
1118 $key_json = get_site_option( 'sm_key_json' );
1119 if ( empty($key_json) ) {
1120 $key_json = $this->get( 'sm.key_json' );
1121 }
1122
1123 /* Try to initialize GS Client */
1124 $this->client = GS_Client::get_instance( array(
1125 'bucket' => $this->get( 'sm.bucket' ),
1126 'key_json' => $key_json
1127 ) );
1128 }
1129
1130 return $this->client;
1131
1132 }
1133
1134 /**
1135 * Determines if we can connect to Google Storage Bucket.
1136 *
1137 * @author peshkov@UD
1138 */
1139 public function is_connected_to_gs() {
1140
1141 $trnst = get_transient( 'sm::is_connected_to_gs' );
1142
1143 if ( empty($trnst) || false === $trnst || !isset( $trnst[ 'hash' ] ) || $trnst[ 'hash' ] != md5( serialize( $this->get( 'sm' ) ) ) ) {
1144 $trnst = array(
1145 'success' => 'true',
1146 'error' => '',
1147 'hash' => md5( serialize( $this->get( 'sm' ) ) ),
1148 );
1149 $client = $this->get_client();
1150
1151 if ( is_wp_error( $client ) ) {
1152 $trnst[ 'success' ] = 'false';
1153 $trnst[ 'error' ] = $client->get_error_message();
1154 } else {
1155 $connected = $client->is_connected();
1156 if( $connected !== true ) {
1157 $trnst[ 'success' ] = 'false';
1158 $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' ) );
1159
1160 if( is_callable(array($connected, 'getHandlerContext')) && $handlerContext = $connected->getHandlerContext() ){
1161 if(!empty($handlerContext['error'])){
1162 $handlerContext['error'];
1163 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($handlerContext['error']);
1164 }
1165 }
1166
1167 if( is_callable(array($connected, 'getErrors')) && $error = $connected->getErrors() ){
1168 $error = reset($error);
1169 if($error['reason'] == 'accessNotConfigured')
1170 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($error['message']);
1171 }
1172 }
1173 }
1174 set_transient( 'sm::is_connected_to_gs', $trnst, 4 * HOUR_IN_SECONDS );
1175 }
1176
1177 if( isset( $trnst[ 'success' ] ) && $trnst[ 'success' ] == 'false' ) {
1178 return new \WP_Error( 'error', ( !empty( $trnst[ 'error' ] ) ? $trnst[ 'error' ] : __( 'There is an Error on connection to Google Storage.', $this->domain ) ) );
1179 }
1180
1181 return true;
1182 }
1183
1184 /**
1185 * Flush all plugin transients
1186 *
1187 */
1188 public function flush_transients() {
1189 delete_transient( 'sm::is_connected_to_gs' );
1190 }
1191
1192 /**
1193 * Plugin Activation
1194 *
1195 */
1196 public function activate() {
1197 add_action( 'activated_plugin', array($this, 'redirect_to_splash'), 99 );
1198 $this->run_upgrade_process();
1199 }
1200
1201 /**
1202 * Run Install Process.
1203 * Triggered on plugins_loaded instead of register_activation_hook action.
1204 * Works on even manual plugin update.
1205 *
1206 * @author alim@UD
1207 */
1208 public function run_install_process()
1209 {
1210 // calling the upgrade function because it's same as this point for fresh install or updates.
1211 $this->run_upgrade_process();
1212 }
1213
1214 /**
1215 * Run Upgrade Process:
1216 * Triggered on plugins_loaded instead of register_activation_hook action.
1217 * Works on even manual plugin update.
1218 *
1219 * @author alim@UD
1220 */
1221 public function run_upgrade_process()
1222 {
1223 // Creating database on new installation.
1224 $this->create_db();
1225 /**
1226 * Maybe Upgrade current Version
1227 */
1228 Upgrader::call( $this->args[ 'version' ] );
1229 }
1230
1231 /**
1232 * Create database on plugin activation.
1233 * @param boolean $force - whether to create db even if option exists. For debug purpose only.
1234 */
1235 public function create_db($force = false) {
1236 global $wpdb;
1237 $sm_sync_db_version = get_option( 'sm_sync_db_version' );
1238
1239 if( $sm_sync_db_version && $force == false ) {
1240 return;
1241 }
1242
1243 $table_name = $wpdb->prefix . 'sm_sync';
1244 $charset_collate = $wpdb->get_charset_collate();
1245
1246 // `expire` timestamp NULL DEFAULT NULL,
1247 $sql = "CREATE TABLE $table_name (
1248 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT ,
1249 `file` varchar(255) NOT NULL ,
1250 `status` varchar(10) NOT NULL ,
1251 PRIMARY KEY (`id`)
1252 ) $charset_collate;";
1253
1254 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
1255 dbDelta( $sql );
1256
1257 add_option( 'sm_sync_db_version', $this->args[ 'version' ] );
1258 }
1259
1260 /**
1261 * Redirect_to_splash
1262 *
1263 * @param string $plugin
1264 */
1265 public function redirect_to_splash($plugin =''){
1266 $this->settings = new Settings();
1267
1268 if(defined( 'WP_CLI' ) || $this->settings->get('sm.key_json') || isset($_POST['checked']) && count($_POST['checked']) > 1){
1269 return;
1270 }
1271
1272 if(
1273 !$this->settings->get('sm.key_json') &&
1274 defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true &&
1275 defined('WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL') && WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL == true
1276 ) {
1277 return;
1278 }
1279
1280 if( !$this->settings->get('sm.key_json') && defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true ) {
1281 $url = $this->get_settings_page_url('?page=stateless-settings');
1282 exit( wp_redirect($url));
1283 }
1284
1285 if( $plugin == plugin_basename( $this->boot_file ) ) {
1286 $url = $this->get_settings_page_url('?page=stateless-setup&step=splash-screen');
1287 if(json_decode($this->settings->get('sm.key_json'))){
1288 $url = $this->get_settings_page_url('?page=stateless-settings');
1289 }
1290 exit( wp_redirect($url));
1291 }
1292 }
1293
1294 /**
1295 * Plugin Deactivation
1296 *
1297 */
1298 public function deactivate() {}
1299
1300 /**
1301 * Show_notice_stateless_cache_busting
1302 *
1303 */
1304 public function show_notice_stateless_cache_busting(){
1305 $this->errors->add( array(
1306 'key' => 'stateless_cache_busting',
1307 'button' => 'View Settings',
1308 'button_link' => admin_url('upload.php?page=stateless-settings'),
1309 'title' => sprintf( __( "Stateless mode now requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1310 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1311 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.
1312 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" ),
1313 ), 'notice' );
1314 }
1315
1316 /**
1317 * Filter for wp_get_attachment_url();
1318 *
1319 * @param string $url
1320 * @param string $post_id
1321 * @return mixed|null|string
1322 */
1323 public function wp_get_attachment_url( $url = '', $post_id = '' ) {
1324 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
1325 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1326 $_url = parse_url($sm_cloud[ 'fileLink' ]);
1327 $url = !isset($_url['scheme']) ? ( 'https:' . $sm_cloud[ 'fileLink' ] ) : $sm_cloud[ 'fileLink' ];
1328 return apply_filters('wp_stateless_bucket_link', $url);
1329 }
1330 return $url;
1331 }
1332
1333 /**
1334 * Filter for attachment_url_to_postid()
1335 *
1336 * @param int|false $post_id originally found post ID (or false if not found)
1337 * @param string $url the URL to find the post ID for
1338 * @return int|false found post ID from cloud storage URL
1339 */
1340 public function attachment_url_to_postid( $post_id, $url ) {
1341 global $wpdb;
1342
1343 if ( ! $post_id ) {
1344 $post_id = get_transient("stateless_url_to_postid_" . md5($url));
1345
1346 if(defined('WP_STATELESS_LEGACY_URL_TO_POSTID')){
1347 // User can use this constant if they change the Bucket Folder (root_dir) after uploading image.
1348 // This can be little slow at first run.
1349 if(empty($post_id)){
1350 $query = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'sm_cloud' AND meta_value LIKE '%s'";
1351 $post_id = $wpdb->get_var( $wpdb->prepare( $query, '%' . $url . '%' ) );
1352
1353 if($post_id){
1354 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1355 }
1356 }
1357 return $post_id;
1358 }
1359
1360 if(empty($post_id)){
1361 $gs_base_url = $this->get_gs_host();
1362 $gs_url = $this->get_gs_host() . '/' . $this->get( 'sm.root_dir' );
1363 $site_url = parse_url($gs_url);
1364 $image_path = parse_url( $url );
1365
1366 //force the protocols to match if needed
1367 if( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
1368 $url = str_replace( $image_path['scheme'], $site_url['scheme'], $url );
1369 }
1370
1371 if( 0 === strpos( $url, $gs_url . '/' ) ) {
1372 $url = substr( $url, strlen( $gs_url . '/' ) );
1373 }
1374 else if( 0 === strpos( $url, $gs_base_url . '/' ) ) {
1375 // In case user added Bucket Folder (root_dir) after uploading image.
1376 $url = substr( $url, strlen( $gs_base_url . '/' ) );
1377 }
1378
1379 $sql = $wpdb->prepare(
1380 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
1381 $url
1382 );
1383 $post_id = $wpdb->get_var( $sql );
1384
1385 if($post_id){
1386 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1387 }
1388 }
1389 }
1390
1391 return $post_id;
1392 }
1393
1394 /**
1395 * Upload the full size image first.
1396 *
1397 */
1398 public function before_intermediate_image_sizes($sizes, $metadata = array()){
1399 if(empty($metadata)){
1400 return $sizes;
1401 }
1402
1403 try{
1404 $attachment_id = attachment_url_to_postid($metadata['file']);
1405 $this->add_media(null, $attachment_id, false, array('no_thumb' => true));
1406 }
1407 catch(Exception $e){
1408
1409 }
1410
1411 return $sizes;
1412 }
1413
1414 /**
1415 * Change Upload BaseURL when CDN Used.
1416 *
1417 * @param $data
1418 * @return mixed
1419 */
1420 public function upload_dir( $data ) {
1421 $data[ 'basedir' ] = $this->get_gs_host();
1422 $data[ 'baseurl' ] = $this->get_gs_host();
1423 $data[ 'url' ] = $data[ 'baseurl' ] . $data[ 'subdir' ];
1424
1425 return $data;
1426
1427 }
1428
1429 /**
1430 * Set Feature Flag constants by parsing composer.json
1431 *
1432 * @todo Make sure settings from DB can override these.
1433 *
1434 * @author potanin@UD
1435 * @return array|mixed|null|object
1436 */
1437 public function parse_feature_flags( ) {
1438
1439 try {
1440
1441 $_raw = file_get_contents( Utility::normalize_path( $this->root_path ) . 'composer.json' );
1442
1443 $_parsed = json_decode( $_raw );
1444
1445 // @todo Catch poorly formatted JSON.
1446 if( !is_object( $_parsed ) ) {
1447 // throw new Error( "unable to parse." );
1448 }
1449
1450 foreach( (array) $_parsed->extra->featureFlags as $_feature ) {
1451
1452 if( !defined( $_feature->constant ) ) {
1453 define( $_feature->constant, $_feature->enabled );
1454
1455 if( $_feature->enabled ) {
1456 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] enabled.' );
1457 } else {
1458 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] disabled.' );
1459 }
1460 }
1461
1462 }
1463
1464 } catch ( Exception $e ) {
1465 Utility::log( 'Unable to parse [composer.json] feature flags. Error: [' . $e->getMessage() . ']' );
1466 // echo 'Caught exception: ', $e->getMessage(), "\n";
1467 }
1468
1469 return isset( $_parsed ) ? $_parsed : null;
1470
1471 }
1472
1473 /**
1474 * Determine if Utility class contains missed function
1475 * in other case, just return NULL to prevent ERRORS
1476 *
1477 * @author peshkov@UD
1478 * @param $name
1479 * @param $arguments
1480 * @return mixed|null
1481 */
1482 public function __call( $name, $arguments ) {
1483 if( is_callable( array( "wpCloud\\StatelessMedia\\Utility", $name ) ) ) {
1484 return call_user_func_array( array( "wpCloud\\StatelessMedia\\Utility", $name ), $arguments );
1485 } else {
1486 return NULL;
1487 }
1488 }
1489 }
1490 }
1491 }
1492