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

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

1,491 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 foreach ($sources as $width => &$image) {
278 if (!isset($image_meta['gs_name']) || empty($image_meta['gs_name'])) {
279 continue;
280 }
281
282 // If srcset includes original image src, replace it
283 if (substr_compare($image['url'], $gs_name, -strlen($gs_name)) === 0) {
284 $image['url'] = $image_meta['gs_link'];
285 // Replace all sizes
286 } elseif (isset($image_meta['sizes']) && is_array($image_meta['sizes'])) {
287 $found = false;
288 foreach ($image_meta['sizes'] as $key => $meta) {
289 if (!isset($meta['gs_name']) || empty($meta['gs_name'])) {
290 // if mode is stateless and nothing to show for srcset item - unset that item
291 if ( $this->get( 'sm.mode' ) === 'stateless' ) {
292 $image = null;
293 }
294 continue;
295 }
296
297 $thumb_gs_name = $meta['gs_name'];
298 // removing rood_dir from gs_name
299 if($root_dir_pos !== false){
300 $thumb_gs_name = substr($thumb_gs_name, $root_dir_pos);
301 }
302
303 if (substr_compare($image['url'], $thumb_gs_name, -strlen($thumb_gs_name)) === 0) {
304 $image['url'] = $meta['gs_link'];
305 $found = true;
306 break;
307 }
308 }
309
310 // if no size found and mode is stateless and nothing to show for srcset item - unset that item
311 if (!$found && $this->get( 'sm.mode' ) === 'stateless') {
312 $image = null;
313 }
314 } else {
315 // if mode is stateless and nothing to show for srcset item - unset that item
316 if ( $this->get( 'sm.mode' ) === 'stateless' ) {
317 $image = null;
318 }
319 }
320 }
321 }
322
323 return array_filter( $sources );
324 }
325
326 /**
327 * Return gs host.
328 * If custom domain is set it's return bucket name as host,
329 * else return storage.googleapis.com as host and append bucket name at the end.
330 *
331 * @param array $sm
332 * @return mixed|void
333 */
334 public function get_gs_host($sm = array()) {
335 $sm = $sm?$sm: $this->get( 'sm');
336 $image_host = 'https://storage.googleapis.com/';
337 $image_host .= $sm['bucket'];
338
339 $custom_domain = $sm['custom_domain'];
340 $is_ssl = strpos($custom_domain, 'https://');
341 $custom_domain = str_replace(array('http://', 'https://'), '', $custom_domain);
342 $custom_domain = trim($custom_domain, '/');
343
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 if ( !empty( $root_dir ) ) {
615 return $root_dir . '/' . $current_path;
616 }
617
618 return $current_path;
619 }
620
621 /**
622 * @param $content
623 * @return mixed
624 */
625 public function the_content_filter( $content ) {
626
627 if ( $upload_data = wp_upload_dir() ) {
628
629 if ( !empty( $upload_data['baseurl'] ) && !empty( $content ) ) {
630 $baseurl = preg_replace('/https?:\/\//','',$upload_data['baseurl']);
631 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
632 $root_dir = !empty( $root_dir ) ? $root_dir . '/' : false;
633 $image_host = $this->get_gs_host();
634 $file_ext = $this->replaceable_file_types();
635 $content = preg_replace( '/(href|src)=(\'|")(https?:\/\/'.str_replace('/', '\/', $baseurl).')\/(.+?)('.$file_ext.')(\'|")/i',
636 '$1=$2'.$image_host.'/'.($root_dir?$root_dir:'').'$4$5$6', $content);
637 }
638 }
639
640 return $content;
641 }
642
643 /**
644 * Return file types supported by File URL Replacement.
645 *
646 */
647 public function replaceable_file_types(){
648 $types = $this->get('sm.body_rewrite_types');
649
650 // Removing extra space.
651 $types = trim($types);
652 $types = preg_replace("/\s{2,}/", ' ', $types);
653
654 $types_arr = explode(' ', $types);
655 return '\.' . implode('|\.', $types_arr);
656 }
657
658 /**
659 * Copied from https://developer.wordpress.org/reference/functions/get_metadata/
660 *
661 * @param $value null unless other filter hooked in this function.
662 * @param $object_id post id
663 * @param $meta_key
664 * @param $single
665 * @return mixed
666 */
667 public function post_metadata_filter($value, $object_id, $meta_key, $single){
668 if(empty($value)){
669 $meta_type = 'post';
670 $transient_key = "stateless_{$meta_type}_meta";
671
672 $meta_cache = wp_cache_get($object_id, $transient_key);
673 if(empty($meta_cache)){
674 $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
675
676 if ( !$meta_cache ) {
677 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
678 $meta_cache = $meta_cache[$object_id];
679 }
680
681 foreach($meta_cache as $key => $meta){
682 $meta_cache[$key] = array_map('maybe_unserialize', $meta_cache[$key]);
683 }
684
685 $meta_cache = $this->convert_to_gs_link($meta_cache);
686 wp_cache_set($object_id, $meta_cache, $transient_key);
687 }
688
689 if ( ! $meta_key ) {
690 return $meta_cache;
691 }
692
693 if ( isset($meta_cache[$meta_key]) ) {
694 if ( $single )
695 return $meta_cache[$meta_key][0];
696 else
697 return $meta_cache[$meta_key];
698 }
699
700 // in case no metadata is found return what was passed in $value.
701 // $value most of the time is null.
702 return $value;
703 }
704
705 return $this->convert_to_gs_link($value);
706
707 }
708
709 /**
710 * Replace all image link with gs link and return only if meta modified.
711 *
712 * @param $meta
713 * @return mixed or null when not changed.
714 */
715 public function convert_to_gs_link($meta){
716 $updated = $meta;
717 if ( $meta && $upload_data = wp_upload_dir() ) {
718 if ( !empty( $upload_data['baseurl'] ) && !empty( $meta ) ) {
719 $baseurl = preg_replace('/https?:\/\//','',$upload_data['baseurl']);
720 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
721 $root_dir = !empty( $root_dir ) ? $root_dir . '/': false;
722 $image_host = $this->get_gs_host().'/'.($root_dir?$root_dir:'');
723 $file_ext = $this->replaceable_file_types();
724 $updated = $this->_convert_to_gs_link($meta, $image_host, $baseurl, $file_ext);
725 }
726 }
727
728 if($updated == $meta){
729 return null; // Not changed.
730 }
731 return $updated;
732 }
733
734 /**
735 * Replace all image link with gs link
736 *
737 * @param $meta
738 * @return mixed
739 */
740 public function _convert_to_gs_link($meta, $image_host, $baseurl, $file_ext){
741 if(is_array($meta)){
742 foreach ($meta as $key => $value) {
743 $meta[$key] = $this->_convert_to_gs_link($value, $image_host, $baseurl, $file_ext);
744 }
745 return $meta;
746 } elseif (is_object($meta) && $meta instanceof \stdClass ) {
747 foreach (get_object_vars($meta) as $key => $value) {
748 $meta->{$key} = $this->_convert_to_gs_link($value, $image_host, $baseurl, $file_ext);
749 }
750 return $meta;
751 } elseif(is_string($meta)){
752 return preg_replace( '/(https?:\/\/'.str_replace('/', '\/', $baseurl).')\/(.+?)(' . $file_ext . ')/i', $image_host.'$2$3', $meta);
753 }
754
755 return $meta;
756 }
757
758
759 /**
760 * @param $links
761 * @param $file
762 * @return mixed
763 */
764 public function plugin_action_links( $links, $file ) {
765
766 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless-media.php' ) ) {
767 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
768 array_unshift( $links, $settings_link );
769 }
770
771 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless.php' ) ) {
772 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
773 array_unshift( $links, $settings_link );
774 }
775
776 return $links;
777 }
778
779 /**
780 * Determines if plugin is loaded via mu-plugins
781 * or Network Enabled.
782 *
783 * @author peshkov@UD
784 */
785 public function is_network_detected() {
786 /* Plugin is loaded via mu-plugins. */
787
788 if( strpos( Utility::normalize_path( $this->root_path ), Utility::normalize_path( WPMU_PLUGIN_DIR ) ) !== false ) {
789 return true;
790 }
791
792 if( is_multisite() ) {
793 /* Looks through network enabled plugins to see if our one is there. */
794 foreach (wp_get_active_network_plugins() as $path) {
795 // Trying again using readlink in case it's a symlink file.
796 // boot_file is already solved.
797 // wp_normalize_path is helpfull in windows.
798 if (wp_normalize_path($this->boot_file) == wp_normalize_path($path) || (is_link($path) AND $this->boot_file == readlink($path))) {
799 return true;
800 }
801 }
802 }
803 return false;
804 }
805
806 /**
807 * Initialization.
808 * Register scripts and styles
809 */
810 public function admin_init() {
811 $this->show_notice_stateless_cache_busting();
812 wp_register_style( 'wp-stateless', $this->path( 'static/styles/wp-stateless.css', 'url' ), array(), self::$version );
813
814 /* Attachment or upload page */
815 wp_register_script( 'wp-stateless-uploads-js', $this->path( 'static/scripts/wp-stateless-uploads.js', 'url' ), array( 'jquery' ), self::$version );
816
817 /* Setup wizard styles and scripts. */
818 wp_register_style( 'wp-stateless-bootstrap', $this->path( 'static/styles/bootstrap.min.css', 'url' ), array(), '3.3.7' );
819 wp_register_style( 'bootstrap-grid-v4', $this->path( 'static/styles/bootstrap-grid.min.css', 'url' ), array(), '3.3.7' );
820 wp_register_style( 'wp-stateless-setup-wizard', $this->path( 'static/styles/wp-stateless-setup-wizard.css', 'url' ), array(), self::$version );
821
822 wp_register_script( 'async.min', ud_get_stateless_media()->path( 'static/scripts/async.js', 'url' ), array(), ud_get_stateless_media()->version );
823 wp_register_script( 'jquery.history', ud_get_stateless_media()->path( 'static/scripts/jquery.history.js', 'url' ), array( 'jquery' ), ud_get_stateless_media()->version, true );
824 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 );
825 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 );
826 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 );
827 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 );
828 wp_localize_script( 'wp-stateless-setup', 'stateless_l10n', $this->get_l10n_data() );
829
830 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 );
831
832
833 /* Stateless settings page */
834 wp_register_script( 'wp-stateless-settings', ud_get_stateless_media()->path( 'static/scripts/wp-stateless-settings.js', 'url' ), array(), ud_get_stateless_media()->version );
835 wp_localize_script( 'wp-stateless-settings', 'stateless_l10n', $this->get_l10n_data() );
836
837 wp_register_style( 'wp-stateless-settings', $this->path( 'static/styles/wp-stateless-settings.css', 'url' ), array(), self::$version );
838
839 // Sync tab
840 if ( wp_script_is( 'jquery-ui-widget', 'registered' ) ){
841 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' );
842 }
843 else{
844 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' );
845 }
846 wp_register_script( 'wp-stateless-angular', ud_get_stateless_media()->path( 'static/scripts/angular.min.js', 'url' ), array(), '1.5.0', true );
847 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 );
848
849 wp_localize_script( 'wp-stateless', 'stateless_l10n', $this->get_l10n_data() );
850 wp_localize_script('wp-stateless', 'wp_stateless_configs', array(
851 'WP_DEBUG' => defined('WP_DEBUG') ? WP_DEBUG : false,
852 ));
853
854 $settings = ud_get_stateless_media()->get('sm');
855 if(defined('WP_STATELESS_MEDIA_JSON_KEY') && WP_STATELESS_MEDIA_JSON_KEY){
856 $settings['key_json'] = "Currently configured via a constant.";
857 }
858 wp_localize_script('wp-stateless', 'wp_stateless_settings', $settings);
859 wp_localize_script('wp-stateless', 'wp_stateless_compatibility', Module::get_modules());
860 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' );
861
862 }
863
864 /**
865 * Get_l10n_data
866 *
867 * @param string $value
868 * @return mixed
869 */
870 public function get_l10n_data($value=''){
871 include ud_get_stateless_media()->path( 'l10n.php', 'dir');
872 return $l10n;
873 }
874
875 /**
876 * Admin Scripts
877 *
878 * @param $hook
879 */
880 public function admin_enqueue_scripts( $hook ) {
881
882 switch( $hook ) {
883
884 case 'options-media.php':
885 //wp_enqueue_script( 'wp-api' );
886
887 wp_enqueue_style( 'wp-stateless');
888 wp_enqueue_script( 'wp-stateless-setup' );
889 break;
890
891 case 'upload.php':
892
893 wp_enqueue_style( 'wp-stateless');
894 wp_enqueue_script( 'wp-stateless-uploads-js' );
895
896 break;
897
898 case 'post.php':
899
900 global $post;
901
902 if ( $post->post_type == 'attachment' ) {
903 wp_enqueue_style( 'wp-stateless');
904 wp_enqueue_script( 'wp-stateless-uploads-js' );
905 }
906
907 break;
908
909 case 'media_page_stateless-setup':
910 wp_enqueue_style( 'wp-stateless');
911 wp_enqueue_style( 'wp-stateless-bootstrap' );
912 wp_enqueue_style( 'wp-stateless-setup-wizard' );
913
914 wp_enqueue_script( 'async.min' );
915 wp_enqueue_script( 'jquery.history' );
916 wp_enqueue_script( 'wp-stateless-validation' );
917 wp_enqueue_script( 'wp-stateless-loading' );
918 wp_enqueue_script( 'wp-stateless-comboBox' );
919 wp_enqueue_script( 'wp-stateless-setup' );
920 wp_enqueue_script( 'wp-stateless-setup-wizard-js' );
921 break;
922 case 'media_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 default: break;
946 }
947
948 }
949
950 /**
951 * Add Attributes to media HTML
952 *
953 * @author potanin@UD
954 * @param $attr
955 * @param $attachment
956 * @param $size
957 * @return mixed
958 */
959 public function wp_get_attachment_image_attributes( $attr, $attachment, $size = null ) {
960
961 $sm_cloud = get_post_meta( $attachment->ID, 'sm_cloud', true );
962 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'name' ] ) ) {
963 $attr[ 'class' ] = $attr[ 'class' ] . ' wp-stateless-item';
964 $attr[ 'data-image-size' ] = is_array( $size ) ? implode( 'x', $size ) : $size;
965 $attr[ 'data-stateless-media-bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
966 $attr[ 'data-stateless-media-name' ] = $sm_cloud[ 'name' ];
967 }
968
969 return $attr;
970
971 }
972
973 /**
974 * Adds filter link to Media Library table.
975 *
976 * @param $views
977 * @return mixed
978 */
979 public function views_upload( $views ) {
980 $views['stateless'] = '<a href="#">' . __( 'Stateless Media' ) . '</a>';
981 return $views;
982 }
983
984 /**
985 * Replace media URL
986 *
987 * @param bool $false
988 * @param integer $id
989 * @param string $size
990 * @return mixed $false
991 */
992 public function image_downsize( $false = false, $id, $size ) {
993
994 if ( !isset( $this->client ) || !$this->client || is_wp_error( $this->client ) ) {
995 return $false;
996 }
997
998 /**
999 * Check if enabled
1000 */
1001 if ( $this->get( 'sm.mode' ) !== 'cdn' && $this->get( 'sm.mode' ) !== 'stateless' ) {
1002 return $false;
1003 }
1004
1005 /** Start determine remote file */
1006 $img_url = wp_get_attachment_url($id);
1007 $meta = wp_get_attachment_metadata($id);
1008 $width = $height = 0;
1009 $is_intermediate = false;
1010
1011 //** try for a new style intermediate size */
1012 if ( $intermediate = image_get_intermediate_size( $id, $size ) ) {
1013 if(!empty( $intermediate['gs_link'] )){
1014 $img_url = $intermediate['gs_link'];
1015 }
1016 else if(!empty( $intermediate['url'] )){
1017 $img_url = $intermediate['url'];
1018 }
1019 else{
1020 $img_url = dirname($img_url) . $intermediate['file'];
1021 }
1022
1023 $width = $intermediate['width'];
1024 $height = $intermediate['height'];
1025 $is_intermediate = true;
1026 }
1027
1028 /**
1029 * maybe try to get images info from sm_cloud
1030 * this case may happen when no local files
1031 * @author korotkov@ud
1032 */
1033 if ( !$width && !$height ) {
1034 $sm_cloud = get_post_meta( $id, 'sm_cloud', true );
1035 if ( is_string($size) && !empty( $sm_cloud['sizes'] ) && !empty( $sm_cloud['sizes'][$size] ) ) {
1036 global $_wp_additional_image_sizes;
1037
1038 $img_url = !empty( $sm_cloud['sizes'][$size]['fileLink'] ) ? $sm_cloud['sizes'][$size]['fileLink'] : $img_url;
1039
1040 if ( !empty( $_wp_additional_image_sizes[ $size ] ) ) {
1041 $width = !empty( $_wp_additional_image_sizes[ $size ]['width'] ) ? $_wp_additional_image_sizes[ $size ]['width'] : $width;
1042 $height = !empty( $_wp_additional_image_sizes[ $size ]['height'] ) ? $_wp_additional_image_sizes[ $size ]['height'] : $height;
1043 }
1044
1045 $is_intermediate = true;
1046 }
1047 }
1048
1049 if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
1050
1051 //** any other type: use the real image */
1052 $width = $meta['width'];
1053 $height = $meta['height'];
1054 }
1055
1056
1057 if ( $img_url) {
1058
1059 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
1060 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
1061 $img_url = apply_filters('wp_stateless_bucket_link', $img_url);
1062 return array( $img_url, $width, $height, $is_intermediate );
1063 }
1064
1065
1066 /**
1067 * All other cases work as usually
1068 */
1069 return $false;
1070
1071 }
1072
1073 /**
1074 * Extends metadata by adding GS information.
1075 * Note: must not be called directly. It's used only on hook
1076 *
1077 * @param $metadata
1078 * @param $attachment_id
1079 * @return array|mixed
1080 */
1081 public function wp_get_attachment_metadata( $metadata, $attachment_id ) {
1082 /* Determine if the media file has GS data at all. */
1083 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
1084 // If metadata not passed the get metadata from post meta.
1085 if(empty($metadata)){
1086 $metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
1087 }
1088
1089 if( is_array( $metadata ) && is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1090 $metadata[ 'gs_link' ] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'fileLink' ]);
1091 $metadata[ 'gs_name' ] = isset( $sm_cloud[ 'name' ] ) ? $sm_cloud[ 'name' ] : false;
1092 $metadata[ 'gs_bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
1093 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
1094 foreach( $metadata[ 'sizes' ] as $k => $v ) {
1095 if( !empty( $sm_cloud[ 'sizes' ][ $k ][ 'name' ] ) ) {
1096 $metadata['sizes'][$k]['gs_name'] = $sm_cloud[ 'sizes' ][ $k ][ 'name' ];
1097 $metadata['sizes'][$k]['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'sizes' ][ $k ][ 'fileLink' ]);
1098 }
1099 }
1100 }
1101 }
1102
1103 return $metadata;
1104 }
1105
1106 /**
1107 * Returns client object
1108 * or WP_Error on failure.
1109 *
1110 * @author peshkov@UD
1111 * @return object $this->client. \wpCloud\StatelessMedia\GS_Client or \WP_Error
1112 */
1113 public function get_client() {
1114
1115 if( null === $this->client ) {
1116
1117 $key_json = get_site_option( 'sm_key_json' );
1118 if ( empty($key_json) ) {
1119 $key_json = $this->get( 'sm.key_json' );
1120 }
1121
1122 /* Try to initialize GS Client */
1123 $this->client = GS_Client::get_instance( array(
1124 'bucket' => $this->get( 'sm.bucket' ),
1125 'key_json' => $key_json
1126 ) );
1127 }
1128
1129 return $this->client;
1130
1131 }
1132
1133 /**
1134 * Determines if we can connect to Google Storage Bucket.
1135 *
1136 * @author peshkov@UD
1137 */
1138 public function is_connected_to_gs() {
1139
1140 $trnst = get_transient( 'sm::is_connected_to_gs' );
1141
1142 if ( empty($trnst) || false === $trnst || !isset( $trnst[ 'hash' ] ) || $trnst[ 'hash' ] != md5( serialize( $this->get( 'sm' ) ) ) ) {
1143 $trnst = array(
1144 'success' => 'true',
1145 'error' => '',
1146 'hash' => md5( serialize( $this->get( 'sm' ) ) ),
1147 );
1148 $client = $this->get_client();
1149
1150 if ( is_wp_error( $client ) ) {
1151 $trnst[ 'success' ] = 'false';
1152 $trnst[ 'error' ] = $client->get_error_message();
1153 } else {
1154 $connected = $client->is_connected();
1155 if( $connected !== true ) {
1156 $trnst[ 'success' ] = 'false';
1157 $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' ) );
1158
1159 if( is_callable(array($connected, 'getHandlerContext')) && $handlerContext = $connected->getHandlerContext() ){
1160 if(!empty($handlerContext['error'])){
1161 $handlerContext['error'];
1162 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($handlerContext['error']);
1163 }
1164 }
1165
1166 if( is_callable(array($connected, 'getErrors')) && $error = $connected->getErrors() ){
1167 $error = reset($error);
1168 if($error['reason'] == 'accessNotConfigured')
1169 $trnst[ 'error' ] = "Could not connect to Google Storage bucket. " . make_clickable($error['message']);
1170 }
1171 }
1172 }
1173 set_transient( 'sm::is_connected_to_gs', $trnst, 4 * HOUR_IN_SECONDS );
1174 }
1175
1176 if( isset( $trnst[ 'success' ] ) && $trnst[ 'success' ] == 'false' ) {
1177 return new \WP_Error( 'error', ( !empty( $trnst[ 'error' ] ) ? $trnst[ 'error' ] : __( 'There is an Error on connection to Google Storage.', $this->domain ) ) );
1178 }
1179
1180 return true;
1181 }
1182
1183 /**
1184 * Flush all plugin transients
1185 *
1186 */
1187 public function flush_transients() {
1188 delete_transient( 'sm::is_connected_to_gs' );
1189 }
1190
1191 /**
1192 * Plugin Activation
1193 *
1194 */
1195 public function activate() {
1196 add_action( 'activated_plugin', array($this, 'redirect_to_splash'), 99 );
1197 $this->run_upgrade_process();
1198 }
1199
1200 /**
1201 * Run Install Process.
1202 * Triggered on plugins_loaded instead of register_activation_hook action.
1203 * Works on even manual plugin update.
1204 *
1205 * @author alim@UD
1206 */
1207 public function run_install_process()
1208 {
1209 // calling the upgrade function because it's same as this point for fresh install or updates.
1210 $this->run_upgrade_process();
1211 }
1212
1213 /**
1214 * Run Upgrade Process:
1215 * Triggered on plugins_loaded instead of register_activation_hook action.
1216 * Works on even manual plugin update.
1217 *
1218 * @author alim@UD
1219 */
1220 public function run_upgrade_process()
1221 {
1222 // Creating database on new installation.
1223 $this->create_db();
1224 /**
1225 * Maybe Upgrade current Version
1226 */
1227 Upgrader::call( $this->args[ 'version' ] );
1228 }
1229
1230 /**
1231 * Create database on plugin activation.
1232 * @param boolean $force - whether to create db even if option exists. For debug purpose only.
1233 */
1234 public function create_db($force = false) {
1235 global $wpdb;
1236 $sm_sync_db_version = get_option( 'sm_sync_db_version' );
1237
1238 if( $sm_sync_db_version && $force == false ) {
1239 return;
1240 }
1241
1242 $table_name = $wpdb->prefix . 'sm_sync';
1243 $charset_collate = $wpdb->get_charset_collate();
1244
1245 // `expire` timestamp NULL DEFAULT NULL,
1246 $sql = "CREATE TABLE $table_name (
1247 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT ,
1248 `file` varchar(255) NOT NULL ,
1249 `status` varchar(10) NOT NULL ,
1250 PRIMARY KEY (`id`)
1251 ) $charset_collate;";
1252
1253 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
1254 dbDelta( $sql );
1255
1256 add_option( 'sm_sync_db_version', $this->args[ 'version' ] );
1257 }
1258
1259 /**
1260 * Redirect_to_splash
1261 *
1262 * @param string $plugin
1263 */
1264 public function redirect_to_splash($plugin =''){
1265 $this->settings = new Settings();
1266
1267 if(defined( 'WP_CLI' ) || $this->settings->get('sm.key_json') || isset($_POST['checked']) && count($_POST['checked']) > 1){
1268 return;
1269 }
1270
1271 if(
1272 !$this->settings->get('sm.key_json') &&
1273 defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true &&
1274 defined('WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL') && WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL == true
1275 ) {
1276 return;
1277 }
1278
1279 if( !$this->settings->get('sm.key_json') && defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true ) {
1280 $url = $this->get_settings_page_url('?page=stateless-settings');
1281 exit( wp_redirect($url));
1282 }
1283
1284 if( $plugin == plugin_basename( $this->boot_file ) ) {
1285 $url = $this->get_settings_page_url('?page=stateless-setup&step=splash-screen');
1286 if(json_decode($this->settings->get('sm.key_json'))){
1287 $url = $this->get_settings_page_url('?page=stateless-settings');
1288 }
1289 exit( wp_redirect($url));
1290 }
1291 }
1292
1293 /**
1294 * Plugin Deactivation
1295 *
1296 */
1297 public function deactivate() {}
1298
1299 /**
1300 * Show_notice_stateless_cache_busting
1301 *
1302 */
1303 public function show_notice_stateless_cache_busting(){
1304 $this->errors->add( array(
1305 'key' => 'stateless_cache_busting',
1306 'button' => 'View Settings',
1307 'button_link' => admin_url('upload.php?page=stateless-settings'),
1308 'title' => sprintf( __( "Stateless mode now requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1309 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1310 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.
1311 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" ),
1312 ), 'notice' );
1313 }
1314
1315 /**
1316 * Filter for wp_get_attachment_url();
1317 *
1318 * @param string $url
1319 * @param string $post_id
1320 * @return mixed|null|string
1321 */
1322 public function wp_get_attachment_url( $url = '', $post_id = '' ) {
1323 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
1324 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1325 $_url = parse_url($sm_cloud[ 'fileLink' ]);
1326 $url = !isset($_url['scheme']) ? ( 'https:' . $sm_cloud[ 'fileLink' ] ) : $sm_cloud[ 'fileLink' ];
1327 return apply_filters('wp_stateless_bucket_link', $url);
1328 }
1329 return $url;
1330 }
1331
1332 /**
1333 * Filter for attachment_url_to_postid()
1334 *
1335 * @param int|false $post_id originally found post ID (or false if not found)
1336 * @param string $url the URL to find the post ID for
1337 * @return int|false found post ID from cloud storage URL
1338 */
1339 public function attachment_url_to_postid( $post_id, $url ) {
1340 global $wpdb;
1341
1342 if ( ! $post_id ) {
1343 $post_id = get_transient("stateless_url_to_postid_" . md5($url));
1344
1345 if(defined('WP_STATELESS_LEGACY_URL_TO_POSTID')){
1346 // User can use this constant if they change the Bucket Folder (root_dir) after uploading image.
1347 // This can be little slow at first run.
1348 if(empty($post_id)){
1349 $query = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'sm_cloud' AND meta_value LIKE '%s'";
1350 $post_id = $wpdb->get_var( $wpdb->prepare( $query, '%' . $url . '%' ) );
1351
1352 if($post_id){
1353 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1354 }
1355 }
1356 return $post_id;
1357 }
1358
1359 if(empty($post_id)){
1360 $gs_base_url = $this->get_gs_host();
1361 $gs_url = $this->get_gs_host() . '/' . $this->get( 'sm.root_dir' );
1362 $site_url = parse_url($gs_url);
1363 $image_path = parse_url( $url );
1364
1365 //force the protocols to match if needed
1366 if( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
1367 $url = str_replace( $image_path['scheme'], $site_url['scheme'], $url );
1368 }
1369
1370 if( 0 === strpos( $url, $gs_url . '/' ) ) {
1371 $url = substr( $url, strlen( $gs_url . '/' ) );
1372 }
1373 else if( 0 === strpos( $url, $gs_base_url . '/' ) ) {
1374 // In case user added Bucket Folder (root_dir) after uploading image.
1375 $url = substr( $url, strlen( $gs_base_url . '/' ) );
1376 }
1377
1378 $sql = $wpdb->prepare(
1379 "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
1380 $url
1381 );
1382 $post_id = $wpdb->get_var( $sql );
1383
1384 if($post_id){
1385 set_transient("stateless_url_to_postid_" . md5($url), $post_id);
1386 }
1387 }
1388 }
1389
1390 return $post_id;
1391 }
1392
1393 /**
1394 * Upload the full size image first.
1395 *
1396 */
1397 public function before_intermediate_image_sizes($sizes, $metadata = array()){
1398 if(empty($metadata)){
1399 return $sizes;
1400 }
1401
1402 try{
1403 $attachment_id = attachment_url_to_postid($metadata['file']);
1404 $this->add_media(null, $attachment_id, false, array('no_thumb' => true));
1405 }
1406 catch(Exception $e){
1407
1408 }
1409
1410 return $sizes;
1411 }
1412
1413 /**
1414 * Change Upload BaseURL when CDN Used.
1415 *
1416 * @param $data
1417 * @return mixed
1418 */
1419 public function upload_dir( $data ) {
1420 $data[ 'basedir' ] = $this->get_gs_host();
1421 $data[ 'baseurl' ] = $this->get_gs_host();
1422 $data[ 'url' ] = $data[ 'baseurl' ] . $data[ 'subdir' ];
1423
1424 return $data;
1425
1426 }
1427
1428 /**
1429 * Set Feature Flag constants by parsing composer.json
1430 *
1431 * @todo Make sure settings from DB can override these.
1432 *
1433 * @author potanin@UD
1434 * @return array|mixed|null|object
1435 */
1436 public function parse_feature_flags( ) {
1437
1438 try {
1439
1440 $_raw = file_get_contents( Utility::normalize_path( $this->root_path ) . 'composer.json' );
1441
1442 $_parsed = json_decode( $_raw );
1443
1444 // @todo Catch poorly formatted JSON.
1445 if( !is_object( $_parsed ) ) {
1446 // throw new Error( "unable to parse." );
1447 }
1448
1449 foreach( (array) $_parsed->extra->featureFlags as $_feature ) {
1450
1451 if( !defined( $_feature->constant ) ) {
1452 define( $_feature->constant, $_feature->enabled );
1453
1454 if( $_feature->enabled ) {
1455 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] enabled.' );
1456 } else {
1457 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] disabled.' );
1458 }
1459 }
1460
1461 }
1462
1463 } catch ( Exception $e ) {
1464 Utility::log( 'Unable to parse [composer.json] feature flags. Error: [' . $e->getMessage() . ']' );
1465 // echo 'Caught exception: ', $e->getMessage(), "\n";
1466 }
1467
1468 return isset( $_parsed ) ? $_parsed : null;
1469
1470 }
1471
1472 /**
1473 * Determine if Utility class contains missed function
1474 * in other case, just return NULL to prevent ERRORS
1475 *
1476 * @author peshkov@UD
1477 * @param $name
1478 * @param $arguments
1479 * @return mixed|null
1480 */
1481 public function __call( $name, $arguments ) {
1482 if( is_callable( array( "wpCloud\\StatelessMedia\\Utility", $name ) ) ) {
1483 return call_user_func_array( array( "wpCloud\\StatelessMedia\\Utility", $name ), $arguments );
1484 } else {
1485 return NULL;
1486 }
1487 }
1488 }
1489 }
1490 }
1491