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 / methods / openstack-base.php

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

597 lines 22.2 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 $storage = $this->get_openstack_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 = $storage->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 /**
107 * This function lists the files found in the configured storage location
108 *
109 * @param String $match a substring to require (tested via strpos() !== false)
110 *
111 * @return Array - each file is represented by an array with entries 'name' and (optional) 'size'
112 */
113 public function listfiles($match = 'backup_') {
114 $opts = $this->get_options();
115 $container = $opts['path'];
116 $path = $container;
117
118 if (empty($opts['user']) || (empty($opts['apikey']) && empty($opts['password']))) return new WP_Error('no_settings', __('No settings were found', 'updraftplus'));
119
120 try {
121 $storage = $this->get_openstack_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
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 // Get the container
127 try {
128 $this->container_object = $storage->getContainer($container);
129 } catch (Exception $e) {
130 return new WP_Error('no_access', sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')');
131 }
132
133 $results = array();
134 $marker = '';
135 $page_size = 1000;
136 try {
137 // http://php-opencloud.readthedocs.io/en/latest/services/object-store/objects.html#list-objects-in-a-container
138 while (null !== $marker) {
139
140 $params = array(
141 'prefix' => $match,
142 'limit' => $page_size,
143 'marker' => $marker
144 );
145
146 $objects = $this->container_object->objectList($params);
147
148 $total = $objects->count();
149
150 if (0 == $total) break;
151
152 $index = 0;
153
154 while (false !== ($file = $objects->offsetGet($index)) && !empty($file)) {
155 $index++;
156 try {
157 if ((is_object($file) && !empty($file->name))) {
158 $result = array('name' => $file->name);
159 // Rackspace returns the size of a manifested file properly; other OpenStack implementations may not
160 if (!empty($file->bytes)) {
161 $result['size'] = $file->bytes;
162 } else {
163 $size = $this->get_remote_size($file->name);
164 if (false !== $size && $size > 0) $result['size'] = $size;
165 }
166 $results[] = $result;
167 }
168 } catch (Exception $e) {
169 // Catch
170 }
171 $marker = (!empty($file->name) && $total >= $page_size) ? $file->name : null;
172 }
173
174 }
175 } catch (Exception $e) {
176 // Catch
177 }
178
179 return $results;
180 }
181
182 /**
183 * Called when all chunks have been uploaded, to allow any required finishing actions to be carried out
184 *
185 * @param String $file - the basename of the file being uploaded
186 *
187 * @return Boolean - success or failure state of any finishing actions
188 */
189 public function chunked_upload_finish($file) {
190
191 $chunk_path = 'chunk-do-not-delete-'.$file;
192 try {
193
194 $headers = array(
195 'Content-Length' => 0,
196 'X-Object-Manifest' => sprintf('%s/%s', $this->container, $chunk_path.'_')
197 );
198
199 $url = $this->container_object->getUrl($file);
200 $this->container_object->getClient()->put($url, $headers)->send();
201 return true;
202
203 } catch (Exception $e) {
204 global $updraftplus;
205 $updraftplus->log("Error when sending manifest (".get_class($e)."): ".$e->getMessage());
206 return false;
207 }
208 }
209
210 /**
211 * N.B. Since we use varying-size chunks, we must be careful as to what we do with $chunk_index
212 *
213 * @param String $file Full path for the file being uploaded
214 * @param Resource $fp File handle to read upload data from
215 * @param Integer $chunk_index Index of chunked upload
216 * @param Integer $upload_size Size of the upload, in bytes
217 * @param Integer $upload_start How many bytes into the file the upload process has got
218 * @param Integer $upload_end How many bytes into the file we will be after this chunk is uploaded
219 * @param Integer $total_file_size Total file size
220 *
221 * @return Boolean
222 */
223 public function chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $total_file_size) {
224
225 global $updraftplus;
226
227 $file_key = 'status_'.md5($file);
228 $file_status = $this->jobdata_get($file_key, null, 'openstack_'.$file_key);
229
230 $next_chunk_size = $upload_size;
231
232 $bytes_already_uploaded = 0;
233
234 $last_uploaded_chunk_index = 0;
235
236 // Once a chunk is uploaded, its status is set, allowing the sequence to be reconstructed
237 if (is_array($file_status) && isset($file_status['chunks']) && !empty($file_status['chunks'])) {
238 foreach ($file_status['chunks'] as $c_id => $c_status) {
239 if ($c_id > $last_uploaded_chunk_index) $last_uploaded_chunk_index = $c_id;
240 if ($chunk_index + 1 == $c_id) {
241 $next_chunk_size = $c_status['size'];
242 }
243 $bytes_already_uploaded += $c_status['size'];
244 }
245 } else {
246 $file_status = array('chunks' => array());
247 }
248
249 $this->jobdata_set($file_key, $file_status);
250
251 if ($upload_start < $bytes_already_uploaded) {
252 if ($next_chunk_size != $upload_size) {
253 $response = new stdClass;
254 $response->new_chunk_size = $upload_size;
255 $response->log = false;
256 return $response;
257 } else {
258 return 1;
259 }
260 }
261
262 // Shouldn't be able to happen
263 if ($chunk_index <= $last_uploaded_chunk_index) {
264 $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));
265 }
266
267 // Used to use $chunk_index here, before switching to variable chunk sizes
268 $upload_remotepath = 'chunk-do-not-delete-'.$file.'_'.sprintf("%016d", $chunk_index);
269
270 $remote_size = $this->get_remote_size($upload_remotepath);
271
272 // 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
273 // Didn't make the difference - instead we just check below for actual success even when Curl reports an error
274 // $chunk_object->headers = array('Expect' => '');
275
276 if ($remote_size >= $upload_size) {
277 $updraftplus->log($this->desc.": Chunk ($upload_start - $upload_end, $chunk_index): already uploaded");
278 } else {
279 $updraftplus->log($this->desc.": Chunk ($upload_start - $upload_end, $chunk_index): begin upload");
280 // Upload the chunk
281 try {
282 $data = fread($fp, $upload_size);
283 $time_start = microtime(true);
284 $this->container_object->uploadObject($upload_remotepath, $data);
285 $time_now = microtime(true);
286 $time_taken = $time_now - $time_start;
287 if ($next_chunk_size < 52428800 && $total_file_size > 0 && $upload_end + 1 < $total_file_size) {
288 $job_run_time = $time_now - $updraftplus->job_time_ms;
289 if ($time_taken < 10) {
290 $upload_rate = $upload_size / max($time_taken, 0.0001);
291 $upload_secs = min(floor($job_run_time), 10);
292 if ($job_run_time < 15) $upload_secs = max(6, $job_run_time*0.6);
293
294 // In megabytes
295 $memory_limit_mb = $updraftplus->memory_check_current();
296 $bytes_used = memory_get_usage();
297 $bytes_free = $memory_limit_mb * 1048576 - $bytes_used;
298
299 $new_chunk = max(min($upload_secs * $upload_rate * 0.9, 52428800, $bytes_free), 5242880);
300 $new_chunk = $new_chunk - ($new_chunk % 5242880);
301 $next_chunk_size = (int) $new_chunk;
302 $updraftplus->jobdata_set('openstack_chunk_size', $next_chunk_size);
303 }
304 }
305
306 } catch (Exception $e) {
307 $updraftplus->log($this->desc." chunk upload: error: ($file / $chunk_index) (".$e->getMessage().") (line: ".$e->getLine().', file: '.$e->getFile().')');
308 // 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.
309
310 $remote_size = $this->get_remote_size($upload_remotepath);
311
312 if ($remote_size >= $upload_size) {
313 $updraftplus->log("$file: Chunk now exists; ignoring error (presuming it was an apparently known curl bug)");
314 } else {
315 $updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload', 'updraftplus'), $this->desc), 'error');
316 return false;
317 }
318 }
319 }
320
321 $file_status['chunks'][$chunk_index]['size'] = $upload_size;
322
323 $this->jobdata_set($file_key, $file_status);
324
325 if ($next_chunk_size != $upload_size) {
326 $response = new stdClass;
327 $response->new_chunk_size = $next_chunk_size;
328 $response->log = true;
329 return $response;
330 }
331
332 return true;
333 }
334
335 public function delete($files, $data = false, $sizeinfo = array()) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
336
337 global $updraftplus;
338 if (is_string($files)) $files = array($files);
339
340 if (is_array($data)) {
341 $container_object = $data['object'];
342 $container = $data['container'];
343 $path = $data['orig_path'];
344 } else {
345 $opts = $this->get_options();
346 $container = $opts['path'];
347 $path = $container;
348 try {
349 $storage = $this->get_openstack_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
350 } catch (AuthenticationError $e) {
351 $updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
352 $updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
353 return false;
354 } catch (Exception $e) {
355 $updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().')');
356 $updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
357 return false;
358 }
359 // Get the container
360 try {
361 $container_object = $storage->getContainer($container);
362 } catch (Exception $e) {
363 $updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().')');
364 $updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
365 return false;
366 }
367
368 }
369
370 $ret = true;
371 foreach ($files as $file) {
372
373 $updraftplus->log($this->desc.": Delete remote: container=$container, path=$file");
374
375 // We need to search for chunks
376 $chunk_path = "chunk-do-not-delete-".$file;
377
378 try {
379 $objects = $container_object->objectList(array('prefix' => $chunk_path));
380 $index = 0;
381 while (false !== ($chunk = $objects->offsetGet($index)) && !empty($chunk)) {
382 try {
383 $name = $chunk->name;
384 $container_object->dataObject()->setName($name)->delete();
385 $updraftplus->log($this->desc.': Chunk deleted: '.$name);
386 } catch (Exception $e) {
387 $updraftplus->log($this->desc." chunk delete failed: $name: ".$e->getMessage());
388 }
389 $index++;
390 }
391 } catch (Exception $e) {
392 $updraftplus->log($this->desc.' chunk delete failed: '.$e->getMessage());
393 }
394
395 // Finally, delete the object itself
396 try {
397 $container_object->dataObject()->setName($file)->delete();
398 $updraftplus->log($this->desc.': Deleted: '.$file);
399 } catch (Exception $e) {
400 $updraftplus->log($this->desc.' delete failed: '.$e->getMessage());
401 $ret = false;
402 }
403 }
404 return $ret;
405 }
406
407 public function download($file) {
408
409 global $updraftplus;
410
411 $opts = $this->get_options();
412
413 try {
414 $storage = $this->get_openstack_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
415 } catch (AuthenticationError $e) {
416 $updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
417 $updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
418 return false;
419 } catch (Exception $e) {
420 $updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().')');
421 $updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
422 return false;
423 }
424
425 $container = untrailingslashit($opts['path']);
426 $updraftplus->log($this->desc." download: ".$this->method."://$container/$file");
427
428 // Get the container
429 try {
430 $this->container_object = $storage->getContainer($container);
431 } catch (Exception $e) {
432 $updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().')');
433 $updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
434 return false;
435 }
436
437 // Get information about the object within the container
438 $remote_size = $this->get_remote_size($file);
439 if (false === $remote_size) {
440 $updraftplus->log('Could not access '.$this->desc.' object');
441 $updraftplus->log(sprintf(__('The %s object was not found', 'updraftplus'), $this->desc), 'error');
442 return false;
443 }
444
445 return (!is_bool($remote_size)) ? $updraftplus->chunked_download($file, $this, $remote_size, true, $this->container_object) : false;
446
447 }
448
449 public function chunked_download($file, $headers, $container_object) {
450 try {
451 $dl = $container_object->getObject($file, $headers);
452 } catch (Exception $e) {
453 global $updraftplus;
454 $updraftplus->log("$file: Failed to download (".$e->getMessage().")");
455 $updraftplus->log("$file: ".sprintf(__("%s Error", 'updraftplus'), $this->desc).": ".__('Error downloading remote file: Failed to download', 'updraftplus').' ('.$e->getMessage().")", 'error');
456 return false;
457 }
458 return $dl->getContent();
459 }
460
461 public function credentials_test_go($opts, $path, $useservercerts, $disableverify) {
462
463 if (preg_match("#^([^/]+)/(.*)$#", $path, $bmatches)) {
464 $container = $bmatches[1];
465 $path = $bmatches[2];
466 } else {
467 $container = $path;
468 $path = '';
469 }
470
471 if (empty($container)) {
472 _e('Failure: No container details were given.', 'updraftplus');
473 return;
474 }
475
476 try {
477 $storage = $this->get_openstack_service($opts, $useservercerts, $disableverify);
478 // @codingStandardsIgnoreLine
479 } catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
480 $response = $e->getResponse();
481 $code = $response->getStatusCode();
482 $reason = $response->getReasonPhrase();
483 if (401 == $code && 'Unauthorized' == $reason) {
484 echo __('Authorisation failed (check your credentials)', 'updraftplus');
485 } else {
486 echo __('Authorisation failed (check your credentials)', 'updraftplus')." ($code:$reason)";
487 }
488 return;
489 } catch (AuthenticationError $e) {
490 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')';
491 return;
492 } catch (Exception $e) {
493 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')';
494 return;
495 }
496
497 try {
498 $container_object = $storage->getContainer($container);
499 // @codingStandardsIgnoreLine
500 } catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
501 $response = $e->getResponse();
502 $code = $response->getStatusCode();
503 $reason = $response->getReasonPhrase();
504 if (404 == $code) {
505 $container_object = $storage->createContainer($container);
506 } else {
507 echo __('Authorisation failed (check your credentials)', 'updraftplus')." ($code:$reason)";
508 return;
509 }
510 } catch (Exception $e) {
511 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')';
512 return;
513 }
514
515 if (!is_a($container_object, 'OpenCloud\ObjectStore\Resource\Container') && !is_a($container_object, 'Container')) {
516 echo sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($container_object).')';
517 return;
518 }
519
520 $try_file = md5(rand()).'.txt';
521
522 try {
523 $object = $container_object->uploadObject($try_file, 'UpdraftPlus test file', array('content-type' => 'text/plain'));
524 } catch (Exception $e) {
525 echo sprintf(__('%s error - we accessed the container, but failed to create a file within it', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')';
526 if (!empty($this->region)) echo ' '.sprintf(__('Region: %s', 'updraftplus'), $this->region);
527 return;
528 }
529
530 echo __('Success', 'updraftplus').": ".__('We accessed the container, and were able to create files within it.', 'updraftplus');
531 if (!empty($this->region)) echo ' '.sprintf(__('Region: %s', 'updraftplus'), $this->region);
532
533 try {
534 if (!empty($object)) {
535 // One OpenStack server we tested on did not delete unless we slept... some kind of race condition at their end
536 sleep(1);
537 $object->delete();
538 }
539 } catch (Exception $e) {
540 // Catch
541 }
542
543 }
544
545 /**
546 * Get the pre configuration template
547 *
548 * @return String - the template
549 */
550 public function get_pre_configuration_template() {
551
552 global $updraftplus_admin;
553
554 $classes = $this->get_css_classes(false);
555
556 ?>
557 <tr class="<?php echo $classes . ' ' . $this->method . '_pre_config_container';?>">
558 <td colspan="2">
559 <?php
560 if (!empty($this->img_url)) {
561 ?>
562 <img alt="<?php echo $this->long_desc; ?>" src="<?php echo UPDRAFTPLUS_URL.$this->img_url; ?>">
563 <?php
564 }
565 ?>
566 <br>
567 <?php
568 // Check requirements.
569 global $updraftplus_admin;
570 if (!function_exists('mb_substr')) {
571 $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);
572 }
573 $updraftplus_admin->curl_check($this->long_desc, false, $this->method);
574 echo '<br>';
575 $this->get_pre_configuration_middlesection_template();
576 ?>
577 </td>
578 </tr>
579
580 <?php
581 }
582
583 /**
584 * Get the configuration template
585 *
586 * @return String - the template, ready for substitutions to be carried out
587 */
588 public function get_configuration_template() {
589 ob_start();
590 $classes = $this->get_css_classes();
591 $template_str = ob_get_clean();
592 $template_str .= $this->get_configuration_middlesection_template();
593 $template_str .= $this->get_test_button_html($this->desc);
594 return $template_str;
595 }
596 }
597