# media-cloud-sync/1.4.2/includes/base/services/service.php

Media Cloud Sync, version 1.4.2. 769 lines.

- Page: https://pluginprobe.com/plugins/media-cloud-sync/1.4.2/code/includes/base/services/service.php
- Raw: https://pluginprobe.com/plugins/media-cloud-sync/1.4.2/raw/includes/base/services/service.php
- Modified: 2026-09-28T18:24:34+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/media-cloud-sync/1.4.2/code/includes/base/services/service.php#L10-L20`.

```php
<?php
namespace Dudlewebs\WPMCS;

defined('ABSPATH') || exit;

class Service {
    private static $instance = null;
    private $assets_url;
    private $version;
    private $token;
    private $service    = false;
    private $providers  = [
        's3'            => ['class' => 'S3',            'sdk' => 's3'],
        'gcloud'        => ['class' => 'GCloud',        'sdk' => 'google'],
        'docean'        => ['class' => 'DOcean',        'sdk' => 's3'],
        'cloudflareR2'  => ['class' => 'CloudflareR2',  'sdk' => 's3'],
        's3compatible'  => ['class' => 'S3Compatible',  'sdk' => 's3'],
    ];

    protected $settings;


    /**
     * Service constructor.
     * @since 1.0.0
     */
    public function __construct() {
        $this->assets_url = WPMCS_ASSETS_URL;
        $this->version    = WPMCS_VERSION;
        $this->token      = WPMCS_TOKEN;

        $this->settings = Utils::get_settings();

        $current_service = Utils::get_service();

        if($current_service) {
            $this->service = $this->get_handler_class($current_service);
        }
    }

    /**
     * Verify Service Credentials
     * @since 1.0.0
     */
    public function verifyCredentials($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }

        $configSource = isset($data['configSource']) ? $data['configSource'] : 'database';

        if($configSource === 'config') {
            // Credentials come from a wp-config.php constant — WPMCS_CONFIG by default, or
            // a caller-supplied constant (e.g. Bucket-to-Bucket's destination uses WPMCS_BTB_CONFIG).
            $constant = !empty($data['wpConfigConstant']) ? $data['wpConfigConstant'] : 'WPMCS_CONFIG';
            if(!Utils::is_wp_config_credentials_defined($constant)) {
                return [
                    'success' => false,
                    /* translators: %s: wp-config.php constant name */
                    'message' => sprintf(esc_html__('%s is not defined in wp-config.php', 'media-cloud-sync'), $constant)
                ];
            }

            $config  = Utils::get_wp_config_credentials($constant);
            $missing = [];
            foreach(self::get_required_config_keys($service) as $key) {
                if(!isset($config[$key]) || $config[$key] === '') {
                    $missing[] = $key;
                }
            }
            if(!empty($missing)) {
                return [
                    'success' => false,
                    /* translators: 1: wp-config.php constant name, 2: comma separated list of missing configuration keys */
                    'message' => sprintf(esc_html__('%1$s is missing key(s): %2$s', 'media-cloud-sync'), $constant, implode(', ', $missing))
                ];
            }
        } else {
            $config = $this->resolve_config($data);
        }

        return $handler_class->verifyCredentials($config);
    }

    /**
     * True if any of the given values is empty.
     * Shared guard for provider config-validation checks (region/access_key/
     * secret_key/bucket_name/etc.) so each provider class doesn't reimplement
     * the same empty() chain.
     * @since 1.3.11
     * @param array $fields
     * @return bool
     */
    public static function has_missing_fields(array $fields) {
        foreach ($fields as $field) {
            if (empty($field)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Resolve the credential config for a wizard request. 'config' pulls credentials from a
     * wp-config.php constant instead of the payload; 'existing' reuses the site's currently
     * connected primary connection's credentials (e.g. Bucket-to-Bucket's destination form,
     * migrating to a different bucket under the same account).
     * @since 1.3.11
     * @param array $data
     * @return array
     */
    private function resolve_config($data) {
        $configSource = isset($data['configSource']) ? $data['configSource'] : 'database';
        if($configSource === 'config') {
            $constant = !empty($data['wpConfigConstant']) ? $data['wpConfigConstant'] : 'WPMCS_CONFIG';
            return Utils::get_wp_config_credentials($constant);
        }
        if($configSource === 'existing') {
            $current = Utils::get_credentials('', []);
            return isset($current['config']) && is_array($current['config']) ? $current['config'] : [];
        }
        return isset($data['config']) ? $data['config'] : [];
    }

    /**
     * Required configuration keys per provider, used to validate the
     * WPMCS_CONFIG constant before attempting credential verification.
     * @since 1.3.11
     * @param string $service
     * @return array
     */
    public static function get_required_config_keys($service) {
        $required = [
            's3'           => ['access_key', 'secret_key', 'region'],
            'gcloud'       => ['config_json'],
            'docean'       => ['access_key', 'secret_key', 'region'],
            'cloudflareR2' => ['account_id', 'access_key', 'secret_key'],
            's3compatible' => ['endpoint', 'access_key', 'secret_key'],
        ];
        return isset($required[$service]) ? $required[$service] : [];
    }

    /**
     * Persist a connection status result and normalize the response payload.
     * @since 1.3.11
     * @param string $status_key
     * @param array  $result
     * @return array
     */
    private function persist_connection_status($status_key, $result) {
        $lastChecked = isset($result['lastChecked']) ? $result['lastChecked'] : time();
        $success     = !empty($result['success']);
        $message     = isset($result['message']) ? $result['message'] : '';

        Utils::set_status($status_key, [
            'status'      => $success,
            'message'     => $message,
            'lastChecked' => $lastChecked,
        ]);

        return [
            'success'     => $success,
            'message'     => $message,
            'lastChecked' => $lastChecked,
        ];
    }

    /**
     * Verify saved storage credentials and bucket write access.
     * @since 1.3.11
     * @return array
     */
    private function run_storage_status_check() {
        if(!Utils::is_service_enabled()) {
            return $this->persist_connection_status('storageCredentials', [
                'success'     => false,
                'message'     => Utils::get_service_configuration_error(),
                'lastChecked' => time(),
            ]);
        }

        $credentials = Utils::get_credentials();
        $data = [
            'service'      => isset($credentials['service']) ? $credentials['service'] : Utils::get_service(),
            'configSource' => Utils::get_credentials_source(),
            'config'       => isset($credentials['config']) ? $credentials['config'] : [],
            'bucketData'   => [
                'config' => isset($credentials['bucketConfig']) ? $credentials['bucketConfig'] : [],
            ],
        ];

        $result = $this->verifyObjectWritePermission($data);

        return $this->persist_connection_status('storageCredentials', $result);
    }

    /**
     * Verify CDN / delivery read access using saved credentials.
     * @since 1.3.11
     * @return array
     */
    private function run_cdn_status_check() {
        if(!Utils::is_service_enabled()) {
            return $this->persist_connection_status('cdnRead', [
                'success'     => false,
                'message'     => Utils::get_service_configuration_error(),
                'lastChecked' => time(),
            ]);
        }

        if(!$this->service) {
            return $this->persist_connection_status('cdnRead', [
                'success'     => false,
                'message'     => esc_html__('No service selected', 'media-cloud-sync'),
                'lastChecked' => time(),
            ]);
        }

        return $this->persist_connection_status('cdnRead', $this->service->verifyObjectReadPermission());
    }

    /**
     * Run one or more saved-connection status checks.
     * @since 1.3.11
     * @param string $check storage|cdn|all
     * @return array
     */
    public function verifyStatus($check = 'storage') {
        $check = is_string($check) ? strtolower($check) : 'storage';

        if($check === 'write') {
            $check = 'storage';
        } elseif($check === 'read') {
            $check = 'cdn';
        }

        if($check === 'all') {
            $storage = $this->run_storage_status_check();
            $cdn     = $this->run_cdn_status_check();

            return [
                'success' => !empty($storage['success']) && !empty($cdn['success']),
                'checks'  => [
                    'storageCredentials' => $storage,
                    'cdnRead'              => $cdn,
                ],
            ];
        }

        if($check === 'cdn') {
            return $this->run_cdn_status_check();
        }

        if($check !== 'storage') {
            return [
                'success' => false,
                'message' => esc_html__('Invalid status check type', 'media-cloud-sync'),
            ];
        }

        return $this->run_storage_status_check();
    }

    /**
     * @deprecated 1.3.11 Use verifyStatus( 'storage' ).
     */
    public function verifyWrite() {
        return $this->verifyStatus('storage');
    }

    /**
     * @deprecated 1.3.11 Use verifyStatus( 'cdn' ).
     */
    public function verifyRead() {
        return $this->verifyStatus('cdn');
    }

    /**
     * Verify Bucket Exist
     * @since 1.0.0
     */
    public function verifyBucketExist($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }

        $config                 = $this->resolve_config($data);
        $bucketData             = isset($data['bucketData']) ? $data['bucketData'] : [];
        $bucketConfig           = isset($bucketData['config']) ? $bucketData['config'] : [];

        return $handler_class->verifyBucketExist($config, $bucketConfig);
    }

    /**
     * Verify Bucket Credentials
     * @since 1.0.0
     */
    public function createBucket($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }
        
        $config                 = $this->resolve_config($data);
        $bucketData             = isset($data['bucketData']) ? $data['bucketData'] : [];
        $bucketAddNewConfig     = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];

        return $handler_class->createBucket( $config, $bucketAddNewConfig );
    }

    /**
     * Verify Object write permission
     * @since 1.0.0
     */
    public function verifyObjectWritePermission($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }

        $config             = $this->resolve_config($data);
        $bucketData         = isset($data['bucketData']) ? $data['bucketData'] : [];
        if(isset($bucketData['addNew']) && $bucketData['addNew']) {
            $bucketConfig   = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
        } else {
            $bucketConfig   = isset($bucketData['config']) ? $bucketData['config'] : [];
        }

        return $handler_class->verifyObjectWritePermission($config, $bucketConfig);
    }


    /**
     * Verify Object delete permission
     * @since 1.0.0
     */
    public function verifyObjectDeletePermission($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }

        $config     = $this->resolve_config($data);
        $bucketData = isset($data['bucketData']) ? $data['bucketData'] : [];
        if(isset($bucketData['addNew']) && $bucketData['addNew']) {
            $bucketConfig   = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
        } else {
            $bucketConfig   = isset($bucketData['config']) ? $bucketData['config'] : [];
        }

        return $handler_class->verifyObjectDeletePermission( $config, $bucketConfig );
    }


    /**
     * Get Bucket Security Settings
     */
    public function getBucketSecuritySettings($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }

        if(!method_exists($handler_class, 'getBucketSecuritySettings')) {
            $result = [
                'success' => false,
                'message' => esc_html__('Service does not have getBucketSecuritySettings method', 'media-cloud-sync')
            ];
            return $result;
        }

        $config                 = $this->resolve_config($data);
        $bucketData             = isset($data['bucketData']) ? $data['bucketData'] : [];
        if(isset($bucketData['addNew']) && $bucketData['addNew']) {
            $bucketConfig           = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
        } else {
            $bucketConfig           = isset($bucketData['config']) ? $bucketData['config'] : [];
        }

        return $handler_class->getBucketSecuritySettings( $config, $bucketConfig );
    }


     /**
     * Change Bucket Public Access
     * @since 1.0.0
     * @param array $data
     */
    public function changePublicAccess($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }

        if(method_exists($handler_class, 'changePublicAccess') == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('Method not supported for this service', 'media-cloud-sync')
            ];
            return $result;
        }

        $config         = $this->resolve_config($data);
        $bucketData     = isset($data['bucketData']) ? $data['bucketData'] : [];
        if(isset($bucketData['addNew']) && $bucketData['addNew']) {
            $bucketConfig   = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
        } else {
            $bucketConfig   = isset($bucketData['config']) ? $bucketData['config'] : [];
        }
        $value  = isset($data['value']) ? $data['value'] : false;

        return $handler_class->changePublicAccess( $config, $bucketConfig, $value );
    }
    
    /**
     * Change bucket ownership
     */
    public function changeObjectOwnership($data) {
        $result = [
            'success' => false,
            'message' => esc_html__('Something went wrong', 'media-cloud-sync')
        ];
        
        $service = isset($data['service'])? $data['service'] : false;
        $handler_class = $this->get_handler_class($service);

        if($service == false || $handler_class == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('No service selected', 'media-cloud-sync')
            ];
            return $result;
        }

        if(method_exists($handler_class, 'changeObjectOwnership') == false) {
            $result = [
                'success' => false,
                'message' => esc_html__('Method not supported for this service', 'media-cloud-sync')
            ];
            return $result;
        }

        $config                 = $this->resolve_config($data);
        $bucketData             = isset($data['bucketData']) ? $data['bucketData'] : [];
        if(isset($bucketData['addNew']) && $bucketData['addNew']) {
            $bucketConfig           = isset($bucketData['addNewConfig']) ? $bucketData['addNewConfig'] : [];
        } else {
            $bucketConfig           = isset($bucketData['config']) ? $bucketData['config'] : [];
        }
        $value  = isset($data['value']) ? $data['value'] : false;

        return $handler_class->changeObjectOwnership( $config, $bucketConfig, $value );
    }

    
    /**
     * Generates a URL for a given key in the cloud storage.
     * 
     * @param string $key The key of the object in the cloud storage.
     * 
     * @return string The URL of the object.
     */
    public function get_url($key) {
        if (!$this->service) {
            return '';
        }
        return $this->service->generate_file_url($key);
    }


    /**
     * Checks if a given URL is from a provider.
     * 
     * @param string $url The URL to be checked.
     * 
     * @return bool True if the URL is from a provider, false otherwise.
     */
    public function is_provider_url($url) {
        if (!$this->service) {
            return false;
        }
        return $this->service->is_provider_url($url);
    }


    /**
     * Get private URL
     * @since 1.0.0
     */
    public function get_private_url($path) {
        if (!$this->service) {
            return false;
        }
        $url_result = $this->service->get_private_url($path);
        if(isset($url_result['success']) && $url_result['success']) {
            return isset($url_result['file_url']) ? $url_result['file_url'] : false;
        }
        return false;
    }


    /**
     * Upload a single file to the cloud storage.
     *
     * @param string $file_path The absolute path to the file on the local server.
     * @param string $relative_source_path The relative path to the file on the local server.
     * @param string $prefix An optional prefix to add to the cloud storage path.
     * @param bool   $is_private Whether the file should be placed under the private path.
     * @return array The result of the upload operation, including success status and any relevant messages.
     */
    public function uploadSingle($file_path, $relative_source_path, $prefix = '', $is_private = false) {
        if (!$this->service) {
            return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
        }
        return $this->service->uploadSingle($file_path, $relative_source_path, $prefix, $is_private);
    }


    public function deleteSingle($key) {
        if (!$this->service) {
            return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
        }
        return $this->service->deleteSingle($key);
    }

    /**
     * Deletes every version/generation of $key, including delete markers — a plain
     * deleteSingle() on a versioned bucket only removes the live copy, leaving older
     * versions (and the storage they use) behind. Used where a key is being permanently
     * relocated (e.g. moving a folder) and shouldn't leave anything recoverable at the old
     * path. Safe to call on a non-versioned bucket too — it's then equivalent to deleteSingle().
     * @since 1.3.14
     */
    public function purge_all_versions($key) {
        if (!$this->service || !method_exists($this->service, 'purge_all_versions')) {
            return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
        }
        return $this->service->purge_all_versions($key);
    }

    /**
     * Exposes the active provider's already-constructed SDK client, for callers
     * (e.g. StreamWrapper) that need it directly rather than going through this
     * class's own upload/delete methods.
     * @since 1.4.3
     */
    public function get_active_client() {
        if (!$this->service || !method_exists($this->service, 'get_client')) {
            return null;
        }
        return $this->service->get_client();
    }


    /**
     * Move object to server from cloud
     */
    public function object_to_server($key, $save_path) {
        if (!$this->service) {
            return false;
        }
        $path_parts = pathinfo($save_path);
        if (!file_exists($path_parts['dirname'])) {
            mkdir($path_parts['dirname'], 0755, true);
        }
        return $this->service->object_to_server($key, $save_path);
    }

    /**
     * Object bytes in memory, no local file — for callers (e.g. zip download) that need
     * the content itself rather than a copy on the server's filesystem.
     * @since 1.3.13
     */
    public function get_object_content($key) {
        if (!$this->service) {
            return false;
        }
        return $this->service->get_object_content($key);
    }

    /**
     * Copy an object to a new path in the cloud storage
     *
     * @param string $key The key of the object to be copied
     * @param string $new_path The new path to move the object to
     * @return array The result of the copy operation
     */
    public function copy_to_new_path($key, $new_path) {
        if (!$this->service) {
            return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync')];
        }
        return $this->service->copy_to_new_path($key, $new_path);
    }

    // Cross-bucket server-side copy on the singleton's own (source) connection.
    public function copy_to_bucket($key, $new_key, $dest_bucket) {
        if (!$this->service || !method_exists($this->service, 'copy_to_bucket')) {
            return ['success' => false, 'code' => 200, 'message' => esc_html__('Not supported for this service', 'media-cloud-sync')];
        }
        return $this->service->copy_to_bucket($key, $new_key, $dest_bucket);
    }

    /**
     * List objects in the currently configured bucket, one folder level at a time by default.
     *
     * @param string $prefix The folder path to list within (empty = bucket root).
     * @param string|null $continuationToken Provider-issued token for the next page.
     * @param int $maxKeys Page size.
     * @param string|null $delimiter '/' for folder-level listing, null for flat/recursive.
     * @return array {success, code, message, folders, objects, next_token}
     */
    public function listObjects($prefix = '', $continuationToken = null, $maxKeys = 1000, $delimiter = '/') {
        if (!$this->service) {
            return ['success' => false, 'code' => 200, 'message' => esc_html__('No storage service configured', 'media-cloud-sync'), 'folders' => [], 'objects' => [], 'next_token' => null];
        }
        return $this->service->listObjects($prefix, $continuationToken, $maxKeys, $delimiter);
    }

    /**
     * Get Service Handler
     *
     * @param string $service
     * @param array|null $credentials Optional explicit credentials to bind the handler to,
     *                                instead of the global Utils::get_credentials() default —
     *                                e.g. a migration destination, independent of this
     *                                singleton's own source-bound connection. Not a singleton
     *                                itself — every call constructs a new SDK client; callers
     *                                reusing one connection across many operations should call
     *                                this once and hold onto the returned handler.
     */
    public function get_handler_class($service, $credentials = null) {
        if(isset($this->providers[$service])) {
            $provider = $this->providers[$service];
            if(!empty($provider['sdk'])) {
                self::load_sdk($provider['sdk']);
            }
            $class = __NAMESPACE__ . '\\' . $provider['class'];
            if(class_exists($class)) {
                return new $class($credentials);
            }
        }
        return false;
    }

    /**
     * Lazy load the bundled SDK autoloader for the given service.
     * Each SDK is required at most once per request.
     *
     * @since 1.3.10
     */
    private static function load_sdk($sdk) {
        static $loaded = [];
        if (isset($loaded[$sdk])) {
            return;
        }
        if ($sdk === 's3') {
            require_once WPMCS_SDK_PATH . 's3/aws-autoloader.php';
        } elseif ($sdk === 'google') {
            require_once WPMCS_SDK_PATH . 'google/autoload.php';
        } else {
            return;
        }
        $loaded[$sdk] = true;
    }

    /**
     * Get the service domain
     * 
     */
    public function get_domain() {
        if (!$this->service) {
            return '';
        }
        return $this->service->get_domain();
    }

    /**
     * Ensures only one instance of Class is loaded or can be loaded.
     *
     * @return Service Class instance
     * @since 1.0.0
     * @static
     */
    public static function instance(){
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}
```
