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

705 lines 22.2 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 = '1.9.0';
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 * Instantaite class.
42 */
43 public function init() {
44
45 /**
46 * Register SM metaboxes
47 */
48 add_action( 'admin_init', array( $this, 'register_metaboxes' ) );
49
50 /**
51 * Add custom actions to media rows
52 */
53 add_filter( 'media_row_actions', array( $this, 'add_custom_row_actions' ), 10, 3 );
54
55 /**
56 * Handle switch blog properly.
57 */
58 add_action( 'switch_blog', array( $this, 'on_switch_blog' ), 10, 2 );
59
60 /**
61 * Init AJAX jobs
62 */
63 new Ajax();
64
65 /**
66 * Maybe Upgrade current Version
67 */
68 Upgrader::call( $this->args[ 'version' ] );
69
70 /**
71 * Load WP-CLI Commands
72 */
73 if( defined( 'WP_CLI' ) && WP_CLI ) {
74 include_once($this->path('lib/cli/class-sm-cli-command.php', 'dir'));
75 }
76
77 $this->is_network_detected();
78
79 /**
80 * Define settings and UI.
81 *
82 * Example:
83 *
84 * Get option
85 * $this->get( 'sm.client_id' )
86 *
87 * Manually Update/Add option
88 * $this->set( 'sm.client_id', 'zxcvv12adffse' );
89 */
90 $this->settings = new Settings();
91
92 /**
93 * Add scripts
94 */
95 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
96
97 /**
98 * Hashify file name if option is enabled
99 */
100 if ( $this->get( 'sm.hashify_file_name' ) == 'true' ) {
101 add_filter('sanitize_file_name', array( $this, 'randomize_filename' ), 10);
102 }
103
104 /* Initialize plugin only if Mode is not 'disabled'. */
105 if ( $this->get( 'sm.mode' ) !== 'disabled' ) {
106
107 /**
108 * Override Cache Control is option is enabled
109 */
110 if ( $this->get( 'sm.override_cache_control' ) == 'true' ) {
111 add_filter( 'sm:item:cacheControl', array( $this, 'override_cache_control' ) );
112 }
113
114 /**
115 * Determine if we have issues with connection to Google Storage Bucket
116 * if SM is not disabled.
117 */
118 $is_connected = $this->is_connected_to_gs();
119
120 if ( is_wp_error( $is_connected ) ) {
121 $this->errors->add( $is_connected->get_error_message() );
122 }
123
124 /** Temporary fix to WP 4.4 srcset feature **/
125 add_filter( 'max_srcset_image_width', create_function( '', 'return 1;' ) );
126
127 /**
128 * Carry on only if we do not have errors.
129 */
130 if( !$this->has_errors() ) {
131
132 if( $this->get( 'sm.mode' ) === 'cdn' ) {
133 add_filter( 'wp_get_attachment_image_attributes', array( $this, 'wp_get_attachment_image_attributes' ), 20, 3 );
134 add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 20, 2 );
135 add_filter( 'attachment_url_to_postid', array( $this, 'attachment_url_to_postid' ), 20, 2 );
136
137 if ( $this->get( 'sm.body_rewrite' ) == 'true' ) {
138 add_filter( 'the_content', array( $this, 'the_content_filter' ) );
139 }
140 }
141
142 if ( $root_dir = $this->get( 'sm.root_dir' ) ) {
143 if ( trim( $root_dir ) !== '' ) {
144 add_filter( 'wp_stateless_file_name', array( $this, 'handle_root_dir' ) );
145 }
146 }
147
148 /**
149 * Rewrite Image URLS
150 */
151 add_filter( 'image_downsize', array( $this, 'image_downsize' ), 99, 3 );
152
153 /**
154 * Extends metadata by adding GS information.
155 */
156 add_filter( 'wp_get_attachment_metadata', array( $this, 'wp_get_attachment_metadata' ), 10, 2 );
157
158 /**
159 * Add/Edit Media
160 *
161 * Once added or edited we can get into Attachment ID then get all image sizes and sync them with GS
162 */
163 add_filter( 'wp_generate_attachment_metadata', array( $this, 'add_media' ), 100, 2 );
164
165 if ( $this->get( 'sm.on_fly' ) == 'true' ) {
166 /**
167 * Handle any other on fly generated media
168 */
169 add_filter('image_make_intermediate_size', array($this, 'handle_on_fly'));
170 }
171
172 if ( $this->get( 'sm.delete_remote' ) == 'true' ) {
173 /**
174 * On physical file deletion we remove any from GS
175 */
176 add_filter('delete_attachment', array($this, 'remove_media'));
177 }
178 }
179
180 }
181
182 }
183
184 /**
185 * Get new blog settings once switched blog.
186 * @param $new_blog
187 * @param $prev_blog_id
188 */
189 public function on_switch_blog( $new_blog, $prev_blog_id ) {
190 $this->settings->refresh();
191 }
192
193 /**
194 * @param $actions
195 * @param $post
196 * @param $detached
197 * @return mixed
198 */
199 public function add_custom_row_actions( $actions, $post, $detached ) {
200
201 if ( !current_user_can( 'upload_files' ) ) return $actions;
202
203 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
204 $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>';
205 }
206
207 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
208 $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>';
209 }
210
211 return $actions;
212
213 }
214
215 /**
216 * Register metaboxes
217 */
218 public function register_metaboxes() {
219 add_meta_box(
220 'sm-attachment-metabox',
221 __( 'Google Cloud Storage', ud_get_stateless_media()->domain ),
222 array($this, 'attachment_meta_box_callback'),
223 'attachment',
224 'side',
225 'low'
226 );
227 }
228
229 /**
230 * @param $post
231 */
232 public function attachment_meta_box_callback( $post ) {
233 ob_start();
234
235 $sm_cloud = get_post_meta( $post->ID, 'sm_cloud', 1 );
236
237 if ( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) { ?>
238
239 <?php if( !empty( $sm_cloud[ 'cacheControl' ] ) ) { ?>
240 <div class="misc-pub-cache-control hidden">
241 <?php _e( 'Cache Control:', ud_get_stateless_media()->domain ); ?> <strong><span><?php echo $sm_cloud[ 'cacheControl' ]; ?></span> </strong>
242 </div>
243 <?php } ?>
244
245 <div class="misc-pub-gs-file-link" style="margin-bottom: 15px;">
246 <label>
247 <?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>
248 <input type="text" class="widefat urlfield" readonly="readonly" value="<?php echo esc_attr($sm_cloud[ 'fileLink' ]); ?>" />
249 </label>
250 </div>
251
252 <?php
253
254 if ( !empty( $sm_cloud[ 'bucket' ] ) ) {
255 ?>
256 <div class="misc-pub-gs-bucket" style="margin-bottom: 15px;">
257 <label>
258 <?php _e( 'Storage Bucket:', ud_get_stateless_media()->domain ); ?>
259 <input type="text" class="widefat urlfield" readonly="readonly" value="gs://<?php echo esc_attr($sm_cloud[ 'bucket' ]); ?>" />
260 </label>
261 </div>
262 <?php
263 }
264
265 if ( current_user_can( 'upload_files' ) ) {
266 if ( $post && 'attachment' == $post->post_type && 'image/' == substr( $post->post_mime_type, 0, 6 ) ) {
267 ?>
268 <a href="javascript:;" data-type="image" data-id="<?php echo $post->ID; ?>"
269 class="button-secondary sm_inline_sync"><?php _e('Regenerate and Sync with GCS', ud_get_stateless_media()->domain); ?></a>
270 <?php
271 }
272
273 if ( $post && 'attachment' == $post->post_type && 'image/' != substr( $post->post_mime_type, 0, 6 ) ) {
274 ?>
275 <a href="javascript:;" data-type="other" data-id="<?php echo $post->ID; ?>"
276 class="button-secondary sm_inline_sync"><?php _e('Sync with GCS', ud_get_stateless_media()->domain); ?></a>
277 <?php
278 }
279 }
280 }
281
282 echo apply_filters( 'sm::attachment::meta', ob_get_clean(), $post->ID );
283 }
284
285 /**
286 * @param $current_path
287 * @return string
288 */
289 public function handle_root_dir( $current_path ) {
290 $root_dir = $this->get( 'sm.root_dir' );
291 $root_dir = trim( $root_dir );
292
293 if ( !empty( $root_dir ) ) {
294 return $root_dir . $current_path;
295 }
296
297 return $current_path;
298 }
299
300 /**
301 * @param $content
302 * @return mixed
303 */
304 public function the_content_filter( $content ) {
305
306 if ( $upload_data = wp_upload_dir() ) {
307
308 if ( !empty( $upload_data['baseurl'] ) && !empty( $content ) ) {
309 $baseurl = preg_replace('/https?:\/\//','',$upload_data['baseurl']);
310 $root_dir = trim( $this->get( 'sm.root_dir' ) );
311 $root_dir = !empty( $root_dir ) ? $root_dir : false;
312 $content = preg_replace( '/(href|src)=(\'|")(https?:\/\/'.str_replace('/', '\/', $baseurl).')\/(.+?)(\.jpg|\.png|\.gif|\.jpeg)(\'|")/i',
313 '$1=$2https://storage.googleapis.com/'.$this->get( 'sm.bucket' ).'/'.($root_dir?$root_dir:'').'$4$5$6', $content);
314 }
315 }
316
317 return $content;
318 }
319
320 /**
321 * Handle images on fly
322 *
323 * @param $file
324 * @return mixed
325 */
326 public function handle_on_fly( $file ) {
327
328 $client = ud_get_stateless_media()->get_client();
329 $upload_dir = wp_upload_dir();
330
331 $file_path = str_replace( trailingslashit( $upload_dir[ 'basedir' ] ), '', $file );
332 $file_info = @getimagesize( $file );
333
334 if ( $file_info ) {
335 $_metadata = array(
336 'width' => $file_info[0],
337 'height' => $file_info[1],
338 'object-id' => 'unknown', // we really don't know it
339 'source-id' => md5( $file.ud_get_stateless_media()->get( 'sm.bucket' ) ),
340 'file-hash' => md5( $file )
341 );
342 }
343
344 $client->add_media( apply_filters('sm:item:on_fly:before_add', array_filter( array(
345 'name' => $file_path,
346 'absolutePath' => wp_normalize_path( $file ),
347 'cacheControl' => apply_filters( 'sm:item:cacheControl', 'public, max-age=36000, must-revalidate', $_metadata ),
348 'contentDisposition' => null,
349 'metadata' => $_metadata
350 ) ) ) );
351
352 return $file;
353 }
354
355 /**
356 * @param $links
357 * @param $file
358 * @return mixed
359 */
360 public function plugin_action_links( $links, $file ) {
361
362 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless-media.php' ) ) {
363 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
364 array_unshift( $links, $settings_link );
365 }
366
367 if ($file == plugin_basename( dirname( __DIR__ ) . '/wp-stateless.php' ) ) {
368 $settings_link = '<a href="'. '' .'">'.__( 'Settings' , 'ssd').'</a>';
369 array_unshift( $links, $settings_link );
370 }
371
372 return $links;
373 }
374
375 /**
376 * Determines if plugin is loaded via mu-plugins
377 * or Network Enabled.
378 *
379 * @author peshkov@UD
380 */
381 public function is_network_detected() {
382 /* Plugin is loaded via mu-plugins. */
383
384 if( strpos( Utility::normalize_path( $this->root_path ), Utility::normalize_path( WPMU_PLUGIN_DIR ) ) !== false ) {
385 return true;
386 }
387
388 if( is_multisite() ) {
389 /* Looks through network enabled plugins to see if our one is there. */
390 foreach (wp_get_active_network_plugins() as $path) {
391 if ($this->boot_file == $path) {
392 return true;
393 }
394 }
395 }
396 return false;
397 }
398
399 /**
400 *
401 * @todo: it should not be loaded everywhere. peshkov@UD
402 */
403 public function admin_enqueue_scripts( $hook ) {
404
405 wp_enqueue_style( 'wp-stateless', $this->path( 'static/styles/wp-stateless.css', 'url' ), array(), self::$version );
406
407 switch( $hook ) {
408
409 case 'upload.php':
410
411 wp_enqueue_script( 'wp-stateless-uploads-js', $this->path( 'static/scripts/wp-stateless-uploads.js', 'url' ), array( 'jquery' ), self::$version );
412
413 break;
414
415 case 'post.php':
416
417 global $post;
418
419 if ( $post->post_type == 'attachment' ) {
420 wp_enqueue_script( 'wp-stateless-uploads-js', $this->path( 'static/scripts/wp-stateless-uploads.js', 'url' ), array( 'jquery' ), self::$version );
421 }
422
423 break;
424
425 default: break;
426 }
427
428 }
429
430 /**
431 * Add Attributes to media HTML
432 *
433 * @author potanin@UD
434 * @param $attr
435 * @param $attachment
436 * @param $size
437 * @return mixed
438 */
439 public function wp_get_attachment_image_attributes( $attr, $attachment, $size ) {
440
441 $sm_cloud = get_post_meta( $attachment->ID, 'sm_cloud', true );
442 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'name' ] ) ) {
443 $attr[ 'class' ] = $attr[ 'class' ] . ' wp-stateless-item';
444 $attr[ 'data-image-size' ] = is_array( $size ) ? implode( 'x', $size ) : $size;
445 $attr[ 'data-stateless-media-bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
446 $attr[ 'data-stateless-media-name' ] = $sm_cloud[ 'name' ];
447 }
448
449 return $attr;
450
451 }
452
453 /**
454 * Adds filter link to Media Library table.
455 *
456 * @param $views
457 * @return mixed
458 */
459 public function views_upload( $views ) {
460 $views['stateless'] = '<a href="#">' . __( 'Stateless Media' ) . '</a>';
461 return $views;
462 }
463
464 /**
465 * Replace media URL
466 *
467 * @param bool $false
468 * @param integer $id
469 * @param string $size
470 * @return mixed $false
471 */
472 public function image_downsize( $false = false, $id, $size ) {
473
474 if ( !isset( $this->client ) || !$this->client || is_wp_error( $this->client ) ) {
475 return $false;
476 }
477
478 /**
479 * Check if enabled
480 */
481 if ( $this->get( 'sm.mode' ) !== 'cdn' ) {
482 return $false;
483 }
484
485 /** Start determine remote file */
486 $img_url = wp_get_attachment_url($id);
487 $meta = wp_get_attachment_metadata($id);
488 $width = $height = 0;
489 $is_intermediate = false;
490
491 //** try for a new style intermediate size */
492 if ( $intermediate = image_get_intermediate_size( $id, $size ) ) {
493 $img_url = !empty( $intermediate['gs_link'] ) ? $intermediate['gs_link'] : $intermediate['url'];
494 $width = $intermediate['width'];
495 $height = $intermediate['height'];
496 $is_intermediate = true;
497 }
498 //die( '<pre>' . print_r( $intermediate, true ) . '</pre>' );
499 if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
500
501 //** any other type: use the real image */
502 $width = $meta['width'];
503 $height = $meta['height'];
504 }
505
506
507 if ( $img_url) {
508
509 //** we have the actual image size, but might need to further constrain it if content_width is narrower */
510 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
511
512 return array( $img_url, $width, $height, $is_intermediate );
513 }
514
515
516 /**
517 * All other cases work as usually
518 */
519 return $false;
520
521 }
522
523 /**
524 * Extends metadata by adding GS information.
525 * Note: must not be called directly. It's used only on hook
526 *
527 * @action wp_get_attachment_metadata
528 * @param $metadata
529 * @param $attachment_id
530 * @return $metadata
531 */
532 public function wp_get_attachment_metadata( $metadata, $attachment_id ) {
533 /* Determine if the media file has GS data at all. */
534 $sm_cloud = get_post_meta( $attachment_id, 'sm_cloud', true );
535 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
536 $metadata[ 'gs_link' ] = $sm_cloud[ 'fileLink' ];
537 $metadata[ 'gs_name' ] = isset( $sm_cloud[ 'name' ] ) ? $sm_cloud[ 'name' ] : false;
538 $metadata[ 'gs_bucket' ] = isset( $sm_cloud[ 'bucket' ] ) ? $sm_cloud[ 'bucket' ] : false;
539 if( !empty( $metadata[ 'sizes' ] ) && is_array( $metadata[ 'sizes' ] ) ) {
540 foreach( $metadata[ 'sizes' ] as $k => $v ) {
541 if( !empty( $sm_cloud[ 'sizes' ][ $k ][ 'name' ] ) ) {
542 $metadata['sizes'][$k]['gs_name'] = $sm_cloud[ 'sizes' ][ $k ][ 'name' ];
543 $metadata['sizes'][$k]['gs_link'] = $sm_cloud[ 'sizes' ][ $k ][ 'fileLink' ];
544 }
545 }
546 }
547 }
548
549 return $metadata;
550 }
551
552 /**
553 * Returns client object
554 * or WP_Error on failure.
555 *
556 * @author peshkov@UD
557 * @return object $this->client. \wpCloud\StatelessMedia\GS_Client or \WP_Error
558 */
559 public function get_client() {
560
561 if( null === $this->client ) {
562
563 $key_json = get_site_option( 'sm_key_json' );
564 if ( empty($key_json) ) {
565 $key_json = $this->get( 'sm.key_json' );
566 }
567
568 /* Try to initialize GS Client */
569 $this->client = GS_Client::get_instance( array(
570 'bucket' => $this->get( 'sm.bucket' ),
571 'key_json' => $key_json
572 ) );
573 }
574
575 return $this->client;
576
577 }
578
579 /**
580 * Determines if we can connect to Google Storage Bucket.
581 *
582 * @author peshkov@UD
583 */
584 public function is_connected_to_gs() {
585
586 //$trnst = get_transient( 'sm::is_connected_to_gs' );
587
588 if ( empty($trnst) || false === $trnst || !isset( $trnst[ 'hash' ] ) || $trnst[ 'hash' ] != md5( serialize( $this->get( 'sm' ) ) ) ) {
589 $trnst = array(
590 'success' => 'true',
591 'error' => '',
592 'hash' => md5( serialize( $this->get( 'sm' ) ) ),
593 );
594 $client = $this->get_client();
595 if ( is_wp_error( $client ) ) {
596 $trnst[ 'success' ] = 'false';
597 $trnst[ 'error' ] = $client->get_error_message();
598 } else {
599 if( !$client->is_connected() ) {
600 $trnst[ 'success' ] = 'false';
601 $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' ) );
602 }
603 }
604 set_transient( 'sm::is_connected_to_gs', $trnst, 24 * HOUR_IN_SECONDS );
605 }
606
607 if( isset( $trnst[ 'success' ] ) && $trnst[ 'success' ] == 'false' ) {
608 return new \WP_Error( 'error', ( !empty( $trnst[ 'error' ] ) ? $trnst[ 'error' ] : __( 'There is an Error on connection to Google Storage.', $this->domain ) ) );
609 }
610
611 return true;
612 }
613
614 /**
615 * Flush all plugin transients
616 *
617 */
618 public function flush_transients() {
619 delete_transient( 'sm::is_connected_to_gs' );
620 }
621
622 /**
623 * Plugin Activation
624 *
625 */
626 public function activate() {}
627
628 /**
629 * Plugin Deactivation
630 *
631 */
632 public function deactivate() {}
633
634 /**
635 * Filter for wp_get_attachment_url();
636 *
637 * @param string $url
638 * @param string $post_id
639 * @return mixed|null|string
640 */
641 public function wp_get_attachment_url( $url = '', $post_id = '' ) {
642 $sm_cloud = get_post_meta( $post_id, 'sm_cloud', 1 );
643 if( is_array( $sm_cloud ) && !empty( $sm_cloud[ 'fileLink' ] ) ) {
644 return strpos( $sm_cloud[ 'fileLink' ], 'https://' ) === false ? ( 'https:' . $sm_cloud[ 'fileLink' ] ) : $sm_cloud[ 'fileLink' ];
645 }
646 return $url;
647 }
648
649 /**
650 * Filter for attachment_url_to_postid()
651 *
652 * @param int|false $post_id originally found post ID (or false if not found)
653 * @param string $url the URL to find the post ID for
654 * @return int|false found post ID from cloud storage URL
655 */
656 public function attachment_url_to_postid( $post_id, $url ) {
657 global $wpdb;
658
659 if ( ! $post_id ) {
660 $query = "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'sm_cloud' AND meta_value LIKE '%s'";
661
662 $post_id = $wpdb->get_var( $wpdb->prepare( $query, '%' . $url . '%' ) );
663 }
664
665 return $post_id;
666 }
667
668 /**
669 * Change Upload BaseURL when CDN Used.
670 *
671 * @param $data
672 * @return mixed
673 */
674 public function upload_dir( $data ) {
675
676 $data[ 'baseurl' ] = '//storage.googleapis.com/' . ( $this->get( 'sm.bucket' ) );
677 $data[ 'url' ] = $data[ 'baseurl' ] . $data[ 'subdir' ];
678
679 return $data;
680
681 }
682
683 /**
684 * Determine if Utility class contains missed function
685 * in other case, just return NULL to prevent ERRORS
686 *
687 * @author peshkov@UD
688 * @param $name
689 * @param $arguments
690 * @return mixed|null
691 */
692 public function __call( $name, $arguments ) {
693 if( is_callable( array( "wpCloud\\StatelessMedia\\Utility", $name ) ) ) {
694 return call_user_func_array( array( "wpCloud\\StatelessMedia\\Utility", $name ), $arguments );
695 } else {
696 return NULL;
697 }
698 }
699
700 }
701
702 }
703
704 }
705