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

1,266 lines 46.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.1';
29
30 /**
31 * Singleton Instance Reference.
32 *
33 * @protected
34 * @static
35 * @property $instance
36 * @type \wpCloud\StatelessMedia\Bootstrap object
37 */
38 protected static $instance = null;
39
40
41 /**
42 * Constructor
43 * Attention: MUST NOT BE CALLED DIRECTLY! USE get_instance() INSTEAD!
44 *
45 * @author peshkov@UD
46 */
47 protected function __construct( $args ) {
48 parent::__construct( $args );
49
50 //** Define our Admin Notices handler object */
51 $this->errors = new Errors( array_merge( $args, array(
52 'type' => $this->type
53 ) ) );
54
55 // Initialize compatibility modules.
56 add_action( 'plugins_loaded', function(){
57 new Module();
58 });
59 }
60
61 /**
62 * Instantaite 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 localisation 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 * Maybe Upgrade current Version
131 */
132 Upgrader::call( $this->args[ 'version' ] );
133
134 /**
135 * Load WP-CLI Commands
136 */
137 if( defined( 'WP_CLI' ) && WP_CLI ) {
138 include_once($this->path('lib/cli/class-sm-cli-command.php', 'dir'));
139 }
140
141 $this->is_network_detected();
142
143 /**
144 * Add scripts
145 */
146 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
147
148 /**
149 * Hashify file name if option is enabled
150 */
151 if ( $this->get( 'sm.hashify_file_name' ) == 'true' ) {
152 add_filter('sanitize_file_name', array( 'wpCloud\StatelessMedia\Utility', 'randomize_filename' ), 10);
153 }
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', create_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' ) {
199 add_filter( 'the_content', array( $this, 'the_content_filter' ), 99 );
200 add_filter( 'get_post_metadata', array( $this, 'post_metadata_filter' ), 2, 4 );
201 }
202
203 if ( $this->get( 'sm.custom_domain' ) == $this->get( 'sm.bucket' ) ) {
204 add_filter( 'wp_stateless_bucket_link', array( $this, 'wp_stateless_bucket_link' ) );
205 }
206 }
207
208 if ( $root_dir = $this->get( 'sm.root_dir' ) ) {
209 if ( trim( $root_dir, '/ ' ) !== '' ) { // Remove any forward slash and empty space.
210 add_filter( 'wp_stateless_file_name', array( $this, 'handle_root_dir' ) );
211 }
212 }
213
214 /**
215 * Rewrite Image URLS
216 */
217 add_filter( 'image_downsize', array( $this, 'image_downsize' ), 99, 3 );
218 add_filter( 'wp_calculate_image_srcset', array($this, 'wp_calculate_image_srcset'), 10, 5 );
219
220 /**
221 * Extends metadata by adding GS information.
222 */
223 add_filter( 'wp_get_attachment_metadata', array( $this, 'wp_get_attachment_metadata' ), 10, 2 );
224
225 /**
226 * Add/Edit Media
227 *
228 * Once added or edited we can get into Attachment ID then get all image sizes and sync them with GS
229 * We can't use this. That's prevent removing this filter.
230 */
231 add_filter( 'wp_update_attachment_metadata', array( 'wpCloud\StatelessMedia\Utility', 'add_media' ), 999, 2 );
232
233 /**
234 * Add Media
235 *
236 * Once added we can get into Attachment ID then get all image sizes and sync them with GS
237 */
238 // add_filter( 'wp_generate_attachment_metadata', array( $this, 'add_media' ), 100, 2 );
239
240 if ( $this->get( 'sm.delete_remote' ) == 'true' ) {
241 /**
242 * On physical file deletion we remove any from GS
243 */
244 add_filter('delete_attachment', array($this, 'remove_media'));
245 }
246
247 // Trigger module initialization and registration.
248 do_action('sm::module::init', $this->get( 'sm' ));
249 }
250
251 }
252 }
253
254 /**
255 * Rebuild srcset from gs_link.
256 *
257 *
258 */
259 public function wp_calculate_image_srcset($sources, $size_array, $image_src, $image_meta, $attachment_id){
260
261 if ( empty( $image_meta['gs_link'] ) ) {
262 $image_meta = wp_get_attachment_metadata($attachment_id);
263 }
264
265 foreach ($sources as $width => &$image) {
266 if($width == $image_meta['width'] && isset( $image_meta['gs_link'] ) && $image_meta['gs_link'] ){
267 $image['url'] = $image_meta['gs_link'];
268 }
269 elseif(isset($image_meta['sizes']) && is_array($image_meta['sizes'])){
270 foreach ($image_meta['sizes'] as $key => $meta) {
271 if($width == $meta['width'] && isset($meta['gs_link']) && $meta['gs_link']){
272 $image['url'] = $meta['gs_link'];
273 }
274 }
275 }
276 }
277
278 return $sources;
279 }
280
281 /**
282 * Return gs host.
283 * If custom domain is set it's return bucket name as host,
284 * else return storage.googleapis.com as host and append bucket name at the end.
285 * @param none
286 * @return Host name to use
287 */
288 public function get_gs_host($sm = array()) {
289 $sm = $sm?$sm: $this->get( 'sm');
290 $image_host = 'https://storage.googleapis.com/';
291 $custom_domain = $sm['custom_domain'];
292 $is_ssl = strpos($custom_domain, 'https://');
293 $custom_domain = str_replace('https://', '', $custom_domain);
294 $custom_domain = str_replace('http://', '', $custom_domain);
295 if ( $sm['bucket'] && $custom_domain == $sm['bucket']) {
296 $image_host = $is_ssl === 0 ? 'https://' : 'http://'; // bucketname will be host
297 }
298 return apply_filters( 'get_gs_host', $image_host . $sm['bucket'], $image_host, $sm['bucket'], $is_ssl );
299 }
300
301 /**
302 * Filter for wp_stateless_bucket_link if custom domain is set.
303 * It's get attachment url and remove "storage.googleapis.com" from url.
304 * So that custom url can be used.
305 * @param $new_blog
306 * @param $prev_blog_id
307 */
308 public function wp_stateless_bucket_link($fileLink) {
309 $bucketname = $this->get( 'sm.bucket' );
310 if ( strpos($fileLink, $bucketname) > 8) {
311 $fileLink = 'http://' . substr($fileLink, strpos($fileLink, $bucketname));
312 }
313 return $fileLink;
314 }
315
316 /**
317 * Return settings page url.
318 * @param $path
319 */
320 public function get_settings_page_url( $path = '' ) {
321 $url = get_admin_url( get_current_blog_id(), ( is_network_admin() ? 'network/settings.php' : 'upload.php' ) );
322 return $url . $path;
323 }
324
325 /**
326 * Get new blog settings once switched blog.
327 * @param $new_blog
328 * @param $prev_blog_id
329 */
330 public function on_switch_blog( $new_blog, $prev_blog_id ) {
331 $this->settings->refresh();
332 }
333
334 /**
335 * Get settings handler.
336 * Filling array if some settings missing.
337 *
338 * @param $settings
339 * @return
340 */
341 public function get_settings($settings) {
342
343 $settings_list = array(
344 'mode',
345 'body_rewrite',
346 'body_rewrite_types',
347 'bucket',
348 'root_dir',
349 'key_json',
350 'cache_control',
351 'delete_remote',
352 'custom_domain',
353 'organize_media',
354 'hashify_file_name'
355 );
356
357 foreach ($settings_list as $setting) {
358
359 /** If setting is already exist, just skip it */
360 if( isset( $settings[ $setting ] ) ) {
361 continue;
362 }
363
364 $value = $this->get( 'sm.' . $setting );
365
366 /** Decode json to array */
367 if( $value && is_string( $value ) && $setting === 'key_json' ) {
368 $value = json_decode( $value, true );
369 $setting = 'key';
370 }
371
372 $settings[ $setting ] = $value;
373 }
374
375 return $settings;
376 }
377
378 /**
379 * Remove all settings.
380 */
381 public function reset($network = false) {
382 $this->settings->reset($network);
383 }
384
385 /**
386 * @param $actions
387 * @param $post
388 * @param $detached
389 * @return mixed
390 */
391 public function add_custom_row_actions( $actions, $post, $detached ) {
392
393 if ( !current_user_can( 'upload_files' ) ) return $actions;
394
395 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
396 $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>';
397 }
398
399 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
400 $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>';
401 }
402
403 return $actions;
404
405 }
406
407 /**
408 * Register metaboxes
409 */
410 public function register_metaboxes() {
411 add_meta_box(
412 'sm-attachment-metabox',
413 __( 'Google Cloud Storage', ud_get_stateless_media()->domain ),
414 array($this, 'attachment_meta_box_callback'),
415 'attachment',
416 'side',
417 'low'
418 );
419 }
420
421 /**
422 * Define REST API.
423 *
424 * // https://usabilitydynamics-sandbox-uds-io-stateless-testing.c.rabbit.ci/wp-json/wp-stateless/v1
425 *
426 * @author potanin@UD
427 */
428 public function api_init() {
429
430 $route_namespace = 'wp-stateless/v1';
431 $api_namespace = 'wpCloud\StatelessMedia\API';
432
433 register_rest_route( $route_namespace, '/status', array(
434 'methods' => 'GET',
435 'callback' => array( $api_namespace, 'status' ),
436 ) );
437
438 register_rest_route( $route_namespace, '/jobs', array(
439 'methods' => 'GET',
440 'callback' => array( $api_namespace, 'jobs' ),
441 ) );
442
443 /**
444 * Return stateless settings.
445 *
446 * Request parameter: none
447 *
448 * Response:
449 * ok: Whether API is up or not
450 * message: Describe what is done or error message on error.
451 * settings: array of stateless settings
452 *
453 */
454 register_rest_route( $route_namespace, '/getSettings', array( 'methods' => 'GET', 'callback' => array( $api_namespace, 'getSettings' ), ) );
455
456 /**
457 * Essentially for scrolling through media library to build our index.
458 *
459 * Request parameter: none
460 *
461 * Response:
462 * ok: Whether API is up or not
463 * message: Describe what is done or error message on error.
464 * settings: array of media files
465 *
466 */
467 register_rest_route( $route_namespace, '/getMediaLibrary', array( 'methods' => 'GET', 'callback' => array( $api_namespace, 'getMediaLibrary' ), ) );
468
469 /**
470 * Get detailed information of media file
471 *
472 * Request parameter: none
473 *
474 * Response:
475 * ok: Whether API is up or not
476 * message: Describe what is done or error message on error.
477 * settings: media file array
478 *
479 */
480 register_rest_route( $route_namespace, '/getMediaItem', array( 'methods' => 'GET', 'callback' => array( $api_namespace, 'getMediaItem' ), ) );
481
482 }
483
484 /**
485 * @param $post
486 */
487 public function attachment_meta_box_callback( $post ) {
488 ob_start();
489
490 $sm_cloud = get_post_meta( $post->ID, 'sm_cloud', 1 );
491
492 if ( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) { ?>
493
494 <?php if( !empty( $sm_cloud[ 'cacheControl' ] ) ) { ?>
495 <div class="misc-pub-cache-control hidden">
496 <?php _e( 'Cache Control:', ud_get_stateless_media()->domain ); ?> <strong><span><?php echo $sm_cloud[ 'cacheControl' ]; ?></span> </strong>
497 </div>
498 <?php } ?>
499
500 <div class="misc-pub-gs-file-link" style="margin-bottom: 15px;">
501 <label>
502 <?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>
503 <input type="text" class="widefat urlfield" readonly="readonly" value="<?php echo esc_attr($sm_cloud[ 'fileLink' ]); ?>" />
504 </label>
505 </div>
506
507 <?php
508
509 if ( !empty( $sm_cloud[ 'bucket' ] ) ) {
510 ?>
511 <div class="misc-pub-gs-bucket" style="margin-bottom: 15px;">
512 <label>
513 <?php _e( 'Storage Bucket:', ud_get_stateless_media()->domain ); ?>
514 <input type="text" class="widefat urlfield" readonly="readonly" value="gs://<?php echo esc_attr($sm_cloud[ 'bucket' ]); ?>" />
515 </label>
516 </div>
517 <?php
518 }
519
520 if ( current_user_can( 'upload_files' ) ) {
521 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
522 ?>
523 <a href="javascript:;" data-type="image" data-id="<?php echo $post->ID; ?>"
524 class="button-secondary sm_inline_sync"><?php _e('Regenerate and Sync with GCS', ud_get_stateless_media()->domain); ?></a>
525 <?php
526 }
527
528 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
529 ?>
530 <a href="javascript:;" data-type="other" data-id="<?php echo $post->ID; ?>"
531 class="button-secondary sm_inline_sync"><?php _e('Sync with GCS', ud_get_stateless_media()->domain); ?></a>
532 <?php
533 }
534 }
535 }
536
537 echo apply_filters( 'sm::attachment::meta', ob_get_clean(), $post->ID );
538 }
539
540 /**
541 * @param $current_path
542 * @return string
543 */
544 public function handle_root_dir( $current_path ) {
545 $root_dir = $this->get( 'sm.root_dir' );
546 $root_dir = trim( $root_dir, '/ ' ); // Remove any forward slash and empty space.
547
548 if ( !empty( $root_dir ) ) {
549 return $root_dir . '/' . $current_path;
550 }
551
552 return $current_path;
553 }
554
555 /**
556 * @param $content
557 * @return mixed
558 */
559 public function the_content_filter( $content ) {
560
561 if ( $upload_data = wp_upload_dir() ) {
562
563 if ( !empty( $upload_data['baseurl'] ) && !empty( $content ) ) {
564 $baseurl = preg_replace('/https?:\/\//','',$upload_data['baseurl']);
565 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
566 $root_dir = !empty( $root_dir ) ? $root_dir . '/' : false;
567 $image_host = $this->get_gs_host();
568 $file_ext = $this->replaceable_file_types();
569 $content = preg_replace( '/(href|src)=(\'|")(https?:\/\/'.str_replace('/', '\/', $baseurl).')\/(.+?)('.$file_ext.')(\'|")/i',
570 '$1=$2'.$image_host.'/'.($root_dir?$root_dir:'').'$4$5$6', $content);
571 }
572 }
573
574 return $content;
575 }
576
577 /**
578 * Return file types supported by File URL Replacement.
579 *
580 */
581 public function replaceable_file_types(){
582 $types = $this->get('sm.body_rewrite_types');
583
584 // Removing extra space.
585 $types = trim($types);
586 $types = preg_replace("/\s{2,}/", ' ', $types);
587
588 $types_arr = explode(' ', $types);
589 return '\.' . implode('|\.', $types_arr);
590 }
591
592 /**
593 * @param $value null unless other filter hooked in this function.
594 * @param $object_id post id
595 * @param $meta_key
596 * @param $single
597 * @return mixed
598 */
599 public function post_metadata_filter($value, $object_id, $meta_key, $single){
600 if(empty($value)){
601 $meta_type = 'post';
602 $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
603
604 if ( !$meta_cache ) {
605 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
606 $meta_cache = $meta_cache[$object_id];
607 }
608
609 if ( ! $meta_key ) {
610 return $this->convert_to_gs_link($meta_cache);
611 }
612
613 if ( isset($meta_cache[$meta_key]) ) {
614 return $this->convert_to_gs_link(array_map('maybe_unserialize', $meta_cache[$meta_key]));
615 }
616
617 if ($single)
618 return '';
619 else
620 return array();
621 }
622
623 return $this->convert_to_gs_link($value);
624
625 }
626
627 /**
628 * Rplace all image link with gs link and return only if meta modified.
629 * @param $meta
630 * @return mixed or null when not changed.
631 */
632 public function convert_to_gs_link($meta){
633 $updated = $meta;
634 if ( $meta && $upload_data = wp_upload_dir() ) {
635 if ( !empty( $upload_data['baseurl'] ) && !empty( $meta ) ) {
636 $baseurl = preg_replace('/https?:\/\//','',$upload_data['baseurl']);
637 $root_dir = trim( $this->get( 'sm.root_dir' ), '/ ' ); // Remove any forward slash and empty space.
638 $root_dir = !empty( $root_dir ) ? $root_dir . '/': false;
639 $image_host = $this->get_gs_host().'/'.($root_dir?$root_dir:'');
640 $file_ext = $this->replaceable_file_types();
641 $updated = $this->_convert_to_gs_link($meta, $image_host, $baseurl, $file_ext);
642 }
643 }
644
645 if($updated == $meta){
646 return null; // Not changed.
647 }
648 return $updated;
649 }
650
651 /**
652 * Rplace all image link with gs link
653 * @param $meta
654 * @return mixed
655 */
656 public function _convert_to_gs_link($meta, $image_host, $baseurl, $file_ext){
657 if(is_array($meta)){
658 foreach ($meta as $key => $value) {
659 $meta[$key] = $this->_convert_to_gs_link($value, $image_host, $baseurl, $file_ext);
660 }
661 return $meta;
662 } elseif (is_object($meta) && $meta instanceof \stdClass ) {
663 foreach (get_object_vars($meta) as $key => $value) {
664 $meta->{$key} = $this->_convert_to_gs_link($value, $image_host, $baseurl, $file_ext);
665 }
666 return $meta;
667 } elseif(is_string($meta)){
668 return preg_replace( '/(https?:\/\/'.str_replace('/', '\/', $baseurl).')\/(.+?)(\.jpg|\.png|\.gif|\.jpeg|\.pdf)/i', $image_host.'$2$3', $meta);
669 }
670
671 return $meta;
672 }
673
674
675 /**
676 * @param $links
677 * @param $file
678 * @return mixed
679 */
680 public function plugin_action_links( $links, $file ) {
681
682 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless-media.php' ) ) {
683 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
684 array_unshift( $links, $settings_link );
685 }
686
687 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless.php' ) ) {
688 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
689 array_unshift( $links, $settings_link );
690 }
691
692 return $links;
693 }
694
695 /**
696 * Determines if plugin is loaded via mu-plugins
697 * or Network Enabled.
698 *
699 * @author peshkov@UD
700 */
701 public function is_network_detected() {
702 /* Plugin is loaded via mu-plugins. */
703
704 if( strpos( Utility::normalize_path( $this->root_path ), Utility::normalize_path( WPMU_PLUGIN_DIR ) ) !== false ) {
705 return true;
706 }
707
708 if( is_multisite() ) {
709 /* Looks through network enabled plugins to see if our one is there. */
710 foreach (wp_get_active_network_plugins() as $path) {
711 // Trying again using readlink in case it's a symlink file.
712 // boot_file is already solved.
713 // wp_normalize_path is helpfull in windows.
714 if (wp_normalize_path($this->boot_file) == wp_normalize_path($path) || (is_link($path) AND $this->boot_file == readlink($path))) {
715 return true;
716 }
717 }
718 }
719 return false;
720 }
721
722 /**
723 * Initialization.
724 * Register scripts and styles
725 */
726 public function admin_init() {
727 $this->show_notice_stateless_cache_busting();
728 wp_register_style( 'wp-stateless', $this->path( 'static/styles/wp-stateless.css', 'url' ), array(), self::$version );
729
730 /* Attachment or upload page */
731 wp_register_script( 'wp-stateless-uploads-js', $this->path( 'static/scripts/wp-stateless-uploads.js', 'url' ), array( 'jquery' ), self::$version );
732
733 /* Setup wizard styles and scripts. */
734 wp_register_style( 'wp-stateless-bootstrap', $this->path( 'static/styles/bootstrap.min.css', 'url' ), array(), '3.3.7' );
735 wp_register_style( 'bootstrap-grid-v4', $this->path( 'static/styles/bootstrap-grid.min.css', 'url' ), array(), '3.3.7' );
736 wp_register_style( 'wp-stateless-setup-wizard', $this->path( 'static/styles/wp-stateless-setup-wizard.css', 'url' ), array(), self::$version );
737
738 wp_register_script( 'async.min', ud_get_stateless_media()->path( 'static/scripts/async.js', 'url' ), array(), ud_get_stateless_media()->version );
739 wp_register_script( 'jquery.history', ud_get_stateless_media()->path( 'static/scripts/jquery.history.js', 'url' ), array( 'jquery' ), ud_get_stateless_media()->version, true );
740 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 );
741 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 );
742 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 );
743 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 );
744 wp_localize_script( 'wp-stateless-setup', 'stateless_l10n', $this->get_l10n_data() );
745
746 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 );
747
748
749 /* Stateless settings page */
750 wp_register_script( 'wp-stateless-settings', ud_get_stateless_media()->path( 'static/scripts/wp-stateless-settings.js', 'url' ), array(), ud_get_stateless_media()->version );
751 wp_localize_script( 'wp-stateless-settings', 'stateless_l10n', $this->get_l10n_data() );
752
753 wp_register_style( 'wp-stateless-settings', $this->path( 'static/styles/wp-stateless-settings.css', 'url' ), array(), self::$version );
754
755 // Sync tab
756 if ( wp_script_is( 'jquery-ui-widget', 'registered' ) ){
757 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' );
758 }
759 else{
760 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' );
761 }
762 wp_register_script( 'wp-stateless-angular', ud_get_stateless_media()->path( 'static/scripts/angular.min.js', 'url' ), array(), '1.5.0', true );
763 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 );
764
765 wp_localize_script('wp-stateless', 'wp_stateless_configs', array(
766 'WP_DEBUG' => defined('WP_DEBUG') ? WP_DEBUG : false,
767 ));
768 wp_localize_script('wp-stateless', 'wp_stateless_settings', ud_get_stateless_media()->get('sm'));
769 wp_localize_script('wp-stateless', 'wp_stateless_compatibility', Module::get_modules());
770 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' );
771
772 }
773
774 public function get_l10n_data($value=''){
775 include ud_get_stateless_media()->path( 'l10n.php', 'dir');
776 return $l10n;
777 }
778
779 /**
780 *
781 * @todo: it should not be loaded everywhere. peshkov@UD
782 * @param $hook
783 */
784 public function admin_enqueue_scripts( $hook ) {
785
786 wp_enqueue_style( 'wp-stateless');
787
788 switch( $hook ) {
789
790 case 'options-media.php':
791 //wp_enqueue_script( 'wp-api' );
792
793 wp_enqueue_script( 'wp-stateless-setup' );
794 break;
795
796 case 'upload.php':
797
798 wp_enqueue_script( 'wp-stateless-uploads-js' );
799
800 break;
801
802 case 'post.php':
803
804 global $post;
805
806 if ( $post->post_type == 'attachment' ) {
807 wp_enqueue_script( 'wp-stateless-uploads-js' );
808 }
809
810 break;
811
812 case $this->settings->setup_wizard_ui:
813 wp_enqueue_style( 'wp-stateless-bootstrap' );
814 wp_enqueue_style( 'wp-stateless-setup-wizard' );
815
816 wp_enqueue_script( 'async.min' );
817 wp_enqueue_script( 'jquery.history' );
818 wp_enqueue_script( 'wp-stateless-validation' );
819 wp_enqueue_script( 'wp-stateless-loading' );
820 wp_enqueue_script( 'wp-stateless-comboBox' );
821 wp_enqueue_script( 'wp-stateless-setup' );
822 wp_enqueue_script( 'wp-stateless-setup-wizard-js' );
823 break;
824 case $this->settings->stateless_settings:
825 wp_enqueue_script( 'wp-stateless-settings' );
826 wp_enqueue_style( 'bootstrap-grid-v4' );
827 wp_enqueue_style( 'wp-stateless-settings' );
828
829 // Sync tab
830 wp_enqueue_script( 'jquery-ui-progressbar' );
831 wp_enqueue_script( 'wp-stateless-angular' );
832 wp_enqueue_script( 'wp-stateless' );
833 wp_enqueue_style( 'jquery-ui-regenthumbs' );
834
835 $data = array(
836 'key' => 'stateless-cache-busting',
837 'class' => 'notice',
838 'title' => sprintf( __( "Stateless mode enables and requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
839 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
840 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.
841 Override with the <a href='%s' target='_blank'>%s</a> constant.", ud_get_stateless_media()->domain),"https://github.com/wpCloud/wp-stateless/wiki/Constants#wp_stateless_media_cache_busting", "WP_STATELESS_MEDIA_CACHE_BUSTING" ),
842 );
843 echo "<script id='template-stateless-cache-busting' type='text/html'>";
844 include ud_get_stateless_media()->path( '/static/views/error-notice.php', 'dir' );
845 echo "</script>";
846 default: break;
847 }
848
849 }
850
851 /**
852 * Add Attributes to media HTML
853 *
854 * @author potanin@UD
855 * @param $attr
856 * @param $attachment
857 * @param $size
858 * @return mixed
859 */
860 public function wp_get_attachment_image_attributes( $attr, $attachment, $size ) {
861
862 $sm_cloud = get_post_meta( $attachment->ID, 'sm_cloud', true );
863 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'name' ] ) ) {
864 $attr[ 'class' ] = $attr[ 'class' ] . ' wp-stateless-item';
865 $attr[ 'data-image-size' ] = is_array( $size ) ? implode( 'x', $size ) : $size;
866 $attr[ 'data-stateless-media-bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
867 $attr[ 'data-stateless-media-name' ] = $sm_cloud[ 'name' ];
868 }
869
870 return $attr;
871
872 }
873
874 /**
875 * Adds filter link to Media Library table.
876 *
877 * @param $views
878 * @return mixed
879 */
880 public function views_upload( $views ) {
881 $views['stateless'] = '<a href="#">' . __( 'Stateless Media' ) . '</a>';
882 return $views;
883 }
884
885 /**
886 * Replace media URL
887 *
888 * @param bool $false
889 * @param integer $id
890 * @param string $size
891 * @return mixed $false
892 */
893 public function image_downsize( $false = false, $id, $size ) {
894
895 if ( !isset( $this->client ) || !$this->client || is_wp_error( $this->client ) ) {
896 return $false;
897 }
898
899 /**
900 * Check if enabled
901 */
902 if ( $this->get( 'sm.mode' ) !== 'cdn' && $this->get( 'sm.mode' ) !== 'stateless' ) {
903 return $false;
904 }
905
906 /** Start determine remote file */
907 $img_url = wp_get_attachment_url($id);
908 $meta = wp_get_attachment_metadata($id);
909 $width = $height = 0;
910 $is_intermediate = false;
911
912 //** try for a new style intermediate size */
913 if ( $intermediate = image_get_intermediate_size( $id, $size ) ) {
914 $img_url = !empty( $intermediate['gs_link'] ) ? $intermediate['gs_link'] : $intermediate['url'];
915 $width = $intermediate['width'];
916 $height = $intermediate['height'];
917 $is_intermediate = true;
918 }
919 //die( '<pre>' . print_r( $intermediate, true ) . '</pre>' );
920
921 /**
922 * maybe try to get images info from sm_cloud
923 * this case may happen when no local files
924 * @author korotkov@ud
925 */
926 if ( !$width && !$height ) {
927 $sm_cloud = get_post_meta( $id, 'sm_cloud', true );
928 if ( is_string($size) && !empty( $sm_cloud['sizes'] ) && !empty( $sm_cloud['sizes'][$size] ) ) {
929 global $_wp_additional_image_sizes;
930
931 $img_url = !empty( $sm_cloud['sizes'][$size]['fileLink'] ) ? $sm_cloud['sizes'][$size]['fileLink'] : $img_url;
932
933 if ( !empty( $_wp_additional_image_sizes[ $size ] ) ) {
934 $width = !empty( $_wp_additional_image_sizes[ $size ]['width'] ) ? $_wp_additional_image_sizes[ $size ]['width'] : $width;
935 $height = !empty( $_wp_additional_image_sizes[ $size ]['height'] ) ? $_wp_additional_image_sizes[ $size ]['height'] : $height;
936 }
937
938 $is_intermediate = true;
939 }
940 }
941
942 if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
943
944 //** any other type: use the real image */
945 $width = $meta['width'];
946 $height = $meta['height'];
947 }
948
949
950 if ( $img_url) {
951
952 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
953 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
954 $img_url = apply_filters('wp_stateless_bucket_link', $img_url);
955 return array( $img_url, $width, $height, $is_intermediate );
956 }
957
958
959 /**
960 * All other cases work as usually
961 */
962 return $false;
963
964 }
965
966 /**
967 * Extends metadata by adding GS information.
968 * Note: must not be called directly. It's used only on hook
969 *
970 * @action wp_get_attachment_metadata
971 * @param $metadata
972 * @param $attachment_id
973 * @return $metadata
974 */
975 public function wp_get_attachment_metadata( $metadata, $attachment_id ) {
976 /* Determine if the media file has GS data at all. */
977 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
978 // If metadata not passed the get metadata from post meta.
979 if(empty($metadata)){
980 $metadata = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
981 }
982
983 if( is_array( $metadata ) && is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
984 $metadata[ 'gs_link' ] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'fileLink' ]);
985 $metadata[ 'gs_name' ] = isset( $sm_cloud[ 'name' ] ) ? $sm_cloud[ 'name' ] : false;
986 $metadata[ 'gs_bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
987 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
988 foreach( $metadata[ 'sizes' ] as $k => $v ) {
989 if( !empty( $sm_cloud[ 'sizes' ][ $k ][ 'name' ] ) ) {
990 $metadata['sizes'][$k]['gs_name'] = $sm_cloud[ 'sizes' ][ $k ][ 'name' ];
991 $metadata['sizes'][$k]['gs_link'] = apply_filters('wp_stateless_bucket_link', $sm_cloud[ 'sizes' ][ $k ][ 'fileLink' ]);
992 }
993 }
994 }
995 }
996
997 return $metadata;
998 }
999
1000 /**
1001 * Returns client object
1002 * or WP_Error on failure.
1003 *
1004 * @author peshkov@UD
1005 * @return object $this->client. \wpCloud\StatelessMedia\GS_Client or \WP_Error
1006 */
1007 public function get_client() {
1008
1009 if( null === $this->client ) {
1010
1011 $key_json = get_site_option( 'sm_key_json' );
1012 if ( empty($key_json) ) {
1013 $key_json = $this->get( 'sm.key_json' );
1014 }
1015
1016 /* Try to initialize GS Client */
1017 $this->client = GS_Client::get_instance( array(
1018 'bucket' => $this->get( 'sm.bucket' ),
1019 'key_json' => $key_json
1020 ) );
1021 }
1022
1023 return $this->client;
1024
1025 }
1026
1027 /**
1028 * Determines if we can connect to Google Storage Bucket.
1029 *
1030 * @author peshkov@UD
1031 */
1032 public function is_connected_to_gs() {
1033
1034 $trnst = get_transient( 'sm::is_connected_to_gs' );
1035
1036 if ( empty($trnst) || false === $trnst || !isset( $trnst[ 'hash' ] ) || $trnst[ 'hash' ] != md5( serialize( $this->get( 'sm' ) ) ) ) {
1037 $trnst = array(
1038 'success' => 'true',
1039 'error' => '',
1040 'hash' => md5( serialize( $this->get( 'sm' ) ) ),
1041 );
1042 $client = $this->get_client();
1043
1044 if ( is_wp_error( $client ) ) {
1045 $trnst[ 'success' ] = 'false';
1046 $trnst[ 'error' ] = $client->get_error_message();
1047 } else {
1048 $connected = $client->is_connected();
1049 if( $connected !== true ) {
1050 $trnst[ 'success' ] = 'false';
1051 $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' ) );
1052
1053 if( is_callable(array($connected, 'getErrors')) && $error = $connected->getErrors() ){
1054 $error = reset($error);
1055 if($error['reason'] == 'accessNotConfigured')
1056 $trnst[ 'error' ] .= "<br>" . make_clickable($error['message']);
1057 }
1058 }
1059 }
1060 set_transient( 'sm::is_connected_to_gs', $trnst, 4 * HOUR_IN_SECONDS );
1061 }
1062
1063 if( isset( $trnst[ 'success' ] ) && $trnst[ 'success' ] == 'false' ) {
1064 return new \WP_Error( 'error', ( !empty( $trnst[ 'error' ] ) ? $trnst[ 'error' ] : __( 'There is an Error on connection to Google Storage.', $this->domain ) ) );
1065 }
1066
1067 return true;
1068 }
1069
1070 /**
1071 * Flush all plugin transients
1072 *
1073 */
1074 public function flush_transients() {
1075 delete_transient( 'sm::is_connected_to_gs' );
1076 }
1077
1078 /**
1079 * Plugin Activation
1080 *
1081 */
1082 public function activate() {
1083 add_action( 'activated_plugin', array($this, 'redirect_to_splash') );
1084 add_action( 'activated_plugin', array($this, 'is_new_install'), 1 );
1085 }
1086
1087 public function is_new_install($plugin =''){
1088 if( $plugin == plugin_basename( $this->boot_file ) ) {
1089 $sm_mode = get_option('sm_mode', null);
1090 $hashify_file_name = get_option('sm_hashify_file_name', null);
1091 if($sm_mode == 'stateless' && $hashify_file_name == 'false'){
1092 delete_option('dismissed_notice_stateless_cache_busting');
1093 }
1094 else{
1095 update_option('dismissed_notice_stateless_cache_busting', true);
1096 }
1097 }
1098 }
1099
1100 public function show_notice_stateless_cache_busting(){
1101 $this->errors->add( array(
1102 'key' => 'stateless-cache-busting',
1103 'button' => 'View Settings',
1104 'button_link' => admin_url('upload.php?page=stateless-settings'),
1105 'title' => sprintf( __( "Stateless mode now requires the Cache-Busting option.", ud_get_stateless_media()->domain ) ),
1106 'message' => sprintf( __("WordPress looks at local files to prevent files with the same filenames.
1107 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.
1108 Override with the <a href='%s' target='_blank'>%s</a> constant.", ud_get_stateless_media()->domain),"https://github.com/wpCloud/wp-stateless/wiki/Constants#wp_stateless_media_cache_busting", "WP_STATELESS_MEDIA_CACHE_BUSTING" ),
1109 ), 'notice' );
1110 }
1111
1112 public function redirect_to_splash($plugin =''){
1113 $this->settings = new Settings();
1114
1115 if(defined( 'WP_CLI' ) || $this->settings->get('key_json') || isset($_POST['checked']) && count($_POST['checked']) > 1){
1116 return;
1117 }
1118
1119 if(
1120 !$this->settings->get('key_json') &&
1121 defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true &&
1122 defined('WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL') && WP_STATELESS_MEDIA_HIDE_SETTINGS_PANEL == true
1123 ) {
1124 return;
1125 }
1126
1127 if( !$this->settings->get('key_json') && defined('WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT') && WP_STATELESS_MEDIA_HIDE_SETUP_ASSISTANT == true ) {
1128 $url = $this->get_settings_page_url('?page=stateless-settings');
1129 exit( wp_redirect($url));
1130 }
1131
1132 if( $plugin == plugin_basename( $this->boot_file ) ) {
1133 $url = $this->get_settings_page_url('?page=stateless-setup&step=splash-screen');
1134 if(json_decode(get_site_option('sm_key_json'))){
1135 $url = $this->get_settings_page_url('?page=stateless-settings');
1136 }
1137 exit( wp_redirect($url));
1138 }
1139 }
1140
1141 /**
1142 * Plugin Deactivation
1143 *
1144 */
1145 public function deactivate() {}
1146
1147 /**
1148 * Filter for wp_get_attachment_url();
1149 *
1150 * @param string $url
1151 * @param string $post_id
1152 * @return mixed|null|string
1153 */
1154 public function wp_get_attachment_url( $url = '', $post_id = '' ) {
1155 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
1156 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
1157 $_url = parse_url($sm_cloud[ 'fileLink' ]);
1158 $url = !isset($_url['scheme']) ? ( 'https:' . $sm_cloud[ 'fileLink' ] ) : $sm_cloud[ 'fileLink' ];
1159 return apply_filters('wp_stateless_bucket_link', $url);
1160 }
1161 return $url;
1162 }
1163
1164 /**
1165 * Filter for attachment_url_to_postid()
1166 *
1167 * @param int|false $post_id originally found post ID (or false if not found)
1168 * @param string $url the URL to find the post ID for
1169 * @return int|false found post ID from cloud storage URL
1170 */
1171 public function attachment_url_to_postid( $post_id, $url ) {
1172 global $wpdb;
1173
1174 if ( ! $post_id ) {
1175 $query = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'sm_cloud' AND meta_value LIKE '%s'";
1176
1177 $post_id = $wpdb->get_var( $wpdb->prepare( $query, '%' . $url . '%' ) );
1178 }
1179
1180 return $post_id;
1181 }
1182
1183 /**
1184 * Change Upload BaseURL when CDN Used.
1185 *
1186 * @param $data
1187 * @return mixed
1188 */
1189 public function upload_dir( $data ) {
1190 $data[ 'basedir' ] = $this->get_gs_host();
1191 $data[ 'baseurl' ] = $this->get_gs_host();
1192 $data[ 'url' ] = $data[ 'baseurl' ] . $data[ 'subdir' ];
1193
1194 return $data;
1195
1196 }
1197
1198 /**
1199 * Set Feature Flag constants by parsing composer.json
1200 *
1201 * @todo Make sure settings from DB can override these.
1202 *
1203 * @author potanin@UD
1204 * @return array|mixed|null|object
1205 */
1206 public function parse_feature_flags( ) {
1207
1208 try {
1209
1210 $_raw = file_get_contents( Utility::normalize_path( $this->root_path ) . 'composer.json' );
1211
1212 $_parsed = json_decode( $_raw );
1213
1214 // @todo Catch poorly formatted JSON.
1215 if( !is_object( $_parsed ) ) {
1216 // throw new Error( "unable to parse." );
1217 }
1218
1219 foreach( (array) $_parsed->extra->featureFlags as $_feature ) {
1220
1221 if( !defined( $_feature->constant ) ) {
1222 define( $_feature->constant, $_feature->enabled );
1223
1224 if( $_feature->enabled ) {
1225 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] enabled.' );
1226 } else {
1227 Utility::log( 'Feature flag ' . $_feature->name . ', [' . $_feature->constant . '] disabled.' );
1228 }
1229 }
1230
1231 }
1232
1233 } catch ( Exception $e ) {
1234 Utility::log( 'Unable to parse [composer.json] feature flags. Error: [' . $e->getMessage() . ']' );
1235 // echo 'Caught exception: ', $e->getMessage(), "\n";
1236 }
1237
1238
1239 return isset( $_parsed ) ? $_parsed : null;
1240
1241
1242 }
1243
1244 /**
1245 * Determine if Utility class contains missed function
1246 * in other case, just return NULL to prevent ERRORS
1247 *
1248 * @author peshkov@UD
1249 * @param $name
1250 * @param $arguments
1251 * @return mixed|null
1252 */
1253 public function __call( $name, $arguments ) {
1254 if( is_callable( array( "wpCloud\\StatelessMedia\\Utility", $name ) ) ) {
1255 return call_user_func_array( array( "wpCloud\\StatelessMedia\\Utility", $name ), $arguments );
1256 } else {
1257 return NULL;
1258 }
1259 }
1260
1261 }
1262
1263 }
1264
1265 }
1266