Plugin Directory

source: updraftplus/trunk/methods/dreamobjects.php

Last change on this file was 3474785, checked in by DavidAnderson, 3 weeks ago

Release version 1.26.2

File size: 16.6 KB
Line 
1<?php
2
3if (!defined('ABSPATH')) exit;
4if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
5
6updraft_try_include_file('methods/s3.php', 'require_once');
7
8/**
9 * Converted to multi-options (Feb 2017-) and previous options conversion removed: Yes
10 */
11class UpdraftPlus_BackupModule_dreamobjects extends UpdraftPlus_BackupModule_s3 {
12
13        protected $provider_can_use_aws_sdk = false;
14       
15        protected $provider_has_regions = true;
16
17        /**
18         * Regex for validating custom endpoint in the format `s3.<region>.dream.io`.
19         *
20         * @var string
21         */
22        const ENDPOINT_REGEX = '^s3\.[0-9a-z_-]+\.dream\.io$';
23
24        /**
25         * Class constructor
26         */
27        public function __construct() {
28                add_action('updraftplus_admin_enqueue_scripts', array($this, 'updraftplus_admin_enqueue_scripts'));
29        }
30
31        /**
32         * Enqueue scripts on UpdraftPlus settings page.
33         *
34         * @return void
35         */
36        public function updraftplus_admin_enqueue_scripts() {
37                global $updraftplus;
38                $updraftplus->enqueue_select2();
39        }
40
41        /**
42         * Returns endpoint options.
43         *
44         * @return array
45         */
46        public static function get_endpoints() {
47                // When new endpoint introduced in future, Please add it here and also add it as hard coded option for endpoint dropdown in self::get_partial_configuration_template_for_endpoint()
48                // Put the default first
49                return array(
50                        // Endpoint, then the label
51                        's3.us-east-005.dream.io'    => 's3.us-east-005.dream.io',
52                        'objects-us-east-1.dream.io' => 'objects-us-east-1.dream.io',
53                        'objects-us-west-1.dream.io' => 'objects-us-west-1.dream.io ('.__('Closing 1st October 2018', 'updraftplus').')',
54                );
55        }
56       
57        protected $use_v4 = false;
58
59        /**
60         * Given an S3 object, possibly set the region on it
61         *
62         * @param Object $obj             - like UpdraftPlus_S3
63         * @param String $region          - or empty to fetch one from saved configuration
64         * @param String $bucket_name
65         */
66        protected function set_region($obj, $region = '', $bucket_name = '') {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $bucket_name
67
68                $config = $this->get_config();
69                $endpoint = ('' != $region && 'n/a' != $region) ? $region : $config['endpoint'];
70                global $updraftplus;
71                if ($updraftplus->backup_time) {
72                        $updraftplus->log("Set endpoint (".get_class($obj)."): $endpoint");
73               
74                        // Warning for objects-us-west-1 shutdown in Oct 2018
75                        if ('objects-us-west-1.dream.io' == $endpoint) {
76                                $updraftplus->log("The objects-us-west-1.dream.io endpoint shut down on the 1st October 2018. The upload is expected to fail. Please see the following article for more information https://help.dreamhost.com/hc/en-us/articles/360002135871-Cluster-migration-procedure", 'warning', 'dreamobjects_west_shutdown');
77                        }
78                }
79               
80                $obj->setEndpoint($endpoint);
81        }
82
83        /**
84         * This method overrides the parent method and lists the supported features of this remote storage option.
85         *
86         * @return Array - an array of supported features (any features not mentioned are asuumed to not be supported)
87         */
88        public function get_supported_features() {
89                // This options format is handled via only accessing options via $this->get_options()
90                return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic');
91        }
92
93        /**
94         * Retrieve default options for this remote storage module.
95         *
96         * @return Array - an array of options
97         */
98        public function get_default_options() {
99                return array(
100                        'accesskey' => '',
101                        'secretkey' => '',
102                        'path' => '',
103                );
104        }
105
106        /**
107         * Retrieve specific options for this remote storage module
108         *
109         * @param Boolean $force_refresh - if set, and if relevant, don't use cached credentials, but get them afresh
110         *
111         * @return Array - an array of options
112         */
113        protected function get_config($force_refresh = false) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $force_refresh unused
114                $opts = $this->get_options();
115                $opts['whoweare'] = 'DreamObjects';
116                $opts['whoweare_long'] = 'DreamObjects';
117                $opts['key'] = 'dreamobjects';
118                if (empty($opts['endpoint'])) {
119                        $endpoints = array_keys(self::get_endpoints());
120                        $opts['endpoint'] = $endpoints[0];
121                }
122                return $opts;
123        }
124
125        /**
126         * Get the pre configuration template
127         */
128        public function get_pre_configuration_template() {
129                ?>
130                <tr class="{{get_template_css_classes false}} {{method_display_name}}_pre_config_container">
131                        <td colspan="2">
132                                <a href="https://dreamhost.com/cloud/dreamobjects/" target="_blank"><img alt="{{method_display_name}}" src="{{storage_image_url}}"></a>
133                                <br>
134                                {{{xmlwriter_existence_label}}}
135                                {{{simplexmlelement_existence_label}}}
136                                {{{curl_existence_label}}}
137                                <br>
138                                {{{console_url_text}}}
139                                <p>
140                                        <a href="{{updraftplus_com_link}}" target="_blank">{{ssl_error_text}}</a>
141                                </p>
142                        </td>
143                </tr>
144                <?php
145        }
146
147        /**
148         * Get the configuration template
149         *
150         * @return String - the template, ready for substitutions to be carried out
151         */
152        public function get_configuration_template() {
153                // return $this->get_configuration_template_engine('dreamobjects', 'DreamObjects', 'DreamObjects', 'DreamObjects', 'https://panel.dreamhost.com/index.cgi?tree=storage.dreamhostobjects', '<a href="https://dreamhost.com/cloud/dreamobjects/" target="_blank"><img alt="DreamObjects" src="'.UPDRAFTPLUS_URL.'/images/dreamobjects_logo-horiz-2013.png"></a>');
154                ob_start();
155                ?>
156                <tr class="{{get_template_css_classes true}}">
157                        <th>{{input_accesskey_label}}:</th>
158                        <td><input class="updraft_input--wide udc-wd-600" data-updraft_settings_test="accesskey" type="text" autocomplete="off" id="{{get_template_input_attribute_value "id" "accesskey"}}" name="{{get_template_input_attribute_value "name" "accesskey"}}" value="{{accesskey}}" /></td>
159                </tr>
160                <tr class="{{get_template_css_classes true}}">
161                        <th>{{input_secretkey_label}}:</th>
162                        <td><input class="updraft_input--wide udc-wd-600" data-updraft_settings_test="secretkey" type="{{input_secretkey_type}}" autocomplete="off" id="{{get_template_input_attribute_value "id" "secretkey"}}" name="{{get_template_input_attribute_value "name" "secretkey"}}" value="{{secretkey}}" /></td>
163                </tr>
164                <tr class="{{get_template_css_classes true}}">
165                        <th>{{input_location_label}}:</th>
166                        <td>{{method_id}}://<input class="updraft_input--wide  udc-wd-600" data-updraft_settings_test="path" title="{{input_location_title}}" type="text" id="{{get_template_input_attribute_value "id" "path"}}" name="{{get_template_input_attribute_value "name" "path"}}" value="{{path}}" /></td>
167                </tr>
168                <tr class="{{get_template_css_classes true}}">
169                        <th>{{input_endpoint_label}}</th>
170                        <td>
171                                <select class="select2-storage-config dreamobjects-endpoints" data-field-id="endpoint" data-storage-id="{{method_id}}" data-updraft_settings_test="endpoint" id="{{get_template_input_attribute_value "id" "endpoint"}}" name="{{get_template_input_attribute_value "name" "endpoint"}}" style="width: 360px">
172                                        {{#each dreamobjects_endpoints as |description endpoint|}}
173                                                <option value="{{endpoint}}" {{#ifeq ../endpoint endpoint}}selected="selected"{{/ifeq}}>{{description}}</option>
174                                        {{/each}}
175                                </select>
176                                <span class="updraft-input-error-message">{{invalid_endpoint_error_message}}</span>
177                        </td>
178                </tr>
179                {{{get_template_test_button_html "DreamObjects"}}}
180                <?php
181                return ob_get_clean();
182        }
183       
184        /**
185         * Modifies handerbar template options
186         *
187         * @param array $opts
188         * @return Array - Modified handerbar template options
189         */
190        public function transform_options_for_template($opts) {
191                $opts['endpoint'] = empty($opts['endpoint']) ? '' : $opts['endpoint'];
192                $opts['dreamobjects_endpoints'] = self::get_endpoints();
193                // Add custom endpoint in dropdown.
194                if (!empty($opts['endpoint']) && !isset($opts['dreamobjects_endpoints'][$opts['endpoint']])) {
195                        $opts['dreamobjects_endpoints'][$opts['endpoint']] = $opts['endpoint'];
196                }
197                return $opts;
198        }
199
200        /**
201         * Retrieve a list of template properties by taking all the persistent variables and methods of the parent class and combining them with the ones that are unique to this module, also the necessary HTML element attributes and texts which are also unique only to this backup module
202         * NOTE: Please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses()), or any other technique to prevent XSS attacks that could come via WP hooks
203         *
204         * @return Array an associative array keyed by names that describe themselves as they are
205         */
206        public function get_template_properties() {
207                global $updraftplus, $updraftplus_admin;
208
209                if (!apply_filters('updraftplus_dreamobjects_simplexmlelement_exists', class_exists('SimpleXMLElement'))) {
210                        $simplexmlelement_existence_label = wp_kses(
211                                $updraftplus_admin->show_double_warning(
212                                        '<strong>'.__('Warning', 'updraftplus').':</strong> '.
213                                        /* translators: %s: missing PHP module */
214                                        sprintf(__("Your web server's PHP installation does not include a required module (%s).", 'updraftplus'), 'SimpleXMLElement').' '.
215                                        __("Please contact your web hosting provider's support.", 'updraftplus').' '.
216                                        /* translators: 1: module description, 2: required module */
217                                        sprintf(__('UpdraftPlus\'s %1$s module <strong>requires</strong> %2$s.', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()], 'SimpleXMLElement').' '.
218                                        __('Please do not file any support requests; there is no alternative.', 'updraftplus'),
219                                        $this->get_id(),
220                                        false
221                                ),
222                                $this->allowed_html_for_content_sanitisation()
223                        );
224                } else {
225                        $simplexmlelement_existence_label = '';
226                }
227
228                if (!apply_filters('updraftplus_dreamobjects_xmlwriter_exists', 'UpdraftPlus_S3_Compat' != $this->indicate_s3_class() || !class_exists('XMLWriter'))) {
229                        $xmlwriter_existence_label = wp_kses(
230                                $updraftplus_admin->show_double_warning(
231                                        '<strong>'.__('Warning', 'updraftplus').':</strong> '.
232                                        /* translators: %s: missing PHP module */
233                                        sprintf(__("Your web server's PHP installation does not included a required module (%s).", 'updraftplus'), 'XMLWriter').' '.
234                                        __("Please contact your web hosting provider's support and ask for them to enable it.", 'updraftplus'),
235                                        $this->get_id(),
236                                        false
237                                ),
238                                $this->allowed_html_for_content_sanitisation()
239                        );
240                } else {
241                        $xmlwriter_existence_label = '';
242                }
243
244                $properties = array(
245                        'storage_image_url' => UPDRAFTPLUS_URL."/images/dreamobjects_logo-horiz-2013.png",
246                        'curl_existence_label' => wp_kses($updraftplus_admin->curl_check($updraftplus->backup_methods[$this->get_id()], false, $this->get_id()." hidden-in-updraftcentral", false), $this->allowed_html_for_content_sanitisation()),
247                        'simplexmlelement_existence_label' => $simplexmlelement_existence_label,
248                        'xmlwriter_existence_label' => $xmlwriter_existence_label,
249                        'console_url_text' => sprintf(
250                                /* translators: 1: console URL, 2: service name, 3: service name */
251                                __('Get your access key and secret key from your <a href="%1$s">%2$s console</a>, then pick a (globally unique - all %3$s users) bucket name (letters and numbers) (and optionally a path) to use for storage.', 'updraftplus'),
252                                'https://panel.dreamhost.com/index.cgi?tree=storage.dreamhostobjects',
253                                $updraftplus->backup_methods[$this->get_id()],
254                                $updraftplus->backup_methods[$this->get_id()]
255                        ).' '.__('This bucket will be created for you if it does not already exist.', 'updraftplus'),
256                        'updraftplus_com_link' => apply_filters("updraftplus_com_link", "https://teamupdraft.com/documentation/updraftplus/topics/backing-up/troubleshooting/i-get-ssl-certificate-errors-when-backing-up-and-or-restoring/?utm_source=udp-plugin&utm_medium=referral&utm_campaign=paac&utm_content=dreamobjects-ssl-certificates&utm_creative_format=text"),
257                        'ssl_error_text' => __('If you see errors about SSL certificates, then please go here for help.', 'updraftplus'),
258                        'credentials_creation_link_text' => __('Create Azure credentials in your Azure developer console.', 'updraftplus'),
259                        'configuration_helper_link_text' => __('For more detailed instructions, follow this link.', 'updraftplus'),
260                        /* translators: %s: service name */
261                        'input_accesskey_label' => sprintf(__('%s access key', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
262                        /* translators: %s: service name */
263                        'input_secretkey_label' => sprintf(__('%s secret key', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
264                        'input_secretkey_type' => apply_filters('updraftplus_admin_secret_field_type', 'password'),
265                        /* translators: %s: service name */
266                        'input_location_label' => sprintf(__('%s location', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
267                        'input_location_title' => __('Enter only a bucket name or a bucket and path.', 'updraftplus').' '.__('Examples: mybucket, mybucket/mypath', 'updraftplus'),
268                        /* translators: %s: service name */
269                        'input_endpoint_label' => sprintf(__('%s end-point', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
270                        /* translators: %s: service name */
271                        'input_test_label' => sprintf(__('Test %s Settings', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
272                        /* translators: %s: Desired endpoint format.*/
273                        'invalid_endpoint_error_message' => sprintf(__('Custom endpoint should be in the following format "%s".', 'updraftplus'), 's3.<region>.dream.io'),
274                );
275                return wp_parse_args($properties, $this->get_persistent_variables_and_methods());
276        }
277
278        /**
279         * Ensure that only the DreamObjects endpoints (objects-<region>.dream.io and s3.<region>.dream.io) are allowed and that signature header version 4 must exclusively be used for s3.<region>.dream.io enpoint
280         *
281         * @param Object $storage S3 name
282         * @param Array  $config  array of config details; if the provider does not have the concept of regions, then the key 'endpoint' is required to be set
283         * @param String $bucket  S3 Bucket
284         * @param String $path    S3 Path
285         *
286         * @return Array - N.B. May contain updated versions of $storage and $config
287         */
288        protected function get_bucket_access($storage, $config, $bucket, $path) {
289                if (empty($config['endpoint']) || !self::is_valid_endpoint($config['endpoint'])) throw new Exception('Invalid DreamObjects endpoint: '.$config['endpoint']); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should happen when the exception is caught and printed
290                if (preg_match('/'.self::ENDPOINT_REGEX.'/i', trim($config['endpoint']))) {
291                        $this->use_v4 = true;
292                        $storage->setSignatureVersion('v4');
293                        $storage->useDNSBucketName(false);
294                }
295                return parent::get_bucket_access($storage, $config, $bucket, $path);
296        }
297
298        /**
299         * Perform a test of user-supplied credentials, and echo the result.
300         *
301         * @param array $posted_settings Settings to test.
302         *
303         * @return void Echo the result of credentials test.
304         */
305        public function credentials_test($posted_settings) {
306                if (!empty($posted_settings['endpoint']) && !self::is_valid_endpoint($posted_settings['endpoint'])) {
307                        /* translators: 1: Invalid custom endpoint, 2: Expected endpoint format */
308                        echo sprintf(esc_html__('Failure: Custom endpoint "%1$s" is not in the desired format "%2$s".', 'updraftplus'), $posted_settings['endpoint'], 's3.<region>.dream.io'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Prevent escaping '<' & '>' in endpoint as this message is shown in alert.
309                        return;
310                }
311                parent::credentials_test($posted_settings);
312        }
313
314        /**
315         * Sanitization filter for saving DreamObjects settings.
316         *
317         * @param  array $new_settings New settings passed by user.
318         *
319         * @return array Sanitized settings to be saved in DB.
320         */
321        public function options_filter($new_settings) {
322                $current_settings = UpdraftPlus_Options::get_updraft_option('updraft_dreamobjects', array());
323                // Previous settings would be empty on initial load.
324                if (empty($current_settings)) return parent::options_filter($new_settings);
325
326                $current_settings = $current_settings['settings'];
327                // Check if endpoint is updated to an invalid format, then log it.
328                foreach ($new_settings['settings'] as $instance_id => $new_storage_options) {
329                        if (isset($current_settings[$instance_id]['endpoint'], $new_storage_options['endpoint'])
330                                && $current_settings[$instance_id]['endpoint'] !== $new_storage_options['endpoint']
331                                && !self::is_valid_endpoint($new_storage_options['endpoint'])
332                        ) {
333                                $msg = sprintf('Custom endpoint "%s" is not in the format "s3.<region>.dream.io".', esc_html($new_storage_options['endpoint']));
334                                $this->log($msg, 'error');
335                                error_log('UpdraftPlus: DreamObjects: '.$msg);
336                        }
337                }
338                return parent::options_filter($new_settings);
339        }
340
341        /**
342         * Check if valid endpoint.
343         *
344         * @param string $endpoint DreamObjects endpoint provided by user.
345         *
346         * @return bool True for valid endpoint else false.
347         */
348        public static function is_valid_endpoint($endpoint) {
349                $endpoint  = trim($endpoint);
350                $endpoints = self::get_endpoints();
351                if (isset($endpoints[$endpoint]) || preg_match('/'.self::ENDPOINT_REGEX.'/i', $endpoint)) return true;
352                return false;
353        }
354}
Note: See TracBrowser for help on using the repository browser.