PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.4.2
UpdraftPlus: WP Backup & Migration Plugin v1.4.2
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-gdocs.php

class-gdocs.php in UpdraftPlus: WP Backup & Migration Plugin 1.4.2, at includes/class-gdocs.php

631 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Originally contained: GDocs class
4 // Contains: UpdraftPlus_GDocs class (new methods added - could not extend, as too much was private)
5
6 // The following copyright notice is reproduced exactly as found in the "Backup" plugin (http://wordpress.org/extend/plugins/backup)
7 // It applies to the code apart from the methods we added (get_content_link, download_data)
8
9 /*
10 Copyright 2012 Sorin Iclanzan (email : sorin@hel.io)
11
12 This file is part of Backup.
13
14 Backup is free software: you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 Backup is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with Backup. If not, see http://www.gnu.org/licenses/gpl.html.
26 */
27
28 /**
29 * Google Docs class
30 *
31 * Implements communication with Google Docs via the Google Documents List v3 API.
32 *
33 * Currently uploading, resuming and deleting resources is implemented as well as retrieving quotas.
34 *
35 * @uses WP_Error for storing error messages.
36 */
37 class UpdraftPlus_GDocs {
38
39 /**
40 * Stores the API version.
41 *
42 * @var string
43 * @access private
44 */
45 private $gdata_version;
46
47
48 /**
49 * Stores the base URL for the API requests.
50 *
51 * @var string
52 * @access private
53 */
54 private $base_url;
55
56 /**
57 * Stores the URL to the metadata feed.
58 * @var string
59 */
60 private $metadata_url;
61
62 /**
63 * Stores the token needed to access the API.
64 * @var string
65 * @access private
66 */
67 private $token;
68
69 /**
70 * Stores feeds to avoid requesting them again for successive use.
71 *
72 * @var array
73 * @access private
74 */
75 private $cache = array();
76
77 /**
78 * Files are uploadded in chunks of this size in bytes.
79 *
80 * @var integer
81 * @access private
82 */
83 private $chunk_size;
84
85 /**
86 * Stores whether or not to verify host SSL certificate.
87 *
88 * @var boolean
89 * @access private
90 */
91 private $ssl_verify;
92
93 /**
94 * Stores the number of seconds to wait for a response before timing out.
95 *
96 * @var integer
97 * @access private
98 */
99 private $request_timeout;
100
101 /**
102 * Stores the MIME type of the file that is uploading
103 *
104 * @var string
105 * @access private
106 */
107 private $upload_file_type;
108
109 /**
110 * Stores info about the file being uploaded.
111 *
112 * @var array
113 * @access private
114 */
115 private $file;
116
117 /**
118 * Stores the number of seconds the upload process is allowed to run
119 *
120 * @var integer
121 * @access private
122 */
123 private $time_limit;
124
125 /**
126 * Stores a timer for upload processes
127 *
128 * @var array
129 */
130 private $timer;
131
132 /**
133 * Constructor - Sets the access token.
134 *
135 * @param string $token Access token
136 */
137 function __construct( $token ) {
138 $this->token = $token;
139 $this->gdata_version = '3.0';
140 $this->base_url = 'https://docs.google.com/feeds/default/private/full/';
141 $this->metadata_url = 'https://docs.google.com/feeds/metadata/default';
142 $this->chunk_size = 524288; // 512 KiB
143 $this->max_resume_attempts = 5;
144 $this->request_timeout = 5;
145 $this->ssl_verify = true;
146 $this->timer = array(
147 'start' => 0,
148 'stop' => 0,
149 'delta' => 0,
150 'cycle' => 0
151 );
152 $this->time_limit = @ini_get( 'max_execution_time' );
153 if ( ! $this->time_limit && '0' !== $this->time_limit )
154 $this->time_limit = 30; // default php max exec time
155 }
156
157 /**
158 * Sets an option.
159 *
160 * @access public
161 * @param string $option The option to set.
162 * @param mixed $value The value to set the option to.
163 */
164 public function set_option( $option, $value ) {
165 switch ( $option ) {
166 case 'chunk_size':
167 if ( floatval($value) >= 0.5 ) {
168 $this->chunk_size = floatval($value) * 1024 * 1024; // Transform from MiB to bytes
169 return true;
170 }
171 break;
172 case 'ssl_verify':
173 $this->ssl_verify = ( bool ) $value;
174 return true;
175 case 'request_timeout':
176 if ( intval( $value ) > 0 ) {
177 $this->request_timeout = intval( $value );
178 return true;
179 }
180 break;
181 case 'max_resume_attempts':
182 $this->max_resume_attempts = intval($value);
183 return true;
184 }
185 return false;
186 }
187
188 /**
189 * Gets an option.
190 *
191 * @access public
192 * @param string $option The option to get.
193 */
194 public function get_option( $option ) {
195 switch ( $option ) {
196 case 'chunk_size':
197 return $this->chunk_size;
198 case 'ssl_verify':
199 return $this->ssl_verify;
200 case 'request_timeout':
201 return $this->request_timeout;
202 case 'max_resume_attempts':
203 return $this->max_resume_attempts;
204 }
205 return false;
206 }
207
208 /**
209 * This function makes all the requests to the API.
210 *
211 * @uses wp_remote_request
212 * @access private
213 * @param string $url The URL where the request is sent.
214 * @param string $method The HTTP request method, defaults to 'GET'.
215 * @param array $headers Headers to be sent.
216 * @param string $body The body of the request.
217 * @return mixed Returns an array containing the response on success or an instance of WP_Error on failure.
218 */
219 private function request( $url, $method = 'GET', $headers = array(), $body = NULL ) {
220 $args = array(
221 'method' => $method,
222 'timeout' => $this->request_timeout,
223 'httpversion' => '1.1',
224 'redirection' => 0,
225 'sslverify' => $this->ssl_verify,
226 'headers' => array(
227 'Authorization' => 'Bearer ' . $this->token,
228 'GData-Version' => $this->gdata_version
229 )
230 );
231 if ( ! empty( $headers ) )
232 $args['headers'] = array_merge( $args['headers'], $headers );
233 if ( ! empty( $body ) )
234 $args['body'] = $body;
235
236 return wp_remote_request( $url, $args );
237 }
238
239 /**
240 * Returns the feed from a URL.
241 *
242 * @access public
243 * @param string $url The feed URL.
244 * @return mixed Returns the feed as an instance of SimpleXMLElement on success or an instance of WP_Error on failure.
245 */
246 public function get_feed( $url ) {
247 if ( ! isset( $this->cache[$url] ) ) {
248 $result = $this->cache_feed( $url );
249 if ( is_wp_error( $result ) )
250 return $result;
251 }
252
253 return $this->cache[$url];
254 }
255
256 /**
257 * Requests a feed and adds it to cache.
258 *
259 * @access private
260 * @param string $url The feed URL.
261 * @return mixed Returns TRUE on success or an instance of WP_Error on failure.
262 */
263 private function cache_feed( $url ) {
264 $result = $this->request( $url );
265
266 if ( is_wp_error( $result ) )
267 return $result;
268
269 if ( $result['response']['code'] == '200' ) {
270 $feed = @simplexml_load_string( $result['body'] );
271 if ( $feed === false )
272 return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
273
274 $this->cache[$url] = $feed;
275 return true;
276 }
277 return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'. Response body: " . $result['body'] );
278
279 }
280
281 /**
282 * Deletes a resource from Google Docs.
283 *
284 * @access public
285 * @param string $id Gdata Id of the resource to be deleted.
286 * @return mixed Returns TRUE on success, an instance of WP_Error on failure.
287 */
288 public function delete_resource( $id ) {
289 $headers = array( 'If-Match' => '*' );
290
291 $result = $this->request( $this->base_url . $id . '?delete=true', 'DELETE', $headers );
292 if ( is_wp_error( $result ) )
293 return $result;
294
295 if ( $result['response']['code'] == '200' )
296 return true;
297 return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to delete resource '" . $id . "'. The resource might not have been deleted." );
298 }
299
300 /**
301 * Get the resumable-create-media link needed to upload files.
302 *
303 * @access private
304 * @param string $parent The Id of the folder where the upload is to be made. Default is empty string.
305 * @return mixed Returns a link on success, instance of WP_Error on failure.
306 */
307 private function get_resumable_create_media_link( $parent = '' ) {
308 $url = $this->base_url;
309 if ( $parent )
310 $url .= $parent;
311
312 $feed = $this->get_feed( $url );
313
314 if ( is_wp_error( $feed ) )
315 return $feed;
316
317 foreach ( $feed->link as $link )
318 if ( $link['rel'] == 'http://schemas.google.com/g/2005#resumable-create-media' )
319 return ( string ) $link['href'];
320 return new WP_Error( 'not_found', "The 'resumable_create_media_link' was not found in feed." );
321 }
322
323 /**
324 * Get used quota in bytes.
325 *
326 * @access public
327 * @return mixed Returns the number of bytes used in Google Docs on success or an instance of WP_Error on failure.
328 */
329 public function get_quota_used() {
330 $feed = $this->get_feed( $this->metadata_url );
331 if ( is_wp_error( $feed ) )
332 return $feed;
333 return ( string ) $feed->children( "http://schemas.google.com/g/2005" )->quotaBytesUsed;
334 }
335
336 /**
337 * Get total quota in bytes.
338 *
339 * @access public
340 * @return string|WP_Error Returns the total quota in bytes in Google Docs on success or an instance of WP_Error on failure.
341 */
342 public function get_quota_total() {
343 $feed = $this->get_feed( $this->metadata_url );
344 if ( is_wp_error( $feed ) )
345 return $feed;
346 return ( string ) $feed->children( "http://schemas.google.com/g/2005" )->quotaBytesTotal;
347 }
348
349 /**
350 * Function to prepare a file to be uploaded to Google Docs.
351 *
352 * The function requests a URI for uploading and prepends a new element in the resume_list array.
353 *
354 * @uses wp_check_filetype
355 * @access public
356 *
357 * @param string $file Path to the file that is to be uploaded.
358 * @param string $title Title to be given to the file.
359 * @param string $parent ID of the folder in which to upload the file.
360 * @param string $type MIME type of the file to be uploaded. The function tries to identify the type if it is omitted.
361 * @return mixed Returns the URI where to upload on success, an instance of WP_Error on failure.
362 */
363 public function prepare_upload( $file, $title, $parent = '', $type = '' ) {
364 if ( ! @is_readable( $file ) )
365 return new WP_Error( 'not_file', "The path '" . $file . "' does not point to a readable file." );
366
367 // If a mime type wasn't passed try to guess it from the extension based on the WordPress allowed mime types
368 if ( empty( $type ) ) {
369 $check = wp_check_filetype( $file );
370 $this->upload_file_type = $type = $check['type'];
371 }
372
373 $size = filesize( $file );
374
375 $body = '<?xml version=\'1.0\' encoding=\'UTF-8\'?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007"><category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/docs/2007#file"/><title>' . $title . '</title></entry>';
376
377 $headers = array(
378 'Content-Type' => 'application/atom+xml',
379 'X-Upload-Content-Type' => $type,
380 'X-Upload-Content-Length' => (string) $size
381 );
382
383 $url = $this->get_resumable_create_media_link( $parent );
384
385 if ( is_wp_error( $url ) )
386 return $url;
387
388 $url .= '?convert=false'; // needed to upload a file
389
390 $result = $this->request( $url, 'POST', $headers, $body );
391
392 if ( is_wp_error( $result ) )
393 return $result;
394
395 if ( $result['response']['code'] != '200' )
396 return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'." );
397
398 $this->file = array(
399 'path' => $file,
400 'size' => $size,
401 'location' => $result['headers']['location'],
402 'pointer' => 0
403 );
404
405 // Open file for reading.
406 if ( !$this->file['handle'] = fopen( $file, "rb" ) )
407 return new WP_Error( 'open_error', "Could not open file '" . $file . "' for reading." );
408
409 // Start timer
410 $this->timer['start'] = microtime( true );
411
412 return $result['headers']['location'];
413 }
414
415
416 /**
417 * Resume an upload.
418 *
419 * @access public
420 * @param string $file Path to the file which needs to be uploaded
421 * @param string $location URI where to upload the file
422 * @return mixed Returns the next location URI on success, an instance of WP_Error on failure.
423 */
424 public function resume_upload( $file, $location ) {
425
426 if ( ! @is_readable( $file ) )
427 return new WP_Error( 'not_file', "The path '" . $this->resume_list[$id]['path'] . "' does not point to a readable file. Upload has been canceled." );
428
429 $size = filesize( $file );
430
431 $headers = array( 'Content-Range' => 'bytes */' . $size );
432 $result = $this->request( $location, 'PUT', $headers );
433 if( is_wp_error( $result ) )
434 return $result;
435
436 if ( '308' != $result['response']['code'] ) {
437 if ( '201' == $result['response']['code'] ) {
438 $feed = @simplexml_load_string( $result['body'] );
439 if ( $feed === false )
440 return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
441 $this->file['id'] = substr( ( string ) $feed->children( "http://schemas.google.com/g/2005" )->resourceId, 5 );
442 return true;
443 }
444 return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to resume the upload of file '" . $file . "'." );
445 }
446 if( isset( $result['headers']['location'] ) )
447 $location = $result['headers']['location'];
448 $pointer = $this->pointer( $result['headers']['range'] );
449
450 $this->file = array(
451 'path' => $file,
452 'size' => $size,
453 'location' => $location,
454 'pointer' => $pointer
455 );
456
457 // Open file for reading.
458 if ( !$this->file['handle'] = fopen( $file, "rb" ) )
459 return new WP_Error( 'open_error', "Could not open file '" . $file . "' for reading." );
460
461 // Start timer
462 $this->timer['start'] = microtime( true );
463
464 return $location;
465 }
466
467 /**
468 * Work out where the file pointer should be from the range header.
469 *
470 * @access private
471 * @param string $range The range HTTP response header.
472 * @return integer Returns the number of bytes that have been uploaded.
473 */
474 private function pointer( $range ) {
475 return intval(substr( $range, strpos( $range, '-' ) + 1 )) + 1;
476 }
477
478 /**
479 * Uploads a chunk of the file being uploaded.
480 *
481 * @access public
482 * @return mixed Returns TRUE if the chunk was uploaded successfully;
483 * returns Google Docs resource ID if the file upload finished;
484 * returns an instance of WP_Error on failure.
485 */
486 public function upload_chunk() {
487 if ( !isset( $this->file['handle'] ) )
488 return new WP_Error( "no_upload", "There is no file being uploaded." );
489
490 $cycle_start = microtime( true );
491 fseek( $this->file['handle'], $this->file['pointer'] );
492 $chunk = @fread( $this->file['handle'], $this->chunk_size );
493 if ( false === $chunk ) {
494 $is_file = (is_file($this->file['path'])) ? 1 : 0;
495 $is_readable = (is_readable($this->file['path'])) ? 1 : 0;
496 return new WP_Error( 'read_error', "Failed to read from file (path: ".$this->file['path'].", size: ".$this->file['size'].", pointer: ".$this->file['pointer'].", is_file: $is_file, is_readable: $is_readable)");
497 }
498
499 $chunk_size = strlen( $chunk );
500 $bytes = 'bytes ' . (string)$this->file['pointer'] . '-' . (string)($this->file['pointer'] + $chunk_size - 1) . '/' . (string)$this->file['size'];
501
502 $headers = array( 'Content-Range' => $bytes );
503
504 $result = $this->request( $this->file['location'], 'PUT', $headers, $chunk );
505
506 if ( !is_wp_error( $result ) )
507 if ( '308' == $result['response']['code'] ) {
508 if ( isset( $result['headers']['range'] ) )
509 $this->file['pointer'] = $this->pointer( $result['headers']['range'] );
510 else
511 $this->file['pointer'] += $chunk_size;
512
513 if ( isset( $result['headers']['location'] ) )
514 $this->file['location'] = $result['headers']['location'];
515
516 if ( $this->timer['cycle'] )
517 $this->timer['cycle'] = ( microtime( true ) - $cycle_start + $this->timer['cycle'] ) / 2;
518 else
519 $this->timer['cycle'] = microtime(true) - $cycle_start;
520
521 return $this->file['location'];
522 }
523 elseif ( '201' == $result['response']['code'] ) {
524 fclose( $this->file['handle'] );
525
526 // Stop timer
527 $this->timer['stop'] = microtime(true);
528 $this->timer['delta'] = $this->timer['stop'] - $this->timer['start'];
529
530 if ( $this->timer['cycle'] )
531 $this->timer['cycle'] = ( microtime( true ) - $cycle_start + $this->timer['cycle'] ) / 2;
532 else
533 $this->timer['cycle'] = microtime(true) - $cycle_start;
534
535 $this->file['pointer'] = $this->file['size'];
536
537 $feed = @simplexml_load_string( $result['body'] );
538 if ( $feed === false )
539 return new WP_Error( 'invalid_data', "Could not create SimpleXMLElement from '" . $result['body'] . "'." );
540 $this->file['id'] = substr( ( string ) $feed->children( "http://schemas.google.com/g/2005" )->resourceId, 5 );
541 return true;
542 }
543
544 // If we got to this point it means the upload wasn't successful.
545 fclose( $this->file['handle'] );
546 if ( is_wp_error( $result ) )
547 return $result;
548 return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to upload a file chunk." );
549 }
550
551 /**
552 * Get the resource ID of the most recent uploaded file.
553 *
554 * @access public
555 * @return string The ID of the uploaded file or an empty string.
556 */
557 public function get_file_id() {
558 if ( isset( $this->file['id'] ) )
559 return $this->file['id'];
560 return '';
561 }
562
563 /**
564 * Get the upload speed recorded on the last upload performed.
565 *
566 * @access public
567 * @return integer Returns the upload speed in bytes/second or 0.
568 */
569 public function get_upload_speed() {
570 if ( $this->timer['cycle'] > 0 )
571 if ( $this->file['size'] < $this->chunk_size )
572 return $this->file['size'] / $this->timer['cycle'];
573 else
574 return $this->chunk_size / $this->timer['cycle'];
575 return 0;
576 }
577
578 /**
579 * Get the percentage of the file uploaded.
580 *
581 * @return float Returns a percentage on success, 0 on failure.
582 */
583 public function get_upload_percentage() {
584 if ( isset( $this->file['path'] ) )
585 return $this->file['pointer'] * 100 / $this->file['size'];
586 return 0;
587 }
588
589 /**
590 * Returns the time taken for an upload to complete.
591 *
592 * @access public
593 * @return float Returns the number of seconds the last upload took to complete, 0 if there has been no completed upload.
594 */
595 public function time_taken() {
596 return $this->timer['delta'];
597 }
598
599 public function get_content_link( $id, $title ) {
600
601 $feed = $this->get_feed($this->base_url . $id);
602
603 if ( is_wp_error( $feed ) )
604 return $feed;
605
606 if ( $feed->title != $title )
607 return new WP_Error( 'bad_response', "Unexpected response");
608
609 $att = $feed->content->attributes();
610 return $att['src'];
611
612 }
613
614 public function download_data( $link, $saveas ) {
615
616 $result = $this->request( $link );
617
618 if ( is_wp_error( $result ) )
619 return $result;
620
621 if ( $result['response']['code'] != '200' )
622 return new WP_Error( 'bad_response', "Received response code '" . $result['response']['code'] . " " . $result['response']['message'] . "' while trying to get '" . $url . "'." );
623
624 file_put_contents($saveas, $result['body']);
625
626 }
627
628
629
630 }
631