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

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