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

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