PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.16.5
UpdraftPlus: WP Backup & Migration Plugin v1.16.5
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / includes / class-storage-methods-interface.php

class-storage-methods-interface.php in UpdraftPlus: WP Backup & Migration Plugin 1.16.5, at includes/class-storage-methods-interface.php

418 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
4
5 /**
6 * A class for interfacing with storage methods.
7 * N.B. This class began life Sep 2018; it is not guaranteed that there are not many places that bypass it that could be ported over to use it.
8 */
9 class UpdraftPlus_Storage_Methods_Interface {
10
11 /**
12 * Instantiate a remote storage object. If one of the same type has previously been fetched, then it will be returned.
13 *
14 * @param String $method - the storage method (e.g. 'dropbox', 's3', etc.)
15 *
16 * @return Object|WP_Error - an instance of UpdraftPlus_BackupModule, or an error
17 */
18 public static function get_storage_object($method) {
19
20 static $objects = array();
21
22 if (!empty($objects[$method])) return $objects[$method];
23
24 $method_class = 'UpdraftPlus_BackupModule_'.$method;
25
26 if (!class_exists($method_class)) include_once UPDRAFTPLUS_DIR.'/methods/'.$method.'.php';
27
28 if (!class_exists($method_class)) return new WP_Error('no_such_storage_class', "The specified storage method ($method) was not found");
29
30 $objects[$method] = new $method_class;
31
32 return $objects[$method];
33 }
34
35 /**
36 * This method will return an array of remote storage options and storage_templates.
37 *
38 * @return Array - returns an array which consists of storage options and storage_templates multidimensional array
39 */
40 public static function get_remote_storage_options_and_templates() {
41
42 global $updraftplus;
43
44 $storage_objects_and_ids = self::get_storage_objects_and_ids(array_keys($updraftplus->backup_methods));
45 $options = array();
46 $templates = array();
47
48 foreach ($storage_objects_and_ids as $method => $method_info) {
49
50 $object = $method_info['object'];
51
52 if (!$object->supports_feature('multi_options')) {
53 ob_start();
54 do_action('updraftplus_config_print_before_storage', $method, null);
55 $object->config_print();
56 $templates[$method] = ob_get_clean();
57 } else {
58 $templates[$method] = $object->get_template();
59 }
60
61 if (isset($method_info['instance_settings'])) {
62 // Add the methods default settings so that we can add new instances
63 $method_info['instance_settings']['default'] = $object->get_default_options();
64
65 foreach ($method_info['instance_settings'] as $instance_id => $instance_options) {
66
67 $opts_without_transform = $instance_options;
68
69 if ($object->supports_feature('multi_options')) {
70 $opts_without_transform['instance_id'] = $instance_id;
71 }
72
73 $opts = $object->transform_options_for_template($opts_without_transform);
74
75 foreach ($object->filter_frontend_settings_keys() as $filter_frontend_settings_key) {
76 unset($opts[$filter_frontend_settings_key]);
77 }
78
79 $options[$method][$instance_id] = $opts;
80 }
81 }
82 }
83
84 return array(
85 'options' => $options,
86 'templates' => $templates,
87 );
88 }
89
90 /**
91 * This method will return an array of remote storage objects and instance settings of the currently connected remote storage services.
92 *
93 * @param Array $services - an list of service identifiers (e.g. ['dropbox', 's3'])
94 *
95 * @uses self::get_storage_object()
96 *
97 * @return Array - returns an array, with a key equal to each member of the $services list passed in. The corresponding value is then an array with keys 'object', 'instance_settings'. The value for 'object' is an UpdraftPlus_BackupModule instance. The value for 'instance_settings' is an array keyed by associated instance IDs, with the values being the associated settings for the instance ID.
98 */
99 public static function get_storage_objects_and_ids($services) {
100
101 $storage_objects_and_ids = array();
102
103 // N.B. The $services can return any type of values (null, false, etc.) as mentioned from one of the comment found
104 // in the "save_backup_to_history" function above especially if upgrading from (very) old versions. Thus,
105 // here we're adding some check to make sure that we're receiving a non-empty array before iterating through
106 // all the backup services that the user has in store.
107 if (empty($services) || !is_array($services)) return $storage_objects_and_ids;
108
109 foreach ($services as $method) {
110
111 if ('none' === $method || '' == $method) continue;
112
113 $remote_storage = self::get_storage_object($method);
114
115 if (is_a($remote_storage, 'UpdraftPlus_BackupModule')) {
116
117 if (!empty($method_objects[$method])) $storage_objects_and_ids[$method] = array();
118
119 $storage_objects_and_ids[$method]['object'] = $remote_storage;
120
121 if ($remote_storage->supports_feature('multi_options')) {
122
123 $settings_from_db = UpdraftPlus_Options::get_updraft_option('updraft_'.$method);
124
125 $settings = is_array($settings_from_db) ? $settings_from_db : array();
126
127 if (!isset($settings['version'])) $settings = self::update_remote_storage_options_format($method);
128
129 if (is_wp_error($settings)) {
130 if (!empty($settings_from_db)) error_log("UpdraftPlus: failed to convert storage options format: $method");
131 $settings = array('settings' => array());
132 }
133
134 if (empty($settings['settings'])) {
135
136 // Try to recover by getting a default set of options for display
137 if (is_callable(array($remote_storage, 'get_default_options'))) {
138 $uuid = 's-'.md5(rand().uniqid().microtime(true));
139 $settings['settings'] = array($uuid => $remote_storage->get_default_options());
140 }
141
142 // See: https://wordpress.org/support/topic/cannot-setup-connectionauthenticate-with-dropbox/
143 if (empty($settings['settings'])) {
144 // This can get sent to the browser, and break the page, if the user has configured that. However, it should now (1.13.6+) be impossible for this condition to occur, now that we only log it after getting some default options.
145 error_log("UpdraftPlus: Warning: settings for $method are empty. A dummy field is usually needed so that something is saved.");
146 }
147
148 }
149
150 if (!empty($settings['settings'])) {
151
152 if (!isset($storage_objects_and_ids[$method]['instance_settings'])) $storage_objects_and_ids[$method]['instance_settings'] = array();
153
154 foreach ($settings['settings'] as $instance_id => $storage_options) {
155 $storage_objects_and_ids[$method]['instance_settings'][$instance_id] = $storage_options;
156 }
157 }
158 } else {
159 if (!isset($storage_objects_and_ids[$method]['instance_settings'])) $storage_objects_and_ids[$method]['instance_settings'] = $remote_storage->get_default_options();
160 }
161
162 } else {
163 error_log("UpdraftPlus: storage method not found: $method");
164 }
165 }
166
167 return $storage_objects_and_ids;
168
169 }
170
171 /**
172 * This converts array-style options (i.e. late 2013-onwards) to
173 * 2017-style multi-array-style options.
174 *
175 * N.B. Don't actually call this on any particular method's options
176 * until the functions which read the options can cope!
177 *
178 * Don't call for settings that aren't array-style. You may lose
179 * the settings if you do.
180 *
181 * It is safe to call this if you are not sure if the options are
182 * already updated.
183 *
184 * @param String $method - the method identifier
185 *
186 * @returns Array|WP_Error - returns the new options, or a WP_Error if it failed
187 */
188 public static function update_remote_storage_options_format($method) {
189
190 global $updraftplus;
191
192 // Prevent recursion
193 static $already_active = false;
194
195 if ($already_active) return new WP_Error('recursion', 'self::update_remote_storage_options_format() was called in a loop. This is usually caused by an options filter failing to correctly process a "recursion" error code');
196
197 if (!file_exists(UPDRAFTPLUS_DIR.'/methods/'.$method.'.php')) return new WP_Error('no_such_method', 'Remote storage method not found', $method);
198
199 // Sanity/inconsistency check
200 $settings_keys = $updraftplus->get_settings_keys();
201
202 $method_key = 'updraft_'.$method;
203
204 if (!in_array($method_key, $settings_keys)) return new WP_Error('no_such_setting', 'Setting not found for this method', $method);
205
206 $current_setting = UpdraftPlus_Options::get_updraft_option($method_key, array());
207 if ('' == $current_setting) $current_setting = array();
208
209 if (!is_array($current_setting) && false !== $current_setting) return new WP_Error('format_unrecognised', 'Settings format not recognised', array('method' => $method, 'current_setting' => $current_setting));
210
211 // Already converted?
212 if (isset($current_setting['version'])) return $current_setting;
213 if (empty($current_setting)) {
214 $remote_storage = self::get_storage_object($method);
215 $current_setting = $remote_storage->get_default_options();
216 }
217 $new_setting = self::wrap_remote_storage_options($current_setting);
218
219 $already_active = true;
220 $updated = UpdraftPlus_Options::update_updraft_option($method_key, $new_setting);
221 $already_active = false;
222
223 if ($updated) {
224 return $new_setting;
225 } else {
226 return new WP_Error('save_failed', 'Saving the options in the new format failed', array('method' => $method, 'current_setting' => $new_setting));
227 }
228
229 }
230
231 /**
232 * This method will return an array of enabled remote storage objects and instance settings of the currently connected remote storage services.
233 *
234 * @param Array $services - an list of service identifiers (e.g. ['dropbox', 's3'])
235 *
236 * @uses self::get_storage_objects_and_ids()
237 *
238 * @return Array - returns an array, with a key equal to only enabled service member of the $services list passed in. The corresponding value is then an array with keys 'object', 'instance_settings'. The value for 'object' is an UpdraftPlus_BackupModule instance. The value for 'instance_settings' is an array keyed by associated enabled instance IDs, with the values being the associated settings for the enabled instance ID.
239 */
240 public static function get_enabled_storage_objects_and_ids($services) {
241
242 $storage_objects_and_ids = self::get_storage_objects_and_ids($services);
243
244 foreach ($storage_objects_and_ids as $method => $method_information) {
245
246 if (!$method_information['object']->supports_feature('multi_options')) continue;
247
248 foreach ($method_information['instance_settings'] as $instance_id => $instance_information) {
249 if (!isset($instance_information['instance_enabled'])) $instance_information['instance_enabled'] = 1;
250 if (empty($instance_information['instance_enabled'])) {
251 unset($storage_objects_and_ids[$method]['instance_settings'][$instance_id]);
252 }
253 }
254
255 if (empty($storage_objects_and_ids[$method]['instance_settings'])) unset($storage_objects_and_ids[$method]);
256 }
257
258 return $storage_objects_and_ids;
259 }
260
261 /**
262 * This method gets the remote storage information and objects and loops over each of them until we get a successful download of the passed in file.
263 *
264 * @param Array $services - a list of connected service identifiers (e.g. 'dropbox', 's3', etc.)
265 * @param String $file - the name of the file
266 * @param Integer $timestamp - the backup timestamp
267 * @param Boolean $restore - a boolean to indicate if the caller of this method is a restore or not; if so, different messages are logged
268 */
269 public static function get_remote_file($services, $file, $timestamp, $restore = false) {
270
271 global $updraftplus;
272
273 $fullpath = $updraftplus->backups_dir_location().'/'.$file;
274
275 $storage_objects_and_ids = self::get_storage_objects_and_ids($services);
276
277 $is_downloaded = false;
278
279 $updraftplus->register_wp_http_option_hooks();
280
281 foreach ($services as $service) {
282
283 if (empty($service) || 'none' == $service || $is_downloaded) continue;
284
285 if ($restore) {
286 $service_description = empty($updraftplus->backup_methods[$service]) ? $service : $updraftplus->backup_methods[$service];
287 $updraftplus->log(__("File is not locally present - needs retrieving from remote storage", 'updraftplus')." ($service_description)", 'notice-restore');
288 }
289
290 $object = $storage_objects_and_ids[$service]['object'];
291
292 if (!$object->supports_feature('multi_options')) {
293 error_log("UpdraftPlus_Storage_Methods_Interface::get_remote_file(): Multi-options not supported by: ".$service);
294 continue;
295 }
296
297 $instance_ids = $storage_objects_and_ids[$service]['instance_settings'];
298 $backups_instance_ids = isset($backup_history[$timestamp]['service_instance_ids'][$service]) ? $backup_history[$timestamp]['service_instance_ids'][$service] : array(false);
299
300 foreach ($backups_instance_ids as $instance_id) {
301
302 if (isset($instance_ids[$instance_id])) {
303 $options = $instance_ids[$instance_id];
304 } else {
305 // If we didn't find a instance id match, it could be a new UpdraftPlus upgrade or a wipe settings with the same details entered so try the default options saved.
306 $options = $object->get_options();
307 }
308
309 $object->set_options($options, false, $instance_id);
310
311 $download = self::download_file($file, $object);
312
313 if (is_readable($fullpath) && false !== $download) {
314 if ($restore) {
315 $updraftplus->log(__('OK', 'updraftplus'), 'notice-restore');
316 } else {
317 clearstatcache();
318 $updraftplus->log('Remote fetch was successful (file size: '.round(filesize($fullpath)/1024, 1).' KB)');
319 $is_downloaded = true;
320 }
321 break 2;
322 } else {
323 if ($restore) {
324 $updraftplus->log(__('Error', 'updraftplus'), 'notice-restore');
325 } else {
326 clearstatcache();
327 if (0 === @filesize($fullpath)) @unlink($fullpath);
328 $updraftplus->log('Remote fetch failed');
329 }
330 }
331 }
332 }
333 $updraftplus->register_wp_http_option_hooks(false);
334 }
335
336 /**
337 * Downloads a specified file into UD's directory
338 *
339 * @param String $file The basename of the file
340 * @param UpdraftPlus_BackupModule $object The object of the service to use to download with.
341 *
342 * @return Boolean - Whether the operation succeeded. Inherited from the storage module's download() method. N.B. At the time of writing it looks like not all modules necessarily return true upon success; but false can be relied upon for detecting failure.
343 */
344 private static function download_file($file, $object) {
345
346 global $updraftplus;
347
348 @set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);
349
350 $service = $object->get_id();
351
352 $updraftplus->log("Requested file from remote service: $service: $file");
353
354 if (method_exists($object, 'download')) {
355
356 try {
357 return $object->download($file);
358 } catch (Exception $e) {
359 $log_message = 'Exception ('.get_class($e).') occurred during download: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
360 error_log($log_message);
361 // @codingStandardsIgnoreLine
362 if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary();
363 $updraftplus->log($log_message);
364 $updraftplus->log(sprintf(__('A PHP exception (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error');
365 return false;
366 // @codingStandardsIgnoreLine
367 } catch (Error $e) {
368 $log_message = 'PHP Fatal error ('.get_class($e).') has occurred during download. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
369 error_log($log_message);
370 // @codingStandardsIgnoreLine
371 if (function_exists('wp_debug_backtrace_summary')) $log_message .= ' Backtrace: '.wp_debug_backtrace_summary();
372 $updraftplus->log($log_message);
373 $updraftplus->log(sprintf(__('A PHP fatal error (%s) has occurred: %s', 'updraftplus'), get_class($e), $e->getMessage()), 'error');
374 return false;
375 }
376 } else {
377 $updraftplus->log("Automatic backup restoration is not available with the method: $service.");
378 $updraftplus->log("$file: ".sprintf(__("The backup archive for this file could not be found. The remote storage method in use (%s) does not allow us to retrieve files. To perform any restoration using UpdraftPlus, you will need to obtain a copy of this file and place it inside UpdraftPlus's working folder", 'updraftplus'), $service)." (".UpdraftPlus_Manipulation_Functions::prune_updraft_dir_prefix($updraftplus->backups_dir_location()).")", 'error');
379 return false;
380 }
381
382 }
383
384 /**
385 * This method will update the old style remote storage options to the new style (Apr 2017) if the user has imported a old style version of settings
386 *
387 * @param Array $options - The remote storage options settings array
388 * @return Array - The updated remote storage options settings array
389 */
390 public static function wrap_remote_storage_options($options) {
391 // Already converted?
392 if (isset($options['version'])) return $options;
393
394 // Generate an instance id
395 $uuid = self::generate_instance_id();
396
397 $new_setting = array(
398 'version' => 1,
399 );
400
401 if (!is_array($options)) $options = array();
402
403 $new_setting['settings'] = array($uuid => $options);
404
405 return $new_setting;
406 }
407
408 /**
409 * This method will return a random instance id string
410 *
411 * @return String - a random instance id
412 */
413 private static function generate_instance_id() {
414 // Cryptographic randomness not required. The prefix helps avoid potential for type-juggling issues.
415 return 's-'.md5(rand().uniqid().microtime(true));
416 }
417 }
418