PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.4 3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / Destination / Vendors / GoogleDrive / Client / Client.php
backup / src / JetBackup / Destination / Vendors / GoogleDrive / Client Last commit date
.htaccess 1 year ago Client.php 1 year ago ClientException.php 1 year ago File.php 1 year ago ListFiles.php 1 year ago index.html 1 year ago web.config 1 year ago
Client.php
576 lines
1 <?php
2 /*
3 *
4 * JetBackup @ package
5 * Created By Idan Ben-Ezra
6 *
7 * Copyrights @ JetApps
8 * https://www.jetapps.com
9 *
10 **/
11 namespace JetBackup\Destination\Vendors\GoogleDrive\Client;
12
13 use JetBackup\Exception\HttpRequestException;
14 use JetBackup\Exception\IOException;
15 use JetBackup\Web\File\FileChunk;
16 use JetBackup\Web\File\FileChunkIterator;
17 use JetBackup\Web\File\FileDownload;
18 use JetBackup\Web\File\FileException;
19 use JetBackup\Web\File\FileStream;
20 use JetBackup\Web\JetHttp;
21 use JetBackup\Web\JetHttpResponse;
22 use stdClass;
23
24 class Client {
25
26 const ROOT_ID = 'root';
27 const MIMITYPE_DIR = 'application/vnd.google-apps.folder';
28
29 const CLIENT_ID = '72463712983-r6mekushs9d8ugj4c5f29a84u5g4li4c.apps.googleusercontent.com';
30 const CLIENT_SECRET = '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';
31 const REDIRECT_URI = 'https://auth.jetbackup.com/google/';
32
33 const API_URL = 'https://www.googleapis.com';
34 const OAUTH_URL = 'https://oauth2.googleapis.com';
35
36 const TOKEN_URL = self::OAUTH_URL . '/token';
37 const FILES_API_URL = self::API_URL . '/drive/v3/files';
38 const UPLOAD_API_URL = self::API_URL . '/upload/drive/v3/files';
39
40 const RESUME_UPLOAD_CODE = 308;
41 const UPLOAD_CHUNK_SIZE = 52428800; // 50MB - Chunk size must be in mib and not just any size (dividable by 1024).
42
43 private array $_accessToken=[];
44 private string $_client_id='';
45 private string $_client_secret='';
46 private JetHttp $_http;
47 private ?string $_refresh_token = null;
48
49 /**
50 *
51 */
52 public function __construct() {
53 $this->_http = JetHttp::request();
54 }
55
56 /**
57 * @param array $accessToken
58 *
59 * @return void
60 */
61 public function setAccessToken(array $accessToken):void {
62 $this->_accessToken = $accessToken;
63 }
64
65 /**
66 * @return array
67 */
68 public function getAccessToken():array {
69 return $this->_accessToken;
70 }
71
72 /**
73 * @return string
74 */
75 public function getClientId():string { return $this->_client_id ?: self::CLIENT_ID; }
76
77 /**
78 * @param string $id
79 *
80 * @return void
81 */
82 public function setClientId(string $id):void { $this->_client_id = $id; }
83
84 /**
85 * @return string
86 */
87 public function getClientSecret():string { return $this->_client_secret ?: self::CLIENT_SECRET; }
88
89 /**
90 * @param string $secret
91 *
92 * @return void
93 */
94 public function setClientSecret(string $secret):void { $this->_client_secret = $secret; }
95
96 public function setRefreshToken(?string $token):void { $this->_refresh_token = $token; }
97 public function getRefreshToken():?string {return $this->_refresh_token;}
98
99 /**
100 * @return bool
101 */
102 public function isAccessTokenExpired():bool {
103
104 $accessToken = $this->getAccessToken();
105 if(!$accessToken) return true;
106
107 $created = 0;
108 if (isset($accessToken['created'])) {
109 $created = $accessToken['created'];
110 } elseif (isset($accessToken['id_token'])) {
111 // check the ID token for "iat"
112 // signature verification is not required here, as we are just
113 // using this for convenience to save a round trip request
114 // to the Google API server
115 $idToken = $accessToken['id_token'];
116 if (substr_count($idToken, '.') == 2) {
117 $parts = explode('.', $idToken);
118 $payload = json_decode(base64_decode($parts[1]), true);
119 if ($payload && isset($payload['iat'])) {
120 $created = $payload['iat'];
121 }
122 }
123 }
124 if (!isset($accessToken['expires_in'])) {
125 // if the token does not have an "expires_in", then it's considered expired
126 return true;
127 }
128
129 // If the token is set to expire in the next 30 seconds.
130 return ($created + ($accessToken['expires_in'] - 30)) < time();
131
132 }
133
134 /**
135 * @return void
136 * @throws ClientException
137 */
138 public function fetchAccessTokenWithRefreshToken():void {
139
140 try {
141 $this->_http->reset();
142 $response = $this->_http
143 ->setMethod(JetHttp::METHOD_POST)
144 ->setReturnTransfer()
145 ->addHeader('Content-Type', 'application/x-www-form-urlencoded')
146 ->setBody(http_build_query([
147 'client_id' => $this->getClientId(),
148 'client_secret' => $this->getClientSecret(),
149 'refresh_token' => $this->getRefreshToken(),
150 'grant_type' => 'refresh_token',
151 ]))
152 ->exec(self::TOKEN_URL);
153
154 self::_checkResponse($response);
155
156 } catch(HttpRequestException $e) {
157 throw new ClientException($e->getMessage(), $e->getCode());
158 }
159
160 $body = json_decode($response->getBody());
161 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
162
163 $accessToken = $this->getAccessToken();
164 $accessToken['access_token'] = $body->access_token;
165 //$accessToken['created'] = time();
166 $this->setAccessToken($accessToken);
167 }
168
169 /**
170 * @param string $authCode
171 *
172 * @return array
173 * @throws ClientException
174 */
175 public function fetchAccessTokenWithAuthCode(string $authCode):array {
176
177 try {
178 $this->_http->reset();
179 $response = $this->_http
180 ->setMethod(JetHttp::METHOD_POST)
181 ->setReturnTransfer()
182 ->addHeader('Content-Type', 'application/x-www-form-urlencoded')
183 ->setBody(http_build_query([
184 'code' => $authCode,
185 'client_id' => $this->getClientId(),
186 'client_secret' => $this->getClientSecret(),
187 'redirect_uri' => self::REDIRECT_URI,
188 'grant_type' => 'authorization_code',
189 ]))
190 ->exec(self::TOKEN_URL);
191
192 self::_checkResponse($response);
193
194 } catch(HttpRequestException $e) {
195 throw new ClientException($e->getMessage(), $e->getCode());
196 }
197
198 $body = json_decode($response->getBody());
199 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
200
201 return (array) $body;
202 }
203
204 public function getUploadOffset(string $upload_url):int {
205
206 try {
207 $this->_http->reset();
208 $response = $this->_http
209 ->addOption(CURLOPT_PUT, 1)
210 ->setReturnTransfer()
211 ->setBody('')
212 ->addHeader('Content-Range', 'bytes */*') // Request the current upload offset
213 ->addHeader('Content-Length', 0) // Add Content-Length: 0 to avoid 411 error
214 ->exec($upload_url);
215
216 if(
217 $response->getHeaders()->getCode() != 308 ||
218 !($range = $response->getHeaders()->getHeader('range'))
219 ) return 0;
220
221 list(,$end) = explode('-', $range);
222
223 return ((int) $end) + 1;
224 } catch(HttpRequestException $e) {
225 throw new ClientException($e->getMessage(), $e->getCode());
226 }
227 }
228
229 public function getUploadURL(FileStream $stream, string $destination, string $parent):string {
230
231 try {
232
233 $this->_http->reset();
234 $response = $this->_http
235 ->setMethod(JetHttp::METHOD_POST)
236 ->setReturnTransfer()
237 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
238 ->addHeader('Content-Type', 'application/json; charset=UTF-8')
239 ->addHeader('X-Upload-Content-Type', $stream->getMimeType())
240 ->addHeader('X-Upload-Content-Length', $stream->getSize())
241 ->setBody(json_encode([
242 'name' => basename($destination),
243 'parents' => [$parent],
244 ]))
245 ->exec(self::UPLOAD_API_URL . '?uploadType=resumable&fields=id');
246
247 self::_checkResponse($response);
248
249 $body = json_decode($response->getBody());
250 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
251
252 return $response->getHeaders()->getHeader('location');
253
254 } catch(HttpRequestException $e) {
255 throw new ClientException($e->getMessage(), $e->getCode());
256 }
257 }
258
259 /**
260 * @throws ClientException
261 * @throws HttpRequestException
262 */
263 public function uploadChunk(FileChunk $chunk, $uploadURL):object {
264
265 $this->_http->reset();
266
267 $response = $this->_http
268 ->setReturnTransfer()
269 ->addHeader('Content-Length', $chunk->getSize())
270 ->addHeader('Content-Range', sprintf("bytes %d-%d/%d", $chunk->getFile()->tell(), $chunk->getFile()->tell() + $chunk->getSize() - 1, $chunk->getFile()->getSize()))
271 ->addHeader('expect', '')
272 ->uploadChunk($uploadURL, $chunk);
273
274 if($response->getHeaders()->getCode() != self::RESUME_UPLOAD_CODE) self::_checkResponse($response);
275
276 return $response;
277 }
278
279 /**
280 * @param string $source
281 * @param string $destination
282 * @param string $parent
283 *
284 * @return stdClass
285 * @throws ClientException
286 */
287 public function upload(string $source, string $destination, string $parent): stdClass {
288
289 try {
290
291 $stream = new FileStream($source);
292
293 if($stream->getSize() <= self::UPLOAD_CHUNK_SIZE) {
294 // Upload small file
295 $details = json_encode([
296 'name' => basename($destination),
297 'parents' => [$parent],
298 ]);
299
300 $this->_http->reset();
301 $response = $this->_http
302 ->setReturnTransfer()
303 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
304 ->uploadString(self::UPLOAD_API_URL . '?uploadType=multipart&fields=id', $stream, $details);
305
306 } else {
307 // Upload large file
308 $uploadURL = $this->getUploadURL($stream, $destination, $parent);
309
310 $iterator = new FileChunkIterator($stream, self::UPLOAD_CHUNK_SIZE);
311 if(!$iterator->hasNext()) throw new ClientException("Unable to get file chunks");
312
313 while($iterator->hasNext()) {
314
315 try {
316 $chunk = $iterator->next();
317 } catch(IOException $e) {
318 throw new ClientException($e->getMessage(), $e->getCode(), $e);
319 }
320
321 $response = $this->uploadChunk($chunk, $stream, $uploadURL);
322 }
323 }
324
325
326 self::_checkResponse($response);
327
328 $output = json_decode($response->getBody());
329 if(isset($output->error)) throw new ClientException($output->error->message, $output->error->code);
330
331 } catch(HttpRequestException|FileException $e) {
332 throw new ClientException($e->getMessage(), $e->getCode());
333 }
334
335 return $output;
336 }
337
338 /**
339 * @param string $file_id
340 * @param string $destination
341 * @param int $start
342 * @param int $end
343 *
344 * @return int
345 * @throws ClientException
346 */
347 public function downloadChunk(string $file_id, string $destination, int $start, int $end):int {
348
349 try {
350 $download = new FileDownload($destination);
351
352 $this->_http->reset();
353 $response = $this->_http
354 ->setReturnTransfer()
355 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
356 ->addHeader('Range', 'bytes=' . $start . '-' . $end)
357 ->download(self::FILES_API_URL . '/' . $file_id . '?alt=media', $download);
358
359 self::_checkResponse($response);
360
361 return $response->getHeaders()->getHeader('content-length');
362 } catch(HttpRequestException $e) {
363 throw new ClientException($e->getMessage(), $e->getCode());
364 }
365 }
366
367 /**
368 * @param string $file_id
369 * @param string $destination
370 *
371 * @return void
372 * @throws ClientException
373 */
374 public function download(string $file_id, string $destination):void {
375
376 try {
377 $download = new FileDownload($destination);
378
379 $this->_http->reset();
380 $response = $this->_http
381 ->setReturnTransfer()
382 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
383 ->download(self::FILES_API_URL . '/' . $file_id . '?alt=media', $download);
384
385 self::_checkResponse($response);
386
387 } catch(HttpRequestException $e) {
388 throw new ClientException($e->getMessage(), $e->getCode());
389 }
390 }
391
392 /**
393 * @param string $file_id
394 *
395 * @return void
396 * @throws ClientException
397 */
398 public function deleteFile(string $file_id):void {
399
400 try {
401 $this->_http->reset();
402 $response = $this->_http
403 ->setMethod(JetHttp::METHOD_DELETE)
404 ->setReturnTransfer()
405 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
406 ->exec(self::FILES_API_URL . '/' . $file_id);
407
408 self::_checkResponse($response);
409
410 $body = json_decode($response->getBody());
411 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
412
413 } catch(HttpRequestException $e) {
414 throw new ClientException($e->getMessage(), $e->getCode());
415 }
416 }
417
418 /**
419 * @param string $file_id
420 *
421 * @return stdClass
422 * @throws ClientException
423 */
424 public function getFile(string $file_id):stdClass {
425
426 try {
427 $this->_http->reset();
428 $response = $this->_http
429 ->setReturnTransfer()
430 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
431 ->exec(self::FILES_API_URL . '/' . $file_id . '?fields=id,name,size,mimeType,modifiedTime,parents');
432
433 self::_checkResponse($response);
434
435 $body = json_decode($response->getBody());
436 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
437
438 return $body;
439 } catch(HttpRequestException $e) {
440 throw new ClientException($e->getMessage(), $e->getCode());
441 }
442 }
443
444 /**
445 * @param array $params
446 *
447 * @return ListFiles
448 * @throws ClientException
449 */
450 public function listFiles(array $params=[]):ListFiles {
451
452 try {
453 $this->_http->reset();
454 $response = $this->_http
455 ->setReturnTransfer()
456 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
457 ->exec(self::FILES_API_URL . (sizeof($params) ? '?' . http_build_query($params) : ''));
458
459 self::_checkResponse($response);
460
461 $body = json_decode($response->getBody());
462 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
463
464 $listFiles = new ListFiles();
465 $listFiles->setNextPageToken($body->nextPageToken ?? null);
466
467 foreach($body->files as $file_details) {
468 $file = new File();
469 $file->setId($file_details->id);
470 $file->setName($file_details->name);
471 $file->setSize($file_details->size ?? 0);
472 $file->setMimeType($file_details->mimeType);
473 $file->setModificationTime(strtotime($file_details->modifiedTime));
474 $file->setCreationTime(strtotime($file_details->createdTime));
475 $file->setMD5Checksum($file_details->md5Checksum ?? '');
476 $file->setSHA1Checksum($file_details->sha1Checksum ?? '');
477 $file->setSHA256Checksum($file_details->sha256Checksum ?? '');
478
479 $listFiles->addFile($file);
480 }
481
482 return $listFiles;
483 } catch(HttpRequestException $e) {
484 throw new ClientException($e->getMessage(), $e->getCode());
485 }
486 }
487
488 /**
489 * @param string $directory
490 * @param string $parent_id
491 *
492 * @return stdClass
493 * @throws ClientException
494 */
495 public function createDir(string $directory, string $parent_id):stdClass {
496
497 try {
498 $this->_http->reset();
499 $response = $this->_http
500 ->setReturnTransfer()
501 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
502 ->addHeader('Content-Type', 'application/json')
503 ->setBody(json_encode([
504 'name' => $directory,
505 'mimeType' => self::MIMITYPE_DIR,
506 'parents' => $parent_id == self::ROOT_ID ? [] : [$parent_id],
507 ]))
508 ->exec(self::FILES_API_URL . '?fields=id');
509
510 self::_checkResponse($response);
511
512 $body = json_decode($response->getBody());
513 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
514
515 return $body;
516 } catch(HttpRequestException $e) {
517 throw new ClientException($e->getMessage(), $e->getCode());
518 }
519 }
520
521 /**
522 * @return stdClass
523 * @throws ClientException
524 */
525 public function about():stdClass {
526
527 try {
528 $this->_http->reset();
529 $response = $this->_http
530 ->setReturnTransfer()
531 ->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()['access_token'])
532 ->exec(self::API_URL . '/drive/v2/about');
533
534 self::_checkResponse($response);
535
536 $body = json_decode($response->getBody());
537 if(isset($body->error)) throw new ClientException($body->error->message, $body->error->code);
538
539 } catch(HttpRequestException $e) {
540 throw new ClientException($e->getMessage(), $e->getCode());
541 }
542
543 return $body;
544 }
545
546 /**
547 * @param JetHttpResponse $response
548 *
549 * @return void
550 * @throws ClientException
551 */
552 private static function _checkResponse(JetHttpResponse $response):void {
553
554 if(
555 $response->getHeaders()->getCode() < 200 ||
556 $response->getHeaders()->getCode() > 299
557 ) {
558
559 $body = json_decode($response->getBody());
560
561 if($body === null) {
562 $message = $response->getHeaders()->getMessage() ?: $response->getBody();
563 } else {
564 $message = $response->getHeaders()->getMessage() ?: 'Unknown Error';
565
566 if(isset($body->error)) {
567 if(isset($body->error->error_description)) $message = $body->error->error_description;
568 elseif(isset($body->error_description)) $message = $body->error_description;
569 elseif(isset($body->error->message)) $message = $body->error->message;
570 }
571 }
572
573 throw new ClientException($message, $response->getHeaders()->getCode());
574 }
575 }
576 }