PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.13.6
UpdraftPlus: WP Backup & Migration Plugin v1.13.6
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 / methods / openstack-base.php

openstack-base.php in UpdraftPlus: WP Backup & Migration Plugin 1.13.6, at methods/openstack-base.php

554 lines 20.9 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 direct access allowed.');
4
5 if (!class_exists('UpdraftPlus_BackupModule')) require_once(UPDRAFTPLUS_DIR.'/methods/backup-module.php');
6
7 class UpdraftPlus_BackupModule_openstack_base extends UpdraftPlus_BackupModule {
8
9 protected $chunk_size;
10
11 protected $client;
12
13 protected $method;
14
15 protected $desc;
16
17 protected $long_desc;
18
19 protected $img_url;
20
21 public function __construct($method, $desc, $long_desc = null, $img_url = '') {
22 $this->method = $method;
23 $this->desc = $desc;
24 $this->long_desc = (is_string($long_desc)) ? $long_desc : $desc;
25 $this->img_url = $img_url;
26 }
27
28 public function backup($backup_array) {
29
30 global $updraftplus;
31
32 $default_chunk_size = (defined('UPDRAFTPLUS_UPLOAD_CHUNKSIZE') && UPDRAFTPLUS_UPLOAD_CHUNKSIZE > 0) ? max(UPDRAFTPLUS_UPLOAD_CHUNKSIZE, 1048576) : 5242880;
33
34 $this->chunk_size = $updraftplus->jobdata_get('openstack_chunk_size', $default_chunk_size);
35
36 $opts = $this->get_options();
37
38 $this->container = $opts['path'];
39
40 try {
41 $service = $this->get_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
42 } catch (AuthenticationError $e) {
43 $updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
44 $updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
45 return false;
46 } catch (Exception $e) {
47 $updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().') (line: '.$e->getLine().', file: '.$e->getFile().')');
48 $updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
49 return false;
50 }
51 // Get the container
52 try {
53 $this->container_object = $service->getContainer($this->container);
54 } catch (Exception $e) {
55 $updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().') (line: '.$e->getLine().', file: '.$e->getFile().')');
56 $updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
57 return false;
58 }
59
60 foreach ($backup_array as $key => $file) {
61
62 $file_key = 'status_'.md5($file);
63 $file_status = $this->jobdata_get($file_key, null, 'openstack_'.$file_key);
64 if (is_array($file_status) && !empty($file_status['chunks']) && !empty($file_status['chunks'][1]['size'])) $this->chunk_size = $file_status['chunks'][1]['size'];
65
66 // First, see the object's existing size (if any)
67 $uploaded_size = $this->get_remote_size($file);
68
69 try {
70 if (1 === $updraftplus->chunked_upload($this, $file, $this->method."://".$this->container."/$file", $this->desc, $this->chunk_size, $uploaded_size)) {
71 try {
72 if (false !== ($data = fopen($updraftplus->backups_dir_location().'/'.$file, 'r+'))) {
73 $this->container_object->uploadObject($file, $data);
74 $updraftplus->log($this->desc." regular upload: success");
75 $updraftplus->uploaded_file($file);
76 } else {
77 throw new Exception('uploadObject failed: fopen failed');
78 }
79 } catch (Exception $e) {
80 $this->log("$logname regular upload: failed ($file) (".$e->getMessage().")");
81 $this->log("$file: ".sprintf(__('%s Error: Failed to upload', 'updraftplus'), $logname), 'error');
82 }
83 }
84 } catch (Exception $e) {
85 $updraftplus->log($this->desc.' error - failed to upload file'.' ('.$e->getMessage().') (line: '.$e->getLine().', file: '.$e->getFile().')');
86 $updraftplus->log(sprintf(__('%s error - failed to upload file', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
87 return false;
88 }
89 }
90
91 return array('object' => $this->container_object, 'orig_path' => $opts['path'], 'container' => $this->container);
92
93 }
94
95 private function get_remote_size($file) {
96 try {
97 $response = $this->container_object->getClient()->head($this->container_object->getUrl($file))->send();
98 $response_object = $this->container_object->dataObject()->populateFromResponse($response)->setName($file);
99 return $response_object->getContentLength();
100 } catch (Exception $e) {
101 // Allow caller to distinguish between zero-sized and not-found
102 return false;
103 }
104 }
105
106 public function listfiles($match = 'backup_') {
107 $opts = $this->get_options();
108 $container = $opts['path'];
109 $path = $container;
110
111 if (empty($opts['user']) || (empty($opts['apikey']) && empty($opts['password']))) return new WP_Error('no_settings', __('No settings were found', 'updraftplus'));
112
113 try {
114 $service = $this->get_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
115 } catch (Exception $e) {
116 return new WP_Error('no_access', sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')');
117 }
118
119 // Get the container
120 try {
121 $this->container_object = $service->getContainer($container);
122 } catch (Exception $e) {
123 return new WP_Error('no_access', sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')');
124 }
125
126 $results = array();
127 try {
128 $objects = $this->container_object->objectList(array('prefix' => $match));
129 $index = 0;
130 while (false !== ($file = $objects->offsetGet($index)) && !empty($file)) {
131 try {
132 if ((is_object($file) && !empty($file->name))) {
133 $result = array('name' => $file->name);
134 // Rackspace returns the size of a manifested file properly; other OpenStack implementations may not
135 if (!empty($file->bytes)) {
136 $result['size'] = $file->bytes;
137 } else {
138 $size = $this->get_remote_size($file->name);
139 if (false !== $size && $size > 0) $result['size'] = $size;
140 }
141 $results[] = $result;
142 }
143 } catch (Exception $e) {
144 // Catch
145 }
146 $index++;
147 }
148 } catch (Exception $e) {
149 // Catch
150 }
151
152 return $results;
153 }
154
155 public function chunked_upload_finish($file) {
156
157 $chunk_path = 'chunk-do-not-delete-'.$file;
158 try {
159
160 $headers = array(
161 'Content-Length' => 0,
162 'X-Object-Manifest' => sprintf('%s/%s', $this->container, $chunk_path.'_')
163 );
164
165 $url = $this->container_object->getUrl($file);
166 $this->container_object->getClient()->put($url, $headers)->send();
167 return true;
168
169 } catch (Exception $e) {
170 global $updraftplus;
171 $updraftplus->log("Error when sending manifest (".get_class($e)."): ".$e->getMessage());
172 return false;
173 }
174 }
175
176 /**
177 * N.B. Since we use varying-size chunks, we must be careful as to what we do with $chunk_index
178 *
179 * @param string $file Filename
180 * @param string $fp Filepath to be used in chunked upload
181 * @param string $chunk_index Index of chunked upload
182 * @param string $upload_size Size of the upload, in bytes
183 * @param string $upload_start Upload start file size
184 * @param string $upload_end Upload end file size
185 * @param string $total_file_size Total file size
186 * @return boolean
187 */
188 public function chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $total_file_size) {
189
190 global $updraftplus;
191
192 $file_key = 'status_'.md5($file);
193 $file_status = $this->jobdata_get($file_key, null, 'openstack_'.$file_key);
194
195 $next_chunk_size = $upload_size;
196
197 $bytes_already_uploaded = 0;
198
199 $last_uploaded_chunk_index = 0;
200
201 // Once a chunk is uploaded, its status is set, allowing the sequence to be reconstructed
202 if (is_array($file_status) && isset($file_status['chunks']) && !empty($file_status['chunks'])) {
203 foreach ($file_status['chunks'] as $c_id => $c_status) {
204 if ($c_id > $last_uploaded_chunk_index) $last_uploaded_chunk_index = $c_id;
205 if ($chunk_index + 1 == $c_id) {
206 $next_chunk_size = $c_status['size'];
207 }
208 $bytes_already_uploaded += $c_status['size'];
209 }
210 } else {
211 $file_status = array('chunks' => array());
212 }
213
214 $this->jobdata_set($file_key, $file_status, 'openstack_'.$file_key);
215
216 if ($upload_start < $bytes_already_uploaded) {
217 if ($next_chunk_size != $upload_size) {
218 $response = new stdClass;
219 $response->new_chunk_size = $upload_size;
220 $response->log = false;
221 return $response;
222 } else {
223 return 1;
224 }
225 }
226
227 // Shouldn't be able to happen
228 if ($chunk_index <= $last_uploaded_chunk_index) {
229 $updraftplus->log($this->desc.": Chunk sequence error; chunk_index=$chunk_index, last_uploaded_chunk_index=$last_uploaded_chunk_index, upload_start=$upload_start, upload_end=$upload_end, file_status=".json_encode($file_status));
230 }
231
232 // Used to use $chunk_index here, before switching to variable chunk sizes
233 $upload_remotepath = 'chunk-do-not-delete-'.$file.'_'.sprintf("%016d", $chunk_index);
234
235 $remote_size = $this->get_remote_size($upload_remotepath);
236
237 // Without this, some versions of Curl add Expect: 100-continue, which results in Curl then giving this back: curl error: 55) select/poll returned error
238 // Didn't make the difference - instead we just check below for actual success even when Curl reports an error
239 // $chunk_object->headers = array('Expect' => '');
240
241 if ($remote_size >= $upload_size) {
242 $updraftplus->log($this->desc.": Chunk ($upload_start - $upload_end, $chunk_index): already uploaded");
243 } else {
244 $updraftplus->log($this->desc.": Chunk ($upload_start - $upload_end, $chunk_index): begin upload");
245 // Upload the chunk
246 try {
247 $data = fread($fp, $upload_size);
248 $time_start = microtime(true);
249 $this->container_object->uploadObject($upload_remotepath, $data);
250 $time_now = microtime(true);
251 $time_taken = $time_now - $time_start;
252 if ($next_chunk_size < 52428800 && $total_file_size > 0 && $upload_end + 1 < $total_file_size) {
253 $job_run_time = $time_now - $updraftplus->job_time_ms;
254 if ($time_taken < 10) {
255 $upload_rate = $upload_size / max($time_taken, 0.0001);
256 $upload_secs = min(floor($job_run_time), 10);
257 if ($job_run_time < 15) $upload_secs = max(6, $job_run_time*0.6);
258
259 // In megabytes
260 $memory_limit_mb = $updraftplus->memory_check_current();
261 $bytes_used = memory_get_usage();
262 $bytes_free = $memory_limit_mb * 1048576 - $bytes_used;
263
264 $new_chunk = max(min($upload_secs * $upload_rate * 0.9, 52428800, $bytes_free), 5242880);
265 $new_chunk = $new_chunk - ($new_chunk % 5242880);
266 $next_chunk_size = (int) $new_chunk;
267 $updraftplus->jobdata_set('openstack_chunk_size', $next_chunk_size);
268 }
269 }
270
271 } catch (Exception $e) {
272 $updraftplus->log($this->desc." chunk upload: error: ($file / $chunk_index) (".$e->getMessage().") (line: ".$e->getLine().', file: '.$e->getFile().')');
273 // Experience shows that Curl sometimes returns a select/poll error (curl error 55) even when everything succeeded. Google seems to indicate that this is a known bug.
274
275 $remote_size = $this->get_remote_size($upload_remotepath);
276
277 if ($remote_size >= $upload_size) {
278 $updraftplus->log("$file: Chunk now exists; ignoring error (presuming it was an apparently known curl bug)");
279 } else {
280 $updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload', 'updraftplus'), $this->desc), 'error');
281 return false;
282 }
283 }
284 }
285
286 $file_status['chunks'][$chunk_index]['size'] = $upload_size;
287
288 $this->jobdata_set($file_key, $file_status, 'openstack_'.$file_key);
289
290 if ($next_chunk_size != $upload_size) {
291 $response = new stdClass;
292 $response->new_chunk_size = $next_chunk_size;
293 $response->log = true;
294 return $response;
295 }
296
297 return true;
298 }
299
300 public function delete($files, $data = false, $sizeinfo = array()) {
301
302 global $updraftplus;
303 if (is_string($files)) $files = array($files);
304
305 if (is_array($data)) {
306 $container_object = $data['object'];
307 $container = $data['container'];
308 $path = $data['orig_path'];
309 } else {
310 $opts = $this->get_options();
311 $container = $opts['path'];
312 $path = $container;
313 try {
314 $service = $this->get_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
315 } catch (AuthenticationError $e) {
316 $updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
317 $updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
318 return false;
319 } catch (Exception $e) {
320 $updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().')');
321 $updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
322 return false;
323 }
324 // Get the container
325 try {
326 $container_object = $service->getContainer($container);
327 } catch (Exception $e) {
328 $updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().')');
329 $updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
330 return false;
331 }
332
333 }
334
335 $ret = true;
336 foreach ($files as $file) {
337
338 $updraftplus->log($this->desc.": Delete remote: container=$container, path=$file");
339
340 // We need to search for chunks
341 $chunk_path = "chunk-do-not-delete-".$file;
342
343 try {
344 $objects = $container_object->objectList(array('prefix' => $chunk_path));
345 $index = 0;
346 while (false !== ($chunk = $objects->offsetGet($index)) && !empty($chunk)) {
347 try {
348 $name = $chunk->name;
349 $container_object->dataObject()->setName($name)->delete();
350 $updraftplus->log($this->desc.': Chunk deleted: '.$name);
351 } catch (Exception $e) {
352 $updraftplus->log($this->desc." chunk delete failed: $name: ".$e->getMessage());
353 }
354 $index++;
355 }
356 } catch (Exception $e) {
357 $updraftplus->log($this->desc.' chunk delete failed: '.$e->getMessage());
358 }
359
360 // Finally, delete the object itself
361 try {
362 $container_object->dataObject()->setName($file)->delete();
363 $updraftplus->log($this->desc.': Deleted: '.$file);
364 } catch (Exception $e) {
365 $updraftplus->log($this->desc.' delete failed: '.$e->getMessage());
366 $ret = false;
367 }
368 }
369 return $ret;
370 }
371
372 public function download($file) {
373
374 global $updraftplus;
375
376 $opts = $this->get_options();
377
378 try {
379 $service = $this->get_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
380 } catch (AuthenticationError $e) {
381 $updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
382 $updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
383 return false;
384 } catch (Exception $e) {
385 $updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().')');
386 $updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
387 return false;
388 }
389
390 $container = untrailingslashit($opts['path']);
391 $updraftplus->log($this->desc." download: ".$this->method."://$container/$file");
392
393 // Get the container
394 try {
395 $this->container_object = $service->getContainer($container);
396 } catch (Exception $e) {
397 $updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().')');
398 $updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
399 return false;
400 }
401
402 // Get information about the object within the container
403 $remote_size = $this->get_remote_size($file);
404 if (false === $remote_size) {
405 $updraftplus->log('Could not access '.$this->desc.' object');
406 $updraftplus->log(sprintf(__('The %s object was not found', 'updraftplus'), $this->desc), 'error');
407 return false;
408 }
409
410 return (!is_bool($remote_size)) ? $updraftplus->chunked_download($file, $this, $remote_size, true, $this->container_object) : false;
411
412 }
413
414 public function chunked_download($file, $headers, $container_object) {
415 try {
416 $dl = $container_object->getObject($file, $headers);
417 } catch (Exception $e) {
418 global $updraftplus;
419 $updraftplus->log("$file: Failed to download (".$e->getMessage().")");
420 $updraftplus->log("$file: ".sprintf(__("%s Error", 'updraftplus'), $this->desc).": ".__('Error downloading remote file: Failed to download'.' ('.$e->getMessage().")", 'updraftplus'), 'error');
421 return false;
422 }
423 return $dl->getContent();
424 }
425
426 public function credentials_test_go($opts, $path, $useservercerts, $disableverify) {
427
428 if (preg_match("#^([^/]+)/(.*)$#", $path, $bmatches)) {
429 $container = $bmatches[1];
430 $path = $bmatches[2];
431 } else {
432 $container = $path;
433 $path = '';
434 }
435
436 if (empty($container)) {
437 _e('Failure: No container details were given.', 'updraftplus');
438 return;
439 }
440
441 try {
442 $service = $this->get_service($opts, $useservercerts, $disableverify);
443 // @codingStandardsIgnoreLine
444 } catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
445 $response = $e->getResponse();
446 $code = $response->getStatusCode();
447 $reason = $response->getReasonPhrase();
448 if (401 == $code && 'Unauthorized' == $reason) {
449 echo __('Authorisation failed (check your credentials)', 'updraftplus');
450 } else {
451 echo __('Authorisation failed (check your credentials)', 'updraftplus')." ($code:$reason)";
452 }
453 return;
454 } catch (AuthenticationError $e) {
455 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')';
456 return;
457 } catch (Exception $e) {
458 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')';
459 return;
460 }
461
462 try {
463 $container_object = $service->getContainer($container);
464 // @codingStandardsIgnoreLine
465 } catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
466 $response = $e->getResponse();
467 $code = $response->getStatusCode();
468 $reason = $response->getReasonPhrase();
469 if (404 == $code) {
470 $container_object = $service->createContainer($container);
471 } else {
472 echo __('Authorisation failed (check your credentials)', 'updraftplus')." ($code:$reason)";
473 return;
474 }
475 } catch (Exception $e) {
476 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')';
477 return;
478 }
479
480 if (!is_a($container_object, 'OpenCloud\ObjectStore\Resource\Container') && !is_a($container_object, 'Container')) {
481 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($container_object).')';
482 return;
483 }
484
485 $try_file = md5(rand()).'.txt';
486
487 try {
488 $object = $container_object->uploadObject($try_file, 'UpdraftPlus test file', array('content-type' => 'text/plain'));
489 } catch (Exception $e) {
490 echo sprintf(__('%s error - we accessed the container, but failed to create a file within it', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')';
491 if (!empty($this->region)) echo ' '.sprintf(__('Region: %s', 'updraftplus'), $this->region);
492 return;
493 }
494
495 echo __('Success', 'updraftplus').": ".__('We accessed the container, and were able to create files within it.', 'updraftplus');
496 if (!empty($this->region)) echo ' '.sprintf(__('Region: %s', 'updraftplus'), $this->region);
497
498 try {
499 if (!empty($object)) {
500 // One OpenStack server we tested on did not delete unless we slept... some kind of race condition at their end
501 sleep(1);
502 $object->delete();
503 }
504 } catch (Exception $e) {
505 // Catch
506 }
507
508 }
509
510 public function config_print_middlesection() {
511 }
512
513 /**
514 * This outputs the html to the settings page for the Openstack settings.
515 */
516 public function config_print() {
517
518 $classes = $this->get_css_classes();
519
520 ?>
521 <tr class="<?php echo $classes; ?>">
522 <td></td>
523 <td>
524 <?php
525 if (!empty($this->img_url)) {
526 ?>
527 <img alt="<?php echo $this->long_desc; ?>" src="<?php echo UPDRAFTPLUS_URL.$this->img_url; ?>">
528 <?php
529 }
530 ?>
531 <p><em><?php printf(__('%s is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your site is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.', 'updraftplus'), $this->long_desc);?></em></p></td>
532 </tr>
533
534 <tr class="<?php echo $classes; ?>">
535 <th></th>
536 <td>
537 <?php
538 // Check requirements.
539 global $updraftplus_admin;
540 if (!function_exists('mb_substr')) {
541 $updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not included a required module (%s). Please contact your web hosting provider\'s support.', 'updraftplus'), 'mbstring').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s. Please do not file any support requests; there is no alternative.", 'updraftplus'), $this->desc, 'mbstring'), $this->method);
542 }
543 $updraftplus_admin->curl_check($this->long_desc, false, $this->method);
544 ?>
545 </td>
546 </tr>
547
548 <?php
549 $this->config_print_middlesection();
550
551 echo $this->get_test_button_html($this->desc);
552 }
553 }
554