PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
media-cloud-sync / includes / base / services / service.php

service.php in Media Cloud Sync 1.4.1, at includes/base/services/service.php

756 lines 26.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Dudlewebs\WPMCS;
3
4 defined('ABSPATH') || exit;
5
6 class Service {
7 private static $instance = null;
8 private $assets_url;
9 private $version;
10 private $token;
11 private $service = false;
12 private $providers = [
13 's3' => ['class' => 'S3', 'sdk' => 's3'],
14 'gcloud' => ['class' => 'GCloud', 'sdk' => 'google'],
15 'docean' => ['class' => 'DOcean', 'sdk' => 's3'],
16 'cloudflareR2' => ['class' => 'CloudflareR2', 'sdk' => 's3'],
17 's3compatible' => ['class' => 'S3Compatible', 'sdk' => 's3'],
18 ];
19
20 protected $settings;
21
22
23 /**
24 * Service constructor.
25 * @since 1.0.0
26 */
27 public function __construct() {
28 $this->assets_url = WPMCS_ASSETS_URL;
29 $this->version = WPMCS_VERSION;
30 $this->token = WPMCS_TOKEN;
31
32 $this->settings = Utils::get_settings();
33
34 $current_service = Utils::get_service();
35
36 if($current_service) {
37 $this->service = $this->get_handler_class($current_service);
38 }
39 }
40
41 /**
42 * Verify Service Credentials
43 * @since 1.0.0
44 */
45 public function verifyCredentials($data) {
46 $result = [
47 'success' => false,
48 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
49 ];
50
51 $service = isset($data['service'])? $data['service'] : false;
52 $handler_class = $this->get_handler_class($service);
53
54 if($service == false || $handler_class == false) {
55 $result = [
56 'success' => false,
57 'message' => esc_html__('No service selected', 'media-cloud-sync')
58 ];
59 return $result;
60 }
61
62 $configSource = isset($data['configSource']) ? $data['configSource'] : 'database';
63
64 if($configSource === 'config') {
65 // Credentials come from a wp-config.php constant — WPMCS_CONFIG by default, or
66 // a caller-supplied constant (e.g. Bucket-to-Bucket's destination uses WPMCS_BTB_CONFIG).
67 $constant = !empty($data['wpConfigConstant']) ? $data['wpConfigConstant'] : 'WPMCS_CONFIG';
68 if(!Utils::is_wp_config_credentials_defined($constant)) {
69 return [
70 'success' => false,
71 /* translators: %s: wp-config.php constant name */
72 'message' => sprintf(esc_html__('%s is not defined in wp-config.php', 'media-cloud-sync'), $constant)
73 ];
74 }
75
76 $config = Utils::get_wp_config_credentials($constant);
77 $missing = [];
78 foreach(self::get_required_config_keys($service) as $key) {
79 if(!isset($config[$key]) || $config[$key] === '') {
80 $missing[] = $key;
81 }
82 }
83 if(!empty($missing)) {
84 return [
85 'success' => false,
86 /* translators: 1: wp-config.php constant name, 2: comma separated list of missing configuration keys */
87 'message' => sprintf(esc_html__('%1$s is missing key(s): %2$s', 'media-cloud-sync'), $constant, implode(', ', $missing))
88 ];
89 }
90 } else {
91 $config = $this->resolve_config($data);
92 }
93
94 return $handler_class->verifyCredentials($config);
95 }
96
97 /**
98 * True if any of the given values is empty.
99 * Shared guard for provider config-validation checks (region/access_key/
100 * secret_key/bucket_name/etc.) so each provider class doesn't reimplement
101 * the same empty() chain.
102 * @since 1.3.11
103 * @param array $fields
104 * @return bool
105 */
106 public static function has_missing_fields(array $fields) {
107 foreach ($fields as $field) {
108 if (empty($field)) {
109 return true;
110 }
111 }
112 return false;
113 }
114
115 /**
116 * Resolve the credential config for a wizard request. 'config' pulls credentials from a
117 * wp-config.php constant instead of the payload; 'existing' reuses the site's currently
118 * connected primary connection's credentials (e.g. Bucket-to-Bucket's destination form,
119 * migrating to a different bucket under the same account).
120 * @since 1.3.11
121 * @param array $data
122 * @return array
123 */
124 private function resolve_config($data) {
125 $configSource = isset($data['configSource']) ? $data['configSource'] : 'database';
126 if($configSource === 'config') {
127 $constant = !empty($data['wpConfigConstant']) ? $data['wpConfigConstant'] : 'WPMCS_CONFIG';
128 return Utils::get_wp_config_credentials($constant);
129 }
130 if($configSource === 'existing') {
131 $current = Utils::get_credentials('', []);
132 return isset($current['config']) && is_array($current['config']) ? $current['config'] : [];
133 }
134 return isset($data['config']) ? $data['config'] : [];
135 }
136
137 /**
138 * Required configuration keys per provider, used to validate the
139 * WPMCS_CONFIG constant before attempting credential verification.
140 * @since 1.3.11
141 * @param string $service
142 * @return array
143 */
144 public static function get_required_config_keys($service) {
145 $required = [
146 's3' => ['access_key', 'secret_key', 'region'],
147 'gcloud' => ['config_json'],
148 'docean' => ['access_key', 'secret_key', 'region'],
149 'cloudflareR2' => ['account_id', 'access_key', 'secret_key'],
150 's3compatible' => ['endpoint', 'access_key', 'secret_key'],
151 ];
152 return isset($required[$service]) ? $required[$service] : [];
153 }
154
155 /**
156 * Persist a connection status result and normalize the response payload.
157 * @since 1.3.11
158 * @param string $status_key
159 * @param array $result
160 * @return array
161 */
162 private function persist_connection_status($status_key, $result) {
163 $lastChecked = isset($result['lastChecked']) ? $result['lastChecked'] : time();
164 $success = !empty($result['success']);
165 $message = isset($result['message']) ? $result['message'] : '';
166
167 Utils::set_status($status_key, [
168 'status' => $success,
169 'message' => $message,
170 'lastChecked' => $lastChecked,
171 ]);
172
173 return [
174 'success' => $success,
175 'message' => $message,
176 'lastChecked' => $lastChecked,
177 ];
178 }
179
180 /**
181 * Verify saved storage credentials and bucket write access.
182 * @since 1.3.11
183 * @return array
184 */
185 private function run_storage_status_check() {
186 if(!Utils::is_service_enabled()) {
187 return $this->persist_connection_status('storageCredentials', [
188 'success' => false,
189 'message' => Utils::get_service_configuration_error(),
190 'lastChecked' => time(),
191 ]);
192 }
193
194 $credentials = Utils::get_credentials();
195 $data = [
196 'service' => isset($credentials['service']) ? $credentials['service'] : Utils::get_service(),
197 'configSource' => Utils::get_credentials_source(),
198 'config' => isset($credentials['config']) ? $credentials['config'] : [],
199 'bucketData' => [
200 'config' => isset($credentials['bucketConfig']) ? $credentials['bucketConfig'] : [],
201 ],
202 ];
203
204 $result = $this->verifyObjectWritePermission($data);
205
206 return $this->persist_connection_status('storageCredentials', $result);
207 }
208
209 /**
210 * Verify CDN / delivery read access using saved credentials.
211 * @since 1.3.11
212 * @return array
213 */
214 private function run_cdn_status_check() {
215 if(!Utils::is_service_enabled()) {
216 return $this->persist_connection_status('cdnRead', [
217 'success' => false,
218 'message' => Utils::get_service_configuration_error(),
219 'lastChecked' => time(),
220 ]);
221 }
222
223 if(!$this->service) {
224 return $this->persist_connection_status('cdnRead', [
225 'success' => false,
226 'message' => esc_html__('No service selected', 'media-cloud-sync'),
227 'lastChecked' => time(),
228 ]);
229 }
230
231 return $this->persist_connection_status('cdnRead', $this->service->verifyObjectReadPermission());
232 }
233
234 /**
235 * Run one or more saved-connection status checks.
236 * @since 1.3.11
237 * @param string $check storage|cdn|all
238 * @return array
239 */
240 public function verifyStatus($check = 'storage') {
241 $check = is_string($check) ? strtolower($check) : 'storage';
242
243 if($check === 'write') {
244 $check = 'storage';
245 } elseif($check === 'read') {
246 $check = 'cdn';
247 }
248
249 if($check === 'all') {
250 $storage = $this->run_storage_status_check();
251 $cdn = $this->run_cdn_status_check();
252
253 return [
254 'success' => !empty($storage['success']) && !empty($cdn['success']),
255 'checks' => [
256 'storageCredentials' => $storage,
257 'cdnRead' => $cdn,
258 ],
259 ];
260 }
261
262 if($check === 'cdn') {
263 return $this->run_cdn_status_check();
264 }
265
266 if($check !== 'storage') {
267 return [
268 'success' => false,
269 'message' => esc_html__('Invalid status check type', 'media-cloud-sync'),
270 ];
271 }
272
273 return $this->run_storage_status_check();
274 }
275
276 /**
277 * @deprecated 1.3.11 Use verifyStatus( 'storage' ).
278 */
279 public function verifyWrite() {
280 return $this->verifyStatus('storage');
281 }
282
283 /**
284 * @deprecated 1.3.11 Use verifyStatus( 'cdn' ).
285 */
286 public function verifyRead() {
287 return $this->verifyStatus('cdn');
288 }
289
290 /**
291 * Verify Bucket Exist
292 * @since 1.0.0
293 */
294 public function verifyBucketExist($data) {
295 $result = [
296 'success' => false,
297 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
298 ];
299
300 $service = isset($data['service'])? $data['service'] : false;
301 $handler_class = $this->get_handler_class($service);
302
303 if($service == false || $handler_class == false) {
304 $result = [
305 'success' => false,
306 'message' => esc_html__('No service selected', 'media-cloud-sync')
307 ];
308 return $result;
309 }
310
311 $config = $this->resolve_config($data);
312 $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
313 $bucketConfig = isset($bucketData['config']) ? $bucketData['config'] : [];
314
315 return $handler_class->verifyBucketExist($config, $bucketConfig);
316 }
317
318 /**
319 * Verify Bucket Credentials
320 * @since 1.0.0
321 */
322 public function createBucket($data) {
323 $result = [
324 'success' => false,
325 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
326 ];
327
328 $service = isset($data['service'])? $data['service'] : false;
329 $handler_class = $this->get_handler_class($service);
330
331 if($service == false || $handler_class == false) {
332 $result = [
333 'success' => false,
334 'message' => esc_html__('No service selected', 'media-cloud-sync')
335 ];
336 return $result;
337 }
338
339 $config = $this->resolve_config($data);
340 $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
341 $bucketAddNewConfig = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
342
343 return $handler_class->createBucket( $config, $bucketAddNewConfig );
344 }
345
346 /**
347 * Verify Object write permission
348 * @since 1.0.0
349 */
350 public function verifyObjectWritePermission($data) {
351 $result = [
352 'success' => false,
353 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
354 ];
355
356 $service = isset($data['service'])? $data['service'] : false;
357 $handler_class = $this->get_handler_class($service);
358
359 if($service == false || $handler_class == false) {
360 $result = [
361 'success' => false,
362 'message' => esc_html__('No service selected', 'media-cloud-sync')
363 ];
364 return $result;
365 }
366
367 $config = $this->resolve_config($data);
368 $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
369 if(isset($bucketData['addNew']) && $bucketData['addNew']) {
370 $bucketConfig = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
371 } else {
372 $bucketConfig = isset($bucketData['config']) ? $bucketData['config'] : [];
373 }
374
375 return $handler_class->verifyObjectWritePermission($config, $bucketConfig);
376 }
377
378
379 /**
380 * Verify Object delete permission
381 * @since 1.0.0
382 */
383 public function verifyObjectDeletePermission($data) {
384 $result = [
385 'success' => false,
386 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
387 ];
388
389 $service = isset($data['service'])? $data['service'] : false;
390 $handler_class = $this->get_handler_class($service);
391
392 if($service == false || $handler_class == false) {
393 $result = [
394 'success' => false,
395 'message' => esc_html__('No service selected', 'media-cloud-sync')
396 ];
397 return $result;
398 }
399
400 $config = $this->resolve_config($data);
401 $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
402 if(isset($bucketData['addNew']) && $bucketData['addNew']) {
403 $bucketConfig = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
404 } else {
405 $bucketConfig = isset($bucketData['config']) ? $bucketData['config'] : [];
406 }
407
408 return $handler_class->verifyObjectDeletePermission( $config, $bucketConfig );
409 }
410
411
412 /**
413 * Get Bucket Security Settings
414 */
415 public function getBucketSecuritySettings($data) {
416 $result = [
417 'success' => false,
418 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
419 ];
420
421 $service = isset($data['service'])? $data['service'] : false;
422 $handler_class = $this->get_handler_class($service);
423
424 if($service == false || $handler_class == false) {
425 $result = [
426 'success' => false,
427 'message' => esc_html__('No service selected', 'media-cloud-sync')
428 ];
429 return $result;
430 }
431
432 if(!method_exists($handler_class, 'getBucketSecuritySettings')) {
433 $result = [
434 'success' => false,
435 'message' => esc_html__('Service does not have getBucketSecuritySettings method', 'media-cloud-sync')
436 ];
437 return $result;
438 }
439
440 $config = $this->resolve_config($data);
441 $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
442 if(isset($bucketData['addNew']) && $bucketData['addNew']) {
443 $bucketConfig = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
444 } else {
445 $bucketConfig = isset($bucketData['config']) ? $bucketData['config'] : [];
446 }
447
448 return $handler_class->getBucketSecuritySettings( $config, $bucketConfig );
449 }
450
451
452 /**
453 * Change Bucket Public Access
454 * @since 1.0.0
455 * @param array $data
456 */
457 public function changePublicAccess($data) {
458 $result = [
459 'success' => false,
460 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
461 ];
462
463 $service = isset($data['service'])? $data['service'] : false;
464 $handler_class = $this->get_handler_class($service);
465
466 if($service == false || $handler_class == false) {
467 $result = [
468 'success' => false,
469 'message' => esc_html__('No service selected', 'media-cloud-sync')
470 ];
471 return $result;
472 }
473
474 if(method_exists($handler_class, 'changePublicAccess') == false) {
475 $result = [
476 'success' => false,
477 'message' => esc_html__('Method not supported for this service', 'media-cloud-sync')
478 ];
479 return $result;
480 }
481
482 $config = $this->resolve_config($data);
483 $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
484 if(isset($bucketData['addNew']) && $bucketData['addNew']) {
485 $bucketConfig = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
486 } else {
487 $bucketConfig = isset($bucketData['config']) ? $bucketData['config'] : [];
488 }
489 $value = isset($data['value']) ? $data['value'] : false;
490
491 return $handler_class->changePublicAccess( $config, $bucketConfig, $value );
492 }
493
494 /**
495 * Change bucket ownership
496 */
497 public function changeObjectOwnership($data) {
498 $result = [
499 'success' => false,
500 'message' => esc_html__('Something went wrong', 'media-cloud-sync')
501 ];
502
503 $service = isset($data['service'])? $data['service'] : false;
504 $handler_class = $this->get_handler_class($service);
505
506 if($service == false || $handler_class == false) {
507 $result = [
508 'success' => false,
509 'message' => esc_html__('No service selected', 'media-cloud-sync')
510 ];
511 return $result;
512 }
513
514 if(method_exists($handler_class, 'changeObjectOwnership') == false) {
515 $result = [
516 'success' => false,
517 'message' => esc_html__('Method not supported for this service', 'media-cloud-sync')
518 ];
519 return $result;
520 }
521
522 $config = $this->resolve_config($data);
523 $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
524 if(isset($bucketData['addNew']) && $bucketData['addNew']) {
525 $bucketConfig = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
526 } else {
527 $bucketConfig = isset($bucketData['config']) ? $bucketData['config'] : [];
528 }
529 $value = isset($data['value']) ? $data['value'] : false;
530
531 return $handler_class->changeObjectOwnership( $config, $bucketConfig, $value );
532 }
533
534
535 /**
536 * Generates a URL for a given key in the cloud storage.
537 *
538 * @param string $key The key of the object in the cloud storage.
539 *
540 * @return string The URL of the object.
541 */
542 public function get_url($key) {
543 if (!$this->service) {
544 return '';
545 }
546 return $this->service->generate_file_url($key);
547 }
548
549
550 /**
551 * Checks if a given URL is from a provider.
552 *
553 * @param string $url The URL to be checked.
554 *
555 * @return bool True if the URL is from a provider, false otherwise.
556 */
557 public function is_provider_url($url) {
558 if (!$this->service) {
559 return false;
560 }
561 return $this->service->is_provider_url($url);
562 }
563
564
565 /**
566 * Get private URL
567 * @since 1.0.0
568 */
569 public function get_private_url($path) {
570 if (!$this->service) {
571 return false;
572 }
573 $url_result = $this->service->get_private_url($path);
574 if(isset($url_result['success']) && $url_result['success']) {
575 return isset($url_result['file_url']) ? $url_result['file_url'] : false;
576 }
577 return false;
578 }
579
580
581 /**
582 * Upload a single file to the cloud storage.
583 *
584 * @param string $file_path The absolute path to the file on the local server.
585 * @param string $relative_source_path The relative path to the file on the local server.
586 * @param string $prefix An optional prefix to add to the cloud storage path.
587 * @param bool $is_private Whether the file should be placed under the private path.
588 * @return array The result of the upload operation, including success status and any relevant messages.
589 */
590 public function uploadSingle($file_path, $relative_source_path, $prefix = '', $is_private = false) {
591 if (!$this->service) {
592 return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
593 }
594 return $this->service->uploadSingle($file_path, $relative_source_path, $prefix, $is_private);
595 }
596
597
598 public function deleteSingle($key) {
599 if (!$this->service) {
600 return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
601 }
602 return $this->service->deleteSingle($key);
603 }
604
605 /**
606 * Deletes every version/generation of $key, including delete markers — a plain
607 * deleteSingle() on a versioned bucket only removes the live copy, leaving older
608 * versions (and the storage they use) behind. Used where a key is being permanently
609 * relocated (e.g. moving a folder) and shouldn't leave anything recoverable at the old
610 * path. Safe to call on a non-versioned bucket too — it's then equivalent to deleteSingle().
611 * @since 1.3.14
612 */
613 public function purge_all_versions($key) {
614 if (!$this->service || !method_exists($this->service, 'purge_all_versions')) {
615 return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
616 }
617 return $this->service->purge_all_versions($key);
618 }
619
620
621 /**
622 * Move object to server from cloud
623 */
624 public function object_to_server($key, $save_path) {
625 if (!$this->service) {
626 return false;
627 }
628 $path_parts = pathinfo($save_path);
629 if (!file_exists($path_parts['dirname'])) {
630 mkdir($path_parts['dirname'], 0755, true);
631 }
632 return $this->service->object_to_server($key, $save_path);
633 }
634
635 /**
636 * Object bytes in memory, no local file — for callers (e.g. zip download) that need
637 * the content itself rather than a copy on the server's filesystem.
638 * @since 1.3.13
639 */
640 public function get_object_content($key) {
641 if (!$this->service) {
642 return false;
643 }
644 return $this->service->get_object_content($key);
645 }
646
647 /**
648 * Copy an object to a new path in the cloud storage
649 *
650 * @param string $key The key of the object to be copied
651 * @param string $new_path The new path to move the object to
652 * @return array The result of the copy operation
653 */
654 public function copy_to_new_path($key, $new_path) {
655 if (!$this->service) {
656 return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
657 }
658 return $this->service->copy_to_new_path($key, $new_path);
659 }
660
661 // Cross-bucket server-side copy on the singleton's own (source) connection.
662 public function copy_to_bucket($key, $new_key, $dest_bucket) {
663 if (!$this->service || !method_exists($this->service, 'copy_to_bucket')) {
664 return ['success' => false, 'code' => 200, 'message' => esc_html__('Not supported for this service', 'media-cloud-sync')];
665 }
666 return $this->service->copy_to_bucket($key, $new_key, $dest_bucket);
667 }
668
669 /**
670 * List objects in the currently configured bucket, one folder level at a time by default.
671 *
672 * @param string $prefix The folder path to list within (empty = bucket root).
673 * @param string|null $continuationToken Provider-issued token for the next page.
674 * @param int $maxKeys Page size.
675 * @param string|null $delimiter '/' for folder-level listing, null for flat/recursive.
676 * @return array {success, code, message, folders, objects, next_token}
677 */
678 public function listObjects($prefix = '', $continuationToken = null, $maxKeys = 1000, $delimiter = '/') {
679 if (!$this->service) {
680 return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync'), 'folders' => [], 'objects' => [], 'next_token' => null];
681 }
682 return $this->service->listObjects($prefix, $continuationToken, $maxKeys, $delimiter);
683 }
684
685 /**
686 * Get Service Handler
687 *
688 * @param string $service
689 * @param array|null $credentials Optional explicit credentials to bind the handler to,
690 * instead of the global Utils::get_credentials() default —
691 * e.g. a migration destination, independent of this
692 * singleton's own source-bound connection. Not a singleton
693 * itself — every call constructs a new SDK client; callers
694 * reusing one connection across many operations should call
695 * this once and hold onto the returned handler.
696 */
697 public function get_handler_class($service, $credentials = null) {
698 if(isset($this->providers[$service])) {
699 $provider = $this->providers[$service];
700 if(!empty($provider['sdk'])) {
701 self::load_sdk($provider['sdk']);
702 }
703 $class = __NAMESPACE__ . '\\' . $provider['class'];
704 if(class_exists($class)) {
705 return new $class($credentials);
706 }
707 }
708 return false;
709 }
710
711 /**
712 * Lazy load the bundled SDK autoloader for the given service.
713 * Each SDK is required at most once per request.
714 *
715 * @since 1.3.10
716 */
717 private static function load_sdk($sdk) {
718 static $loaded = [];
719 if (isset($loaded[$sdk])) {
720 return;
721 }
722 if ($sdk === 's3') {
723 require_once WPMCS_SDK_PATH . 's3/aws-autoloader.php';
724 } elseif ($sdk === 'google') {
725 require_once WPMCS_SDK_PATH . 'google/autoload.php';
726 } else {
727 return;
728 }
729 $loaded[$sdk] = true;
730 }
731
732 /**
733 * Get the service domain
734 *
735 */
736 public function get_domain() {
737 if (!$this->service) {
738 return '';
739 }
740 return $this->service->get_domain();
741 }
742
743 /**
744 * Ensures only one instance of Class is loaded or can be loaded.
745 *
746 * @return Service Class instance
747 * @since 1.0.0
748 * @static
749 */
750 public static function instance(){
751 if (is_null(self::$instance)) {
752 self::$instance = new self();
753 }
754 return self::$instance;
755 }
756 }