PluginProbe ʕ •ᴥ•ʔ
File Manager Pro – Filester / 1.8
File Manager Pro – Filester v1.8
2.1.1 trunk 1.6.1 1.7.6 1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9 2.0 2.0.1 2.0.2 2.1.0
filester / includes / File_manager / lib / php / elFinderVolumeGoogleDrive.class.php
filester / includes / File_manager / lib / php Last commit date
.tmp 2 years ago editors 2 years ago libs 2 years ago plugins 2 years ago resources 2 years ago MySQLStorage.sql 2 years ago autoload.php 2 years ago elFinder.class.php 2 years ago elFinderConnector.class.php 2 years ago elFinderFlysystemGoogleDriveNetmount.php 2 years ago elFinderPlugin.php 2 years ago elFinderSession.php 2 years ago elFinderSessionInterface.php 2 years ago elFinderVolumeBox.class.php 2 years ago elFinderVolumeDriver.class.php 2 years ago elFinderVolumeDropbox.class.php 2 years ago elFinderVolumeDropbox2.class.php 2 years ago elFinderVolumeFTP.class.php 2 years ago elFinderVolumeGoogleDrive.class.php 2 years ago elFinderVolumeGroup.class.php 2 years ago elFinderVolumeLocalFileSystem.class.php 2 years ago elFinderVolumeMySQL.class.php 2 years ago elFinderVolumeOneDrive.class.php 2 years ago elFinderVolumeSFTPphpseclib.class.php 2 years ago elFinderVolumeTrash.class.php 2 years ago elFinderVolumeTrashMySQL.class.php 2 years ago index.php 2 years ago mime.types 2 years ago
elFinderVolumeGoogleDrive.class.php
2164 lines
1 <?php
2
3 /**
4 * Simple elFinder driver for GoogleDrive
5 * google-api-php-client-2.x or above.
6 *
7 * @author Dmitry (dio) Levashov
8 * @author Cem (discofever)
9 **/
10 class elFinderVolumeGoogleDrive extends elFinderVolumeDriver
11 {
12 /**
13 * Driver id
14 * Must be started from letter and contains [a-z0-9]
15 * Used as part of volume id.
16 *
17 * @var string
18 **/
19 protected $driverId = 'gd';
20
21 /**
22 * Google API client object.
23 *
24 * @var object
25 **/
26 protected $client = null;
27
28 /**
29 * GoogleDrive service object.
30 *
31 * @var object
32 **/
33 protected $service = null;
34
35 /**
36 * Cache of parents of each directories.
37 *
38 * @var array
39 */
40 protected $parents = [];
41
42 /**
43 * Cache of chiled directories of each directories.
44 *
45 * @var array
46 */
47 protected $directories = null;
48
49 /**
50 * Cache of itemID => name of each items.
51 *
52 * @var array
53 */
54 protected $names = [];
55
56 /**
57 * MIME tyoe of directory.
58 *
59 * @var string
60 */
61 const DIRMIME = 'application/vnd.google-apps.folder';
62
63 /**
64 * Fetch fields for list.
65 *
66 * @var string
67 */
68 const FETCHFIELDS_LIST = 'files(id,name,mimeType,modifiedTime,parents,permissions,size,imageMediaMetadata(height,width),thumbnailLink,webContentLink,webViewLink),nextPageToken';
69
70 /**
71 * Fetch fields for get.
72 *
73 * @var string
74 */
75 const FETCHFIELDS_GET = 'id,name,mimeType,modifiedTime,parents,permissions,size,imageMediaMetadata(height,width),thumbnailLink,webContentLink,webViewLink';
76
77 /**
78 * Directory for tmp files
79 * If not set driver will try to use tmbDir as tmpDir.
80 *
81 * @var string
82 **/
83 protected $tmp = '';
84
85 /**
86 * Net mount key.
87 *
88 * @var string
89 **/
90 public $netMountKey = '';
91
92 /**
93 * Current token expires
94 *
95 * @var integer
96 **/
97 private $expires;
98
99 /**
100 * Constructor
101 * Extend options with required fields.
102 *
103 * @author Dmitry (dio) Levashov
104 * @author Cem (DiscoFever)
105 **/
106 public function __construct()
107 {
108 $opts = [
109 'client_id' => '',
110 'client_secret' => '',
111 'access_token' => [],
112 'refresh_token' => '',
113 'serviceAccountConfigFile' => '',
114 'root' => 'My Drive',
115 'gdAlias' => '%s@GDrive',
116 'googleApiClient' => '',
117 'path' => '/',
118 'tmbPath' => '',
119 'separator' => '/',
120 'useGoogleTmb' => true,
121 'acceptedName' => '#.#',
122 'rootCssClass' => 'elfinder-navbar-root-googledrive',
123 'publishPermission' => [
124 'type' => 'anyone',
125 'role' => 'reader',
126 'withLink' => true,
127 ],
128 'appsExportMap' => [
129 'application/vnd.google-apps.document' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
130 'application/vnd.google-apps.spreadsheet' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
131 'application/vnd.google-apps.drawing' => 'application/pdf',
132 'application/vnd.google-apps.presentation' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
133 'application/vnd.google-apps.script' => 'application/vnd.google-apps.script+json',
134 'default' => 'application/pdf',
135 ],
136 ];
137 $this->options = array_merge($this->options, $opts);
138 $this->options['mimeDetect'] = 'internal';
139 }
140
141 /*********************************************************************/
142 /* ORIGINAL FUNCTIONS */
143 /*********************************************************************/
144
145 /**
146 * Get Parent ID, Item ID, Parent Path as an array from path.
147 *
148 * @param string $path
149 *
150 * @return array
151 */
152 protected function _gd_splitPath($path)
153 {
154 $path = trim($path, '/');
155 $pid = '';
156 if ($path === '') {
157 $id = 'root';
158 $parent = '';
159 } else {
160 $path = str_replace('\\/', chr(0), $path);
161 $paths = explode('/', $path);
162 $id = array_pop($paths);
163 $id = str_replace(chr(0), '/', $id);
164 if ($paths) {
165 $parent = '/' . implode('/', $paths);
166 $pid = array_pop($paths);
167 } else {
168 $rootid = ($this->root === '/') ? 'root' : trim($this->root, '/');
169 if ($id === $rootid) {
170 $parent = '';
171 } else {
172 $parent = $this->root;
173 $pid = $rootid;
174 }
175 }
176 }
177
178 return array($pid, $id, $parent);
179 }
180
181 /**
182 * Drive query and fetchAll.
183 *
184 * @param string $sql
185 *
186 * @return bool|array
187 */
188 private function _gd_query($opts)
189 {
190 $result = [];
191 $pageToken = null;
192 $parameters = [
193 'fields' => self::FETCHFIELDS_LIST,
194 'pageSize' => 1000,
195 'spaces' => 'drive',
196 ];
197
198 if (is_array($opts)) {
199 $parameters = array_merge($parameters, $opts);
200 }
201 do {
202 try {
203 if ($pageToken) {
204 $parameters['pageToken'] = $pageToken;
205 }
206 $files = $this->service->files->listFiles($parameters);
207
208 $result = array_merge($result, $files->getFiles());
209 $pageToken = $files->getNextPageToken();
210 } catch (Exception $e) {
211 $pageToken = null;
212 }
213 } while ($pageToken);
214
215 return $result;
216 }
217
218 /**
219 * Get dat(googledrive metadata) from GoogleDrive.
220 *
221 * @param string $path
222 *
223 * @return array googledrive metadata
224 */
225 private function _gd_getFile($path, $fields = '')
226 {
227 list(, $itemId) = $this->_gd_splitPath($path);
228 if (!$fields) {
229 $fields = self::FETCHFIELDS_GET;
230 }
231 try {
232 $file = $this->service->files->get($itemId, ['fields' => $fields]);
233 if ($file instanceof Google_Service_Drive_DriveFile) {
234 return $file;
235 } else {
236 return [];
237 }
238 } catch (Exception $e) {
239 return [];
240 }
241 }
242
243 /**
244 * Parse line from googledrive metadata output and return file stat (array).
245 *
246 * @param array $raw line from ftp_rawlist() output
247 *
248 * @return array
249 * @author Dmitry Levashov
250 **/
251 protected function _gd_parseRaw($raw)
252 {
253 $stat = [];
254
255 $stat['iid'] = isset($raw['id']) ? $raw['id'] : 'root';
256 $stat['name'] = isset($raw['name']) ? $raw['name'] : '';
257 if (isset($raw['modifiedTime'])) {
258 $stat['ts'] = strtotime($raw['modifiedTime']);
259 }
260
261 if ($raw['mimeType'] === self::DIRMIME) {
262 $stat['mime'] = 'directory';
263 $stat['size'] = 0;
264 } else {
265 $stat['mime'] = $raw['mimeType'] == 'image/bmp' ? 'image/x-ms-bmp' : $raw['mimeType'];
266 $stat['size'] = (int)$raw['size'];
267 if ($size = $raw->getImageMediaMetadata()) {
268 $stat['width'] = $size['width'];
269 $stat['height'] = $size['height'];
270 }
271
272 $published = $this->_gd_isPublished($raw);
273
274 if ($this->options['useGoogleTmb']) {
275 if (isset($raw['thumbnailLink'])) {
276 if ($published) {
277 $stat['tmb'] = 'drive.google.com/thumbnail?authuser=0&sz=s' . $this->options['tmbSize'] . '&id=' . $raw['id'];
278 } else {
279 $stat['tmb'] = substr($raw['thumbnailLink'], 8); // remove "https://"
280 }
281 } else {
282 $stat['tmb'] = '';
283 }
284 }
285
286 if ($published) {
287 $stat['url'] = $this->_gd_getLink($raw);
288 } elseif (!$this->disabledGetUrl) {
289 $stat['url'] = '1';
290 }
291 }
292
293 return $stat;
294 }
295
296 /**
297 * Get dat(googledrive metadata) from GoogleDrive.
298 *
299 * @param string $path
300 *
301 * @return array googledrive metadata
302 */
303 private function _gd_getNameByPath($path)
304 {
305 list(, $itemId) = $this->_gd_splitPath($path);
306 if (!$this->names) {
307 $this->_gd_getDirectoryData();
308 }
309
310 return isset($this->names[$itemId]) ? $this->names[$itemId] : '';
311 }
312
313 /**
314 * Make cache of $parents, $names and $directories.
315 *
316 * @param bool $usecache
317 */
318 protected function _gd_getDirectoryData($usecache = true)
319 {
320 if ($usecache) {
321 $cache = $this->session->get($this->id . $this->netMountKey, []);
322 if ($cache) {
323 $this->parents = $cache['parents'];
324 $this->names = $cache['names'];
325 $this->directories = $cache['directories'];
326
327 return;
328 }
329 }
330
331 $root = '';
332 if ($this->root === '/') {
333 // get root id
334 if ($res = $this->_gd_getFile('/', 'id')) {
335 $root = $res->getId();
336 }
337 }
338
339 $data = [];
340 $opts = [
341 'fields' => 'files(id, name, parents)',
342 'q' => sprintf('trashed=false and mimeType="%s"', self::DIRMIME),
343 ];
344 $res = $this->_gd_query($opts);
345 foreach ($res as $raw) {
346 if ($parents = $raw->getParents()) {
347 $id = $raw->getId();
348 $this->parents[$id] = $parents;
349 $this->names[$id] = $raw->getName();
350 foreach ($parents as $p) {
351 if (isset($data[$p])) {
352 $data[$p][] = $id;
353 } else {
354 $data[$p] = [$id];
355 }
356 }
357 }
358 }
359 if ($root && isset($data[$root])) {
360 $data['root'] = $data[$root];
361 }
362 $this->directories = $data;
363 $this->session->set($this->id . $this->netMountKey, [
364 'parents' => $this->parents,
365 'names' => $this->names,
366 'directories' => $this->directories,
367 ]);
368 }
369
370 /**
371 * Get descendants directories.
372 *
373 * @param string $itemId
374 *
375 * @return array
376 */
377 protected function _gd_getDirectories($itemId)
378 {
379 $ret = [];
380 if ($this->directories === null) {
381 $this->_gd_getDirectoryData();
382 }
383 $data = $this->directories;
384 if (isset($data[$itemId])) {
385 $ret = $data[$itemId];
386 foreach ($data[$itemId] as $cid) {
387 $ret = array_merge($ret, $this->_gd_getDirectories($cid));
388 }
389 }
390
391 return $ret;
392 }
393
394 /**
395 * Get ID based path from item ID.
396 *
397 * @param string $id
398 *
399 * @return array
400 */
401 protected function _gd_getMountPaths($id)
402 {
403 $root = false;
404 if ($this->directories === null) {
405 $this->_gd_getDirectoryData();
406 }
407 list($pid) = explode('/', $id, 2);
408 $path = $id;
409 if ('/' . $pid === $this->root) {
410 $root = true;
411 } elseif (!isset($this->parents[$pid])) {
412 $root = true;
413 $path = ltrim(substr($path, strlen($pid)), '/');
414 }
415 $res = [];
416 if ($root) {
417 if ($this->root === '/' || strpos('/' . $path, $this->root) === 0) {
418 $res = [(strpos($path, '/') === false) ? '/' : ('/' . $path)];
419 }
420 } else {
421 foreach ($this->parents[$pid] as $p) {
422 $_p = $p . '/' . $path;
423 $res = array_merge($res, $this->_gd_getMountPaths($_p));
424 }
425 }
426
427 return $res;
428 }
429
430 /**
431 * Return is published.
432 *
433 * @param object $file
434 *
435 * @return bool
436 */
437 protected function _gd_isPublished($file)
438 {
439 $res = false;
440 $pType = $this->options['publishPermission']['type'];
441 $pRole = $this->options['publishPermission']['role'];
442 if ($permissions = $file->getPermissions()) {
443 foreach ($permissions as $permission) {
444 if ($permission->type === $pType && $permission->role === $pRole) {
445 $res = true;
446 break;
447 }
448 }
449 }
450
451 return $res;
452 }
453
454 /**
455 * return item URL link.
456 *
457 * @param object $file
458 *
459 * @return string
460 */
461 protected function _gd_getLink($file)
462 {
463 if (strpos($file->mimeType, 'application/vnd.google-apps.') !== 0) {
464 if ($url = $file->getWebContentLink()) {
465 return str_replace('export=download', 'export=media', $url);
466 }
467 }
468 if ($url = $file->getWebViewLink()) {
469 return $url;
470 }
471
472 return '';
473 }
474
475 /**
476 * Get download url.
477 *
478 * @param Google_Service_Drive_DriveFile $file
479 *
480 * @return string|false
481 */
482 protected function _gd_getDownloadUrl($file)
483 {
484 if (strpos($file->mimeType, 'application/vnd.google-apps.') !== 0) {
485 return 'https://www.googleapis.com/drive/v3/files/' . $file->getId() . '?alt=media';
486 } else {
487 $mimeMap = $this->options['appsExportMap'];
488 if (isset($mimeMap[$file->getMimeType()])) {
489 $mime = $mimeMap[$file->getMimeType()];
490 } else {
491 $mime = $mimeMap['default'];
492 }
493 $mime = rawurlencode($mime);
494
495 return 'https://www.googleapis.com/drive/v3/files/' . $file->getId() . '/export?mimeType=' . $mime;
496 }
497
498 return false;
499 }
500
501 /**
502 * Get thumbnail from GoogleDrive.com.
503 *
504 * @param string $path
505 *
506 * @return string | boolean
507 */
508 protected function _gd_getThumbnail($path)
509 {
510 list(, $itemId) = $this->_gd_splitPath($path);
511
512 try {
513 $contents = $this->service->files->get($itemId, [
514 'alt' => 'media',
515 ]);
516 $contents = $contents->getBody()->detach();
517 rewind($contents);
518
519 return $contents;
520 } catch (Exception $e) {
521 return false;
522 }
523 }
524
525 /**
526 * Publish permissions specified path item.
527 *
528 * @param string $path
529 *
530 * @return bool
531 */
532 protected function _gd_publish($path)
533 {
534 if ($file = $this->_gd_getFile($path)) {
535 if ($this->_gd_isPublished($file)) {
536 return true;
537 }
538 try {
539 if ($this->service->permissions->create($file->getId(), new \Google_Service_Drive_Permission($this->options['publishPermission']))) {
540 return true;
541 }
542 } catch (Exception $e) {
543 return false;
544 }
545 }
546
547 return false;
548 }
549
550 /**
551 * unPublish permissions specified path.
552 *
553 * @param string $path
554 *
555 * @return bool
556 */
557 protected function _gd_unPublish($path)
558 {
559 if ($file = $this->_gd_getFile($path)) {
560 if (!$this->_gd_isPublished($file)) {
561 return true;
562 }
563 $permissions = $file->getPermissions();
564 $pType = $this->options['publishPermission']['type'];
565 $pRole = $this->options['publishPermission']['role'];
566 try {
567 foreach ($permissions as $permission) {
568 if ($permission->type === $pType && $permission->role === $pRole) {
569 $this->service->permissions->delete($file->getId(), $permission->getId());
570
571 return true;
572 break;
573 }
574 }
575 } catch (Exception $e) {
576 return false;
577 }
578 }
579
580 return false;
581 }
582
583 /**
584 * Read file chunk.
585 *
586 * @param resource $handle
587 * @param int $chunkSize
588 *
589 * @return string
590 */
591 protected function _gd_readFileChunk($handle, $chunkSize)
592 {
593 $byteCount = 0;
594 $giantChunk = '';
595 while (!feof($handle)) {
596 // fread will never return more than 8192 bytes if the stream is read buffered and it does not represent a plain file
597 $chunk = fread($handle, 8192);
598 $byteCount += strlen($chunk);
599 $giantChunk .= $chunk;
600 if ($byteCount >= $chunkSize) {
601 return $giantChunk;
602 }
603 }
604
605 return $giantChunk;
606 }
607
608 /*********************************************************************/
609 /* EXTENDED FUNCTIONS */
610 /*********************************************************************/
611
612 /**
613 * Prepare
614 * Call from elFinder::netmout() before volume->mount().
615 *
616 * @return array
617 * @author Naoki Sawada
618 * @author Raja Sharma updating for GoogleDrive
619 **/
620 public function netmountPrepare($options)
621 {
622 if (empty($options['client_id']) && defined('ELFINDER_GOOGLEDRIVE_CLIENTID')) {
623 $options['client_id'] = ELFINDER_GOOGLEDRIVE_CLIENTID;
624 }
625 if (empty($options['client_secret']) && defined('ELFINDER_GOOGLEDRIVE_CLIENTSECRET')) {
626 $options['client_secret'] = ELFINDER_GOOGLEDRIVE_CLIENTSECRET;
627 }
628 if (empty($options['googleApiClient']) && defined('ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT')) {
629 $options['googleApiClient'] = ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT;
630 include_once $options['googleApiClient'];
631 }
632
633 if (!isset($options['pass'])) {
634 $options['pass'] = '';
635 }
636
637 try {
638 $client = new \Google_Client();
639 $client->setClientId($options['client_id']);
640 $client->setClientSecret($options['client_secret']);
641
642 if ($options['pass'] === 'reauth') {
643 $options['pass'] = '';
644 $this->session->set('GoogleDriveAuthParams', [])->set('GoogleDriveTokens', []);
645 } elseif ($options['pass'] === 'googledrive') {
646 $options['pass'] = '';
647 }
648
649 $options = array_merge($this->session->get('GoogleDriveAuthParams', []), $options);
650
651 if (!isset($options['access_token'])) {
652 $options['access_token'] = $this->session->get('GoogleDriveTokens', []);
653 $this->session->remove('GoogleDriveTokens');
654 }
655 $aToken = $options['access_token'];
656
657 $rootObj = $service = null;
658 if ($aToken) {
659 try {
660 $client->setAccessToken($aToken);
661 if ($client->isAccessTokenExpired()) {
662 $aToken = array_merge($aToken, $client->fetchAccessTokenWithRefreshToken());
663 $client->setAccessToken($aToken);
664 }
665 $service = new \Google_Service_Drive($client);
666 $rootObj = $service->files->get('root');
667
668 $options['access_token'] = $aToken;
669 $this->session->set('GoogleDriveAuthParams', $options);
670 } catch (Exception $e) {
671 $aToken = [];
672 $options['access_token'] = [];
673 if ($options['user'] !== 'init') {
674 $this->session->set('GoogleDriveAuthParams', $options);
675
676 return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
677 }
678 }
679 }
680
681 $itpCare = isset($options['code']);
682 $code = $itpCare? $options['code'] : (isset($_GET['code'])? $_GET['code'] : '');
683 if ($code || (isset($options['user']) && $options['user'] === 'init')) {
684 if (empty($options['url'])) {
685 $options['url'] = elFinder::getConnectorUrl();
686 }
687
688 if (isset($options['id'])) {
689 $callback = $options['url']
690 . (strpos($options['url'], '?') !== false? '&' : '?') . 'cmd=netmount&protocol=googledrive&host=' . ($options['id'] === 'elfinder'? '1' : $options['id']);
691 $client->setRedirectUri($callback);
692 }
693
694 if (!$aToken && empty($code)) {
695 $client->setScopes([Google_Service_Drive::DRIVE]);
696 if (!empty($options['offline'])) {
697 $client->setApprovalPrompt('force');
698 $client->setAccessType('offline');
699 }
700 $url = $client->createAuthUrl();
701
702 $html = '<input id="elf-volumedriver-googledrive-host-btn" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" value="{msg:btnApprove}" type="button">';
703 $html .= '<script>
704 $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", {protocol: "googledrive", mode: "makebtn", url: "' . $url . '"});
705 </script>';
706 if (empty($options['pass']) && $options['host'] !== '1') {
707 $options['pass'] = 'return';
708 $this->session->set('GoogleDriveAuthParams', $options);
709
710 return ['exit' => true, 'body' => $html];
711 } else {
712 $out = [
713 'node' => $options['id'],
714 'json' => '{"protocol": "googledrive", "mode": "makebtn", "body" : "' . str_replace($html, '"', '\\"') . '", "error" : "' . elFinder::ERROR_ACCESS_DENIED . '"}',
715 'bind' => 'netmount',
716 ];
717
718 return ['exit' => 'callback', 'out' => $out];
719 }
720 } else {
721 if ($code) {
722 if (!empty($options['id'])) {
723 $aToken = $client->fetchAccessTokenWithAuthCode($code);
724 $options['access_token'] = $aToken;
725 unset($options['code']);
726 $this->session->set('GoogleDriveTokens', $aToken)->set('GoogleDriveAuthParams', $options);
727 $out = [
728 'node' => $options['id'],
729 'json' => '{"protocol": "googledrive", "mode": "done", "reset": 1}',
730 'bind' => 'netmount',
731 ];
732 } else {
733 $nodeid = ($_GET['host'] === '1')? 'elfinder' : $_GET['host'];
734 $out = array(
735 'node' => $nodeid,
736 'json' => json_encode(array(
737 'protocol' => 'googledrive',
738 'host' => $nodeid,
739 'mode' => 'redirect',
740 'options' => array(
741 'id' => $nodeid,
742 'code'=> $code
743 )
744 )),
745 'bind' => 'netmount'
746 );
747 }
748 if (!$itpCare) {
749 return array('exit' => 'callback', 'out' => $out);
750 } else {
751 return array('exit' => true, 'body' => $out['json']);
752 }
753 }
754 $path = $options['path'];
755 if ($path === '/') {
756 $path = 'root';
757 }
758 $folders = [];
759 foreach ($service->files->listFiles([
760 'pageSize' => 1000,
761 'q' => sprintf('trashed = false and "%s" in parents and mimeType = "application/vnd.google-apps.folder"', $path),
762 ]) as $f) {
763 $folders[$f->getId()] = $f->getName();
764 }
765 natcasesort($folders);
766
767 if ($options['pass'] === 'folders') {
768 return ['exit' => true, 'folders' => $folders];
769 }
770
771 $folders = ['root' => $rootObj->getName()] + $folders;
772 $folders = json_encode($folders);
773 $expires = empty($aToken['refresh_token']) ? $aToken['created'] + $aToken['expires_in'] - 30 : 0;
774 $mnt2res = empty($aToken['refresh_token']) ? '' : ', "mnt2res": 1';
775 $json = '{"protocol": "googledrive", "mode": "done", "folders": ' . $folders . ', "expires": ' . $expires . $mnt2res . '}';
776 $options['pass'] = 'return';
777 $html = 'Google.com';
778 $html .= '<script>
779 $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", ' . $json . ');
780 </script>';
781 $this->session->set('GoogleDriveAuthParams', $options);
782
783 return ['exit' => true, 'body' => $html];
784 }
785 }
786 } catch (Exception $e) {
787 $this->session->remove('GoogleDriveAuthParams')->remove('GoogleDriveTokens');
788 if (empty($options['pass'])) {
789 return ['exit' => true, 'body' => '{msg:' . elFinder::ERROR_ACCESS_DENIED . '}' . ' ' . $e->getMessage()];
790 } else {
791 return ['exit' => true, 'error' => [elFinder::ERROR_ACCESS_DENIED, $e->getMessage()]];
792 }
793 }
794
795 if (!$aToken) {
796 return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
797 }
798
799 if ($options['path'] === '/') {
800 $options['path'] = 'root';
801 }
802
803 try {
804 $file = $service->files->get($options['path']);
805 $options['alias'] = sprintf($this->options['gdAlias'], $file->getName());
806 } catch (Google_Service_Exception $e) {
807 $err = json_decode($e->getMessage(), true);
808 if (isset($err['error']) && $err['error']['code'] == 404) {
809 return ['exit' => true, 'error' => [elFinder::ERROR_TRGDIR_NOT_FOUND, $options['path']]];
810 } else {
811 return ['exit' => true, 'error' => $e->getMessage()];
812 }
813 } catch (Exception $e) {
814 return ['exit' => true, 'error' => $e->getMessage()];
815 }
816
817 foreach (['host', 'user', 'pass', 'id', 'offline'] as $key) {
818 unset($options[$key]);
819 }
820
821 return $options;
822 }
823
824 /**
825 * process of on netunmount
826 * Drop `googledrive` & rm thumbs.
827 *
828 * @param $netVolumes
829 * @param $key
830 *
831 * @return bool
832 */
833 public function netunmount($netVolumes, $key)
834 {
835 if (!$this->options['useGoogleTmb']) {
836 if ($tmbs = glob(rtrim($this->options['tmbPath'], '\\/') . DIRECTORY_SEPARATOR . $this->netMountKey . '*.png')) {
837 foreach ($tmbs as $file) {
838 unlink($file);
839 }
840 }
841 }
842 $this->session->remove($this->id . $this->netMountKey);
843
844 return true;
845 }
846
847 /**
848 * Return fileinfo based on filename
849 * For item ID based path file system
850 * Please override if needed on each drivers.
851 *
852 * @param string $path file cache
853 *
854 * @return array
855 */
856 protected function isNameExists($path)
857 {
858 list($parentId, $name) = $this->_gd_splitPath($path);
859 $opts = [
860 'q' => sprintf('trashed=false and "%s" in parents and name="%s"', $parentId, $name),
861 'fields' => self::FETCHFIELDS_LIST,
862 ];
863 $srcFile = $this->_gd_query($opts);
864
865 return empty($srcFile) ? false : $this->_gd_parseRaw($srcFile[0]);
866 }
867
868 /*********************************************************************/
869 /* INIT AND CONFIGURE */
870 /*********************************************************************/
871
872 /**
873 * Prepare FTP connection
874 * Connect to remote server and check if credentials are correct, if so, store the connection id in $ftp_conn.
875 *
876 * @return bool
877 * @author Dmitry (dio) Levashov
878 * @author Cem (DiscoFever)
879 **/
880 protected function init()
881 {
882 $serviceAccountConfig = '';
883 if (empty($this->options['serviceAccountConfigFile'])) {
884 if (empty($options['client_id'])) {
885 if (defined('ELFINDER_GOOGLEDRIVE_CLIENTID') && ELFINDER_GOOGLEDRIVE_CLIENTID) {
886 $this->options['client_id'] = ELFINDER_GOOGLEDRIVE_CLIENTID;
887 } else {
888 return $this->setError('Required option "client_id" is undefined.');
889 }
890 }
891 if (empty($options['client_secret'])) {
892 if (defined('ELFINDER_GOOGLEDRIVE_CLIENTSECRET') && ELFINDER_GOOGLEDRIVE_CLIENTSECRET) {
893 $this->options['client_secret'] = ELFINDER_GOOGLEDRIVE_CLIENTSECRET;
894 } else {
895 return $this->setError('Required option "client_secret" is undefined.');
896 }
897 }
898 if (!$this->options['access_token'] && !$this->options['refresh_token']) {
899 return $this->setError('Required option "access_token" or "refresh_token" is undefined.');
900 }
901 } else {
902 if (!is_readable($this->options['serviceAccountConfigFile'])) {
903 return $this->setError('Option "serviceAccountConfigFile" file is not readable.');
904 }
905 $serviceAccountConfig = $this->options['serviceAccountConfigFile'];
906 }
907
908 try {
909 if (!$serviceAccountConfig) {
910 $aTokenFile = '';
911 if ($this->options['refresh_token']) {
912 // permanent mount
913 $aToken = $this->options['refresh_token'];
914 $this->options['access_token'] = '';
915 $tmp = elFinder::getStaticVar('commonTempPath');
916 if (!$tmp) {
917 $tmp = $this->getTempPath();
918 }
919 if ($tmp) {
920 $aTokenFile = $tmp . DIRECTORY_SEPARATOR . md5($this->options['client_id'] . $this->options['refresh_token']) . '.gtoken';
921 if (is_file($aTokenFile)) {
922 $this->options['access_token'] = json_decode(file_get_contents($aTokenFile), true);
923 }
924 }
925 } else {
926 // make net mount key for network mount
927 if (is_array($this->options['access_token'])) {
928 $aToken = !empty($this->options['access_token']['refresh_token'])
929 ? $this->options['access_token']['refresh_token']
930 : $this->options['access_token']['access_token'];
931 } else {
932 return $this->setError('Required option "access_token" is not Array or empty.');
933 }
934 }
935 }
936
937 $errors = [];
938 if ($this->needOnline && !$this->service) {
939 if (($this->options['googleApiClient'] || defined('ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT')) && !class_exists('Google_Client')) {
940 include_once $this->options['googleApiClient'] ? $this->options['googleApiClient'] : ELFINDER_GOOGLEDRIVE_GOOGLEAPICLIENT;
941 }
942 if (!class_exists('Google_Client')) {
943 return $this->setError('Class Google_Client not found.');
944 }
945
946 $this->client = new \Google_Client();
947
948 $client = $this->client;
949
950 if (!$serviceAccountConfig) {
951 if ($this->options['access_token']) {
952 $client->setAccessToken($this->options['access_token']);
953 $access_token = $this->options['access_token'];
954 }
955 if ($client->isAccessTokenExpired()) {
956 $client->setClientId($this->options['client_id']);
957 $client->setClientSecret($this->options['client_secret']);
958 $access_token = $client->fetchAccessTokenWithRefreshToken($this->options['refresh_token'] ?: null);
959 $client->setAccessToken($access_token);
960 if ($aTokenFile) {
961 file_put_contents($aTokenFile, json_encode($access_token));
962 } else {
963 $access_token['refresh_token'] = $this->options['access_token']['refresh_token'];
964 }
965 if (!empty($this->options['netkey'])) {
966 elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'access_token', $access_token);
967 }
968 $this->options['access_token'] = $access_token;
969 }
970 $this->expires = empty($access_token['refresh_token']) ? $access_token['created'] + $access_token['expires_in'] - 30 : 0;
971 } else {
972 $client->setAuthConfigFile($serviceAccountConfig);
973 $client->setScopes([Google_Service_Drive::DRIVE]);
974 $aToken = $client->getClientId();
975 }
976 $this->service = new \Google_Service_Drive($client);
977 }
978
979 if ($this->needOnline) {
980 $this->netMountKey = md5($aToken . '-' . $this->options['path']);
981 }
982 } catch (InvalidArgumentException $e) {
983 $errors[] = $e->getMessage();
984 } catch (Google_Service_Exception $e) {
985 $errors[] = $e->getMessage();
986 }
987
988 if ($this->needOnline && !$this->service) {
989 $this->session->remove($this->id . $this->netMountKey);
990 if ($aTokenFile) {
991 if (is_file($aTokenFile)) {
992 unlink($aTokenFile);
993 }
994 }
995 $errors[] = 'Google Drive Service could not be loaded.';
996
997 return $this->setError($errors);
998 }
999
1000 // normalize root path
1001 if ($this->options['path'] == 'root') {
1002 $this->options['path'] = '/';
1003 }
1004 $this->root = $this->options['path'] = $this->_normpath($this->options['path']);
1005
1006 if (empty($this->options['alias'])) {
1007 if ($this->needOnline) {
1008 $this->options['root'] = ($this->options['root'] === '')? $this->_gd_getNameByPath('root') : $this->options['root'];
1009 $this->options['alias'] = ($this->options['path'] === '/') ? $this->options['root'] : sprintf($this->options['gdAlias'], $this->_gd_getNameByPath($this->options['path']));
1010 if (!empty($this->options['netkey'])) {
1011 elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'alias', $this->options['alias']);
1012 }
1013 } else {
1014 $this->options['root'] = ($this->options['root'] === '')? 'GoogleDrive' : $this->options['root'];
1015 $this->options['alias'] = $this->options['root'];
1016 }
1017 }
1018
1019 $this->rootName = isset($this->options['alias'])? $this->options['alias'] : 'GoogleDrive';
1020
1021 if (!empty($this->options['tmpPath'])) {
1022 if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'])) && is_writable($this->options['tmpPath'])) {
1023 $this->tmp = $this->options['tmpPath'];
1024 }
1025 }
1026
1027 if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
1028 $this->tmp = $tmp;
1029 }
1030
1031 // This driver dose not support `syncChkAsTs`
1032 $this->options['syncChkAsTs'] = false;
1033
1034 // 'lsPlSleep' minmum 10 sec
1035 $this->options['lsPlSleep'] = max(10, $this->options['lsPlSleep']);
1036
1037 if ($this->options['useGoogleTmb']) {
1038 $this->options['tmbURL'] = 'https://';
1039 $this->options['tmbPath'] = '';
1040 }
1041
1042 // enable command archive
1043 $this->options['useRemoteArchive'] = true;
1044
1045 return true;
1046 }
1047
1048 /**
1049 * Configure after successfull mount.
1050 *
1051 * @author Dmitry (dio) Levashov
1052 **/
1053 protected function configure()
1054 {
1055 parent::configure();
1056
1057 // fallback of $this->tmp
1058 if (!$this->tmp && $this->tmbPathWritable) {
1059 $this->tmp = $this->tmbPath;
1060 }
1061
1062 if ($this->needOnline && $this->isMyReload()) {
1063 $this->_gd_getDirectoryData(false);
1064 }
1065 }
1066
1067 /*********************************************************************/
1068 /* FS API */
1069 /*********************************************************************/
1070
1071 /**
1072 * Close opened connection.
1073 *
1074 * @author Dmitry (dio) Levashov
1075 **/
1076 public function umount()
1077 {
1078 }
1079
1080 /**
1081 * Cache dir contents.
1082 *
1083 * @param string $path dir path
1084 *
1085 * @return array
1086 * @author Dmitry Levashov
1087 */
1088 protected function cacheDir($path)
1089 {
1090 $this->dirsCache[$path] = [];
1091 $hasDir = false;
1092
1093 list(, $pid) = $this->_gd_splitPath($path);
1094
1095 $opts = [
1096 'fields' => self::FETCHFIELDS_LIST,
1097 'q' => sprintf('trashed=false and "%s" in parents', $pid),
1098 ];
1099
1100 $res = $this->_gd_query($opts);
1101
1102 $mountPath = $this->_normpath($path . '/');
1103
1104 if ($res) {
1105 foreach ($res as $raw) {
1106 if ($stat = $this->_gd_parseRaw($raw)) {
1107 $stat = $this->updateCache($mountPath . $raw->id, $stat);
1108 if (empty($stat['hidden']) && $path !== $mountPath . $raw->id) {
1109 if (!$hasDir && $stat['mime'] === 'directory') {
1110 $hasDir = true;
1111 }
1112 $this->dirsCache[$path][] = $mountPath . $raw->id;
1113 }
1114 }
1115 }
1116 }
1117
1118 if (isset($this->sessionCache['subdirs'])) {
1119 $this->sessionCache['subdirs'][$path] = $hasDir;
1120 }
1121
1122 return $this->dirsCache[$path];
1123 }
1124
1125 /**
1126 * Recursive files search.
1127 *
1128 * @param string $path dir path
1129 * @param string $q search string
1130 * @param array $mimes
1131 *
1132 * @return array
1133 * @throws elFinderAbortException
1134 * @author Naoki Sawada
1135 */
1136 protected function doSearch($path, $q, $mimes)
1137 {
1138 if (!empty($this->doSearchCurrentQuery['matchMethod'])) {
1139 // has custom match method use elFinderVolumeDriver::doSearch()
1140 return parent::doSearch($path, $q, $mimes);
1141 }
1142
1143 list(, $itemId) = $this->_gd_splitPath($path);
1144
1145 $path = $this->_normpath($path . '/');
1146 $result = [];
1147 $query = '';
1148
1149 if ($itemId !== 'root') {
1150 $dirs = array_merge([$itemId], $this->_gd_getDirectories($itemId));
1151 $query = '(\'' . implode('\' in parents or \'', $dirs) . '\' in parents)';
1152 }
1153
1154 $tmp = [];
1155 if (!$mimes) {
1156 foreach (explode(' ', $q) as $_v) {
1157 $tmp[] = 'fullText contains \'' . str_replace('\'', '\\\'', $_v) . '\'';
1158 }
1159 $query .= ($query ? ' and ' : '') . implode(' and ', $tmp);
1160 } else {
1161 foreach ($mimes as $_v) {
1162 $tmp[] = 'mimeType contains \'' . str_replace('\'', '\\\'', $_v) . '\'';
1163 }
1164 $query .= ($query ? ' and ' : '') . '(' . implode(' or ', $tmp) . ')';
1165 }
1166
1167 $opts = [
1168 'q' => sprintf('trashed=false and (%s)', $query),
1169 ];
1170
1171 $res = $this->_gd_query($opts);
1172
1173 $timeout = $this->options['searchTimeout'] ? $this->searchStart + $this->options['searchTimeout'] : 0;
1174 foreach ($res as $raw) {
1175 if ($timeout && $timeout < time()) {
1176 $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->_path($path));
1177 break;
1178 }
1179 if ($stat = $this->_gd_parseRaw($raw)) {
1180 if ($parents = $raw->getParents()) {
1181 foreach ($parents as $parent) {
1182 $paths = $this->_gd_getMountPaths($parent);
1183 foreach ($paths as $path) {
1184 $path = ($path === '') ? '/' : (rtrim($path, '/') . '/');
1185 if (!isset($this->cache[$path . $raw->id])) {
1186 $stat = $this->updateCache($path . $raw->id, $stat);
1187 } else {
1188 $stat = $this->cache[$path . $raw->id];
1189 }
1190 if (empty($stat['hidden'])) {
1191 $stat['path'] = $this->_path($path) . $stat['name'];
1192 $result[] = $stat;
1193 }
1194 }
1195 }
1196 }
1197 }
1198 }
1199
1200 return $result;
1201 }
1202
1203 /**
1204 * Copy file/recursive copy dir only in current volume.
1205 * Return new file path or false.
1206 *
1207 * @param string $src source path
1208 * @param string $dst destination dir path
1209 * @param string $name new file name (optionaly)
1210 *
1211 * @return string|false
1212 * @author Dmitry (dio) Levashov
1213 * @author Naoki Sawada
1214 **/
1215 protected function copy($src, $dst, $name)
1216 {
1217 $this->clearcache();
1218 $res = $this->_gd_getFile($src);
1219 if ($res['mimeType'] == self::DIRMIME) {
1220 $newDir = $this->_mkdir($dst, $name);
1221 if ($newDir) {
1222 list(, $itemId) = $this->_gd_splitPath($newDir);
1223 list(, $srcId) = $this->_gd_splitPath($src);
1224 $path = $this->_joinPath($dst, $itemId);
1225 $opts = [
1226 'q' => sprintf('trashed=false and "%s" in parents', $srcId),
1227 ];
1228
1229 $res = $this->_gd_query($opts);
1230 foreach ($res as $raw) {
1231 $raw['mimeType'] == self::DIRMIME ? $this->copy($src . '/' . $raw['id'], $path, $raw['name']) : $this->_copy($src . '/' . $raw['id'], $path, $raw['name']);
1232 }
1233
1234 $ret = $this->_joinPath($dst, $itemId);
1235 $this->added[] = $this->stat($ret);
1236 } else {
1237 $ret = $this->setError(elFinder::ERROR_COPY, $this->_path($src));
1238 }
1239 } else {
1240 if ($itemId = $this->_copy($src, $dst, $name)) {
1241 $ret = $this->_joinPath($dst, $itemId);
1242 $this->added[] = $this->stat($ret);
1243 } else {
1244 $ret = $this->setError(elFinder::ERROR_COPY, $this->_path($src));
1245 }
1246 }
1247 return $ret;
1248 }
1249
1250 /**
1251 * Remove file/ recursive remove dir.
1252 *
1253 * @param string $path file path
1254 * @param bool $force try to remove even if file locked
1255 * @param bool $recursive
1256 *
1257 * @return bool
1258 * @throws elFinderAbortException
1259 * @author Dmitry (dio) Levashov
1260 * @author Naoki Sawada
1261 */
1262 protected function remove($path, $force = false, $recursive = false)
1263 {
1264 $stat = $this->stat($path);
1265 $stat['realpath'] = $path;
1266 $this->rmTmb($stat);
1267 $this->clearcache();
1268
1269 if (empty($stat)) {
1270 return $this->setError(elFinder::ERROR_RM, $this->_path($path), elFinder::ERROR_FILE_NOT_FOUND);
1271 }
1272
1273 if (!$force && !empty($stat['locked'])) {
1274 return $this->setError(elFinder::ERROR_LOCKED, $this->_path($path));
1275 }
1276
1277 if ($stat['mime'] == 'directory') {
1278 if (!$recursive && !$this->_rmdir($path)) {
1279 return $this->setError(elFinder::ERROR_RM, $this->_path($path));
1280 }
1281 } else {
1282 if (!$recursive && !$this->_unlink($path)) {
1283 return $this->setError(elFinder::ERROR_RM, $this->_path($path));
1284 }
1285 }
1286
1287 $this->removed[] = $stat;
1288
1289 return true;
1290 }
1291
1292 /**
1293 * Create thumnbnail and return it's URL on success.
1294 *
1295 * @param string $path file path
1296 * @param $stat
1297 *
1298 * @return string|false
1299 * @throws ImagickException
1300 * @throws elFinderAbortException
1301 * @author Dmitry (dio) Levashov
1302 * @author Naoki Sawada
1303 */
1304 protected function createTmb($path, $stat)
1305 {
1306 if (!$stat || !$this->canCreateTmb($path, $stat)) {
1307 return false;
1308 }
1309
1310 $name = $this->tmbname($stat);
1311 $tmb = $this->tmbPath . DIRECTORY_SEPARATOR . $name;
1312
1313 // copy image into tmbPath so some drivers does not store files on local fs
1314 if (!$data = $this->_gd_getThumbnail($path)) {
1315 return false;
1316 }
1317 if (!file_put_contents($tmb, $data)) {
1318 return false;
1319 }
1320
1321 $result = false;
1322
1323 $tmbSize = $this->tmbSize;
1324
1325 if (($s = getimagesize($tmb)) == false) {
1326 return false;
1327 }
1328
1329 /* If image smaller or equal thumbnail size - just fitting to thumbnail square */
1330 if ($s[0] <= $tmbSize && $s[1] <= $tmbSize) {
1331 $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png');
1332 } else {
1333 if ($this->options['tmbCrop']) {
1334
1335 /* Resize and crop if image bigger than thumbnail */
1336 if (!(($s[0] > $tmbSize && $s[1] <= $tmbSize) || ($s[0] <= $tmbSize && $s[1] > $tmbSize)) || ($s[0] > $tmbSize && $s[1] > $tmbSize)) {
1337 $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, false, 'png');
1338 }
1339
1340 if (($s = getimagesize($tmb)) != false) {
1341 $x = $s[0] > $tmbSize ? intval(($s[0] - $tmbSize) / 2) : 0;
1342 $y = $s[1] > $tmbSize ? intval(($s[1] - $tmbSize) / 2) : 0;
1343 $result = $this->imgCrop($tmb, $tmbSize, $tmbSize, $x, $y, 'png');
1344 }
1345 } else {
1346 $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, true, 'png');
1347 }
1348
1349 $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png');
1350 }
1351
1352 if (!$result) {
1353 unlink($tmb);
1354
1355 return false;
1356 }
1357
1358 return $name;
1359 }
1360
1361 /**
1362 * Return thumbnail file name for required file.
1363 *
1364 * @param array $stat file stat
1365 *
1366 * @return string
1367 * @author Dmitry (dio) Levashov
1368 **/
1369 protected function tmbname($stat)
1370 {
1371 return $this->netMountKey . $stat['iid'] . $stat['ts'] . '.png';
1372 }
1373
1374 /**
1375 * Return content URL (for netmout volume driver)
1376 * If file.url == 1 requests from JavaScript client with XHR.
1377 *
1378 * @param string $hash file hash
1379 * @param array $options options array
1380 *
1381 * @return bool|string
1382 * @author Naoki Sawada
1383 */
1384 public function getContentUrl($hash, $options = [])
1385 {
1386 if (!empty($options['onetime']) && $this->options['onetimeUrl']) {
1387 return parent::getContentUrl($hash, $options);
1388 }
1389 if (!empty($options['temporary'])) {
1390 // try make temporary file
1391 $url = parent::getContentUrl($hash, $options);
1392 if ($url) {
1393 return $url;
1394 }
1395 }
1396 if (($file = $this->file($hash)) == false || !$file['url'] || $file['url'] == 1) {
1397 $path = $this->decode($hash);
1398
1399 if ($this->_gd_publish($path)) {
1400 if ($raw = $this->_gd_getFile($path)) {
1401 return $this->_gd_getLink($raw);
1402 }
1403 }
1404 }
1405
1406 return false;
1407 }
1408
1409 /**
1410 * Return debug info for client.
1411 *
1412 * @return array
1413 **/
1414 public function debug()
1415 {
1416 $res = parent::debug();
1417 if (!empty($this->options['netkey']) && empty($this->options['refresh_token']) && $this->options['access_token'] && isset($this->options['access_token']['refresh_token'])) {
1418 $res['refresh_token'] = $this->options['access_token']['refresh_token'];
1419 }
1420
1421 return $res;
1422 }
1423
1424 /*********************** paths/urls *************************/
1425
1426 /**
1427 * Return parent directory path.
1428 *
1429 * @param string $path file path
1430 *
1431 * @return string
1432 * @author Dmitry (dio) Levashov
1433 **/
1434 protected function _dirname($path)
1435 {
1436 list(, , $parent) = $this->_gd_splitPath($path);
1437
1438 return $this->_normpath($parent);
1439 }
1440
1441 /**
1442 * Return file name.
1443 *
1444 * @param string $path file path
1445 *
1446 * @return string
1447 * @author Dmitry (dio) Levashov
1448 **/
1449 protected function _basename($path)
1450 {
1451 list(, $basename) = $this->_gd_splitPath($path);
1452
1453 return $basename;
1454 }
1455
1456 /**
1457 * Join dir name and file name and retur full path.
1458 *
1459 * @param string $dir
1460 * @param string $name
1461 *
1462 * @return string
1463 * @author Dmitry (dio) Levashov
1464 **/
1465 protected function _joinPath($dir, $name)
1466 {
1467 return $this->_normpath($dir . '/' . str_replace('/', '\\/', $name));
1468 }
1469
1470 /**
1471 * Return normalized path, this works the same as os.path.normpath() in Python.
1472 *
1473 * @param string $path path
1474 *
1475 * @return string
1476 * @author Troex Nevelin
1477 **/
1478 protected function _normpath($path)
1479 {
1480 if (DIRECTORY_SEPARATOR !== '/') {
1481 $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
1482 }
1483 $path = '/' . ltrim($path, '/');
1484
1485 return $path;
1486 }
1487
1488 /**
1489 * Return file path related to root dir.
1490 *
1491 * @param string $path file path
1492 *
1493 * @return string
1494 * @author Dmitry (dio) Levashov
1495 **/
1496 protected function _relpath($path)
1497 {
1498 return $path;
1499 }
1500
1501 /**
1502 * Convert path related to root dir into real path.
1503 *
1504 * @param string $path file path
1505 *
1506 * @return string
1507 * @author Dmitry (dio) Levashov
1508 **/
1509 protected function _abspath($path)
1510 {
1511 return $path;
1512 }
1513
1514 /**
1515 * Return fake path started from root dir.
1516 *
1517 * @param string $path file path
1518 *
1519 * @return string
1520 * @author Dmitry (dio) Levashov
1521 **/
1522 protected function _path($path)
1523 {
1524 if (!$this->names) {
1525 $this->_gd_getDirectoryData();
1526 }
1527 $path = $this->_normpath(substr($path, strlen($this->root)));
1528 $names = [];
1529 $paths = explode('/', $path);
1530 foreach ($paths as $_p) {
1531 $names[] = isset($this->names[$_p]) ? $this->names[$_p] : $_p;
1532 }
1533
1534 return $this->rootName . implode('/', $names);
1535 }
1536
1537 /**
1538 * Return true if $path is children of $parent.
1539 *
1540 * @param string $path path to check
1541 * @param string $parent parent path
1542 *
1543 * @return bool
1544 * @author Dmitry (dio) Levashov
1545 **/
1546 protected function _inpath($path, $parent)
1547 {
1548 return $path == $parent || strpos($path, $parent . '/') === 0;
1549 }
1550
1551 /***************** file stat ********************/
1552 /**
1553 * Return stat for given path.
1554 * Stat contains following fields:
1555 * - (int) size file size in b. required
1556 * - (int) ts file modification time in unix time. required
1557 * - (string) mime mimetype. required for folders, others - optionally
1558 * - (bool) read read permissions. required
1559 * - (bool) write write permissions. required
1560 * - (bool) locked is object locked. optionally
1561 * - (bool) hidden is object hidden. optionally
1562 * - (string) alias for symlinks - link target path relative to root path. optionally
1563 * - (string) target for symlinks - link target path. optionally.
1564 * If file does not exists - returns empty array or false.
1565 *
1566 * @param string $path file path
1567 *
1568 * @return array|false
1569 * @author Dmitry (dio) Levashov
1570 **/
1571 protected function _stat($path)
1572 {
1573 if ($raw = $this->_gd_getFile($path)) {
1574 $stat = $this->_gd_parseRaw($raw);
1575 if ($path === $this->root) {
1576 $stat['expires'] = $this->expires;
1577 }
1578 return $stat;
1579 }
1580
1581 return false;
1582 }
1583
1584 /**
1585 * Return true if path is dir and has at least one childs directory.
1586 *
1587 * @param string $path dir path
1588 *
1589 * @return bool
1590 * @author Dmitry (dio) Levashov
1591 **/
1592 protected function _subdirs($path)
1593 {
1594 if ($this->directories === null) {
1595 $this->_gd_getDirectoryData();
1596 }
1597 list(, $itemId) = $this->_gd_splitPath($path);
1598
1599 return isset($this->directories[$itemId]);
1600 }
1601
1602 /**
1603 * Return object width and height
1604 * Ususaly used for images, but can be realize for video etc...
1605 *
1606 * @param string $path file path
1607 * @param string $mime file mime type
1608 *
1609 * @return string
1610 * @throws ImagickException
1611 * @throws elFinderAbortException
1612 * @author Dmitry (dio) Levashov
1613 */
1614 protected function _dimensions($path, $mime)
1615 {
1616 if (strpos($mime, 'image') !== 0) {
1617 return '';
1618 }
1619 $ret = '';
1620
1621 if ($file = $this->_gd_getFile($path)) {
1622 if (isset($file['imageMediaMetadata'])) {
1623 $ret = array('dim' => $file['imageMediaMetadata']['width'] . 'x' . $file['imageMediaMetadata']['height']);
1624 if (func_num_args() > 2) {
1625 $args = func_get_arg(2);
1626 } else {
1627 $args = array();
1628 }
1629 if (!empty($args['substitute'])) {
1630 $tmbSize = intval($args['substitute']);
1631 $srcSize = explode('x', $ret['dim']);
1632 if ($srcSize[0] && $srcSize[1]) {
1633 if (min(($tmbSize / $srcSize[0]), ($tmbSize / $srcSize[1])) < 1) {
1634 if ($this->_gd_isPublished($file)) {
1635 $tmbSize = strval($tmbSize);
1636 $ret['url'] = 'https://drive.google.com/thumbnail?authuser=0&sz=s' . $tmbSize . '&id=' . $file['id'];
1637 } elseif ($subImgLink = $this->getSubstituteImgLink(elFinder::$currentArgs['target'], $srcSize)) {
1638 $ret['url'] = $subImgLink;
1639 }
1640 }
1641 }
1642 }
1643 }
1644 }
1645
1646 return $ret;
1647 }
1648
1649 /******************** file/dir content *********************/
1650
1651 /**
1652 * Return files list in directory.
1653 *
1654 * @param string $path dir path
1655 *
1656 * @return array
1657 * @author Dmitry (dio) Levashov
1658 * @author Cem (DiscoFever)
1659 **/
1660 protected function _scandir($path)
1661 {
1662 return isset($this->dirsCache[$path])
1663 ? $this->dirsCache[$path]
1664 : $this->cacheDir($path);
1665 }
1666
1667 /**
1668 * Open file and return file pointer.
1669 *
1670 * @param string $path file path
1671 * @param bool $write open file for writing
1672 *
1673 * @return resource|false
1674 * @author Dmitry (dio) Levashov
1675 **/
1676 protected function _fopen($path, $mode = 'rb')
1677 {
1678 if ($mode === 'rb' || $mode === 'r') {
1679 if ($file = $this->_gd_getFile($path)) {
1680 if ($dlurl = $this->_gd_getDownloadUrl($file)) {
1681 $token = $this->client->getAccessToken();
1682 if (!$token && $this->client->isUsingApplicationDefaultCredentials()) {
1683 $this->client->fetchAccessTokenWithAssertion();
1684 $token = $this->client->getAccessToken();
1685 }
1686 $access_token = '';
1687 if (is_array($token)) {
1688 $access_token = $token['access_token'];
1689 } else {
1690 if ($token = json_decode($this->client->getAccessToken())) {
1691 $access_token = $token->access_token;
1692 }
1693 }
1694 if ($access_token) {
1695 $data = array(
1696 'target' => $dlurl,
1697 'headers' => array('Authorization: Bearer ' . $access_token),
1698 );
1699
1700 // to support range request
1701 if (func_num_args() > 2) {
1702 $opts = func_get_arg(2);
1703 } else {
1704 $opts = array();
1705 }
1706 if (!empty($opts['httpheaders'])) {
1707 $data['headers'] = array_merge($opts['httpheaders'], $data['headers']);
1708 }
1709
1710 return elFinder::getStreamByUrl($data);
1711 }
1712 }
1713 }
1714 }
1715
1716 return false;
1717 }
1718
1719 /**
1720 * Close opened file.
1721 *
1722 * @param resource $fp file pointer
1723 *
1724 * @return bool
1725 * @author Dmitry (dio) Levashov
1726 **/
1727 protected function _fclose($fp, $path = '')
1728 {
1729 is_resource($fp) && fclose($fp);
1730 if ($path) {
1731 unlink($this->getTempFile($path));
1732 }
1733 }
1734
1735 /******************** file/dir manipulations *************************/
1736
1737 /**
1738 * Create dir and return created dir path or false on failed.
1739 *
1740 * @param string $path parent dir path
1741 * @param string $name new directory name
1742 *
1743 * @return string|bool
1744 * @author Dmitry (dio) Levashov
1745 **/
1746 protected function _mkdir($path, $name)
1747 {
1748 $path = $this->_joinPath($path, $name);
1749 list($parentId, , $parent) = $this->_gd_splitPath($path);
1750
1751 try {
1752 $file = new \Google_Service_Drive_DriveFile();
1753
1754 $file->setName($name);
1755 $file->setMimeType(self::DIRMIME);
1756 $file->setParents([$parentId]);
1757
1758 //create the Folder in the Parent
1759 $obj = $this->service->files->create($file);
1760
1761 if ($obj instanceof Google_Service_Drive_DriveFile) {
1762 $path = $this->_joinPath($parent, $obj['id']);
1763 $this->_gd_getDirectoryData(false);
1764
1765 return $path;
1766 } else {
1767 return false;
1768 }
1769 } catch (Exception $e) {
1770 return $this->setError('GoogleDrive error: ' . $e->getMessage());
1771 }
1772 }
1773
1774 /**
1775 * Create file and return it's path or false on failed.
1776 *
1777 * @param string $path parent dir path
1778 * @param string $name new file name
1779 *
1780 * @return string|bool
1781 * @author Dmitry (dio) Levashov
1782 **/
1783 protected function _mkfile($path, $name)
1784 {
1785 return $this->_save($this->tmpfile(), $path, $name, []);
1786 }
1787
1788 /**
1789 * Create symlink. FTP driver does not support symlinks.
1790 *
1791 * @param string $target link target
1792 * @param string $path symlink path
1793 *
1794 * @return bool
1795 * @author Dmitry (dio) Levashov
1796 **/
1797 protected function _symlink($target, $path, $name)
1798 {
1799 return false;
1800 }
1801
1802 /**
1803 * Copy file into another file.
1804 *
1805 * @param string $source source file path
1806 * @param string $targetDir target directory path
1807 * @param string $name new file name
1808 *
1809 * @return bool
1810 * @author Dmitry (dio) Levashov
1811 **/
1812 protected function _copy($source, $targetDir, $name)
1813 {
1814 $source = $this->_normpath($source);
1815 $targetDir = $this->_normpath($targetDir);
1816
1817 try {
1818 $file = new \Google_Service_Drive_DriveFile();
1819 $file->setName($name);
1820
1821 //Set the Parent id
1822 list(, $parentId) = $this->_gd_splitPath($targetDir);
1823 $file->setParents([$parentId]);
1824
1825 list(, $srcId) = $this->_gd_splitPath($source);
1826 $file = $this->service->files->copy($srcId, $file, ['fields' => self::FETCHFIELDS_GET]);
1827 $itemId = $file->id;
1828
1829 return $itemId;
1830 } catch (Exception $e) {
1831 return $this->setError('GoogleDrive error: ' . $e->getMessage());
1832 }
1833
1834 return true;
1835 }
1836
1837 /**
1838 * Move file into another parent dir.
1839 * Return new file path or false.
1840 *
1841 * @param string $source source file path
1842 * @param string $target target dir path
1843 * @param string $name file name
1844 *
1845 * @return string|bool
1846 * @author Dmitry (dio) Levashov
1847 **/
1848 protected function _move($source, $targetDir, $name)
1849 {
1850 list($removeParents, $itemId) = $this->_gd_splitPath($source);
1851 $target = $this->_normpath($targetDir . '/' . $itemId);
1852 try {
1853 //moving and renaming a file or directory
1854 $files = new \Google_Service_Drive_DriveFile();
1855 $files->setName($name);
1856
1857 //Set new Parent and remove old parent
1858 list(, $addParents) = $this->_gd_splitPath($targetDir);
1859 $opts = ['addParents' => $addParents, 'removeParents' => $removeParents];
1860
1861 $file = $this->service->files->update($itemId, $files, $opts);
1862
1863 if ($file->getMimeType() === self::DIRMIME) {
1864 $this->_gd_getDirectoryData(false);
1865 }
1866 } catch (Exception $e) {
1867 return $this->setError('GoogleDrive error: ' . $e->getMessage());
1868 }
1869
1870 return $target;
1871 }
1872
1873 /**
1874 * Remove file.
1875 *
1876 * @param string $path file path
1877 *
1878 * @return bool
1879 * @author Dmitry (dio) Levashov
1880 **/
1881 protected function _unlink($path)
1882 {
1883 try {
1884 $files = new \Google_Service_Drive_DriveFile();
1885 $files->setTrashed(true);
1886
1887 list($pid, $itemId) = $this->_gd_splitPath($path);
1888 $opts = ['removeParents' => $pid];
1889 $this->service->files->update($itemId, $files, $opts);
1890 } catch (Exception $e) {
1891 return $this->setError('GoogleDrive error: ' . $e->getMessage());
1892 }
1893
1894 return true;
1895 }
1896
1897 /**
1898 * Remove dir.
1899 *
1900 * @param string $path dir path
1901 *
1902 * @return bool
1903 * @author Dmitry (dio) Levashov
1904 **/
1905 protected function _rmdir($path)
1906 {
1907 $res = $this->_unlink($path);
1908 $res && $this->_gd_getDirectoryData(false);
1909
1910 return $res;
1911 }
1912
1913 /**
1914 * Create new file and write into it from file pointer.
1915 * Return new file path or false on error.
1916 *
1917 * @param resource $fp file pointer
1918 * @param $path
1919 * @param string $name file name
1920 * @param array $stat file stat (required by some virtual fs)
1921 *
1922 * @return bool|string
1923 * @author Dmitry (dio) Levashov
1924 */
1925 protected function _save($fp, $path, $name, $stat)
1926 {
1927 if ($name !== '') {
1928 $path .= '/' . str_replace('/', '\\/', $name);
1929 }
1930 list($parentId, $itemId, $parent) = $this->_gd_splitPath($path);
1931 if ($name === '') {
1932 $stat['iid'] = $itemId;
1933 }
1934
1935 if (!$stat || empty($stat['iid'])) {
1936 $opts = [
1937 'q' => sprintf('trashed=false and "%s" in parents and name="%s"', $parentId, $name),
1938 'fields' => self::FETCHFIELDS_LIST,
1939 ];
1940 $srcFile = $this->_gd_query($opts);
1941 $srcFile = empty($srcFile) ? null : $srcFile[0];
1942 } else {
1943 $srcFile = $this->_gd_getFile($path);
1944 }
1945
1946 try {
1947 $mode = 'update';
1948 $mime = isset($stat['mime']) ? $stat['mime'] : '';
1949
1950 $file = new Google_Service_Drive_DriveFile();
1951 if ($srcFile) {
1952 $mime = $srcFile->getMimeType();
1953 } else {
1954 $mode = 'insert';
1955 $file->setName($name);
1956 $file->setParents([
1957 $parentId,
1958 ]);
1959 }
1960
1961 if (!$mime) {
1962 $mime = self::mimetypeInternalDetect($name);
1963 }
1964 if ($mime === 'unknown') {
1965 $mime = 'application/octet-stream';
1966 }
1967 $file->setMimeType($mime);
1968
1969 $size = 0;
1970 if (isset($stat['size'])) {
1971 $size = $stat['size'];
1972 } else {
1973 $fstat = fstat($fp);
1974 if (!empty($fstat['size'])) {
1975 $size = $fstat['size'];
1976 }
1977 }
1978
1979 // set chunk size (max: 100MB)
1980 $chunkSizeBytes = 100 * 1024 * 1024;
1981 if ($size > 0) {
1982 $memory = elFinder::getIniBytes('memory_limit');
1983 if ($memory > 0) {
1984 $chunkSizeBytes = max(262144, min([$chunkSizeBytes, (intval($memory / 4 / 256) * 256)]));
1985 }
1986 }
1987
1988 if ($size > $chunkSizeBytes) {
1989 $client = $this->client;
1990 // Call the API with the media upload, defer so it doesn't immediately return.
1991 $client->setDefer(true);
1992 if ($mode === 'insert') {
1993 $request = $this->service->files->create($file, [
1994 'fields' => self::FETCHFIELDS_GET,
1995 ]);
1996 } else {
1997 $request = $this->service->files->update($srcFile->getId(), $file, [
1998 'fields' => self::FETCHFIELDS_GET,
1999 ]);
2000 }
2001
2002 // Create a media file upload to represent our upload process.
2003 $media = new Google_Http_MediaFileUpload($client, $request, $mime, null, true, $chunkSizeBytes);
2004 $media->setFileSize($size);
2005 // Upload the various chunks. $status will be false until the process is
2006 // complete.
2007 $status = false;
2008 while (!$status && !feof($fp)) {
2009 elFinder::checkAborted();
2010 // read until you get $chunkSizeBytes from TESTFILE
2011 // fread will never return more than 8192 bytes if the stream is read buffered and it does not represent a plain file
2012 // An example of a read buffered file is when reading from a URL
2013 $chunk = $this->_gd_readFileChunk($fp, $chunkSizeBytes);
2014 $status = $media->nextChunk($chunk);
2015 }
2016 // The final value of $status will be the data from the API for the object
2017 // that has been uploaded.
2018 if ($status !== false) {
2019 $obj = $status;
2020 }
2021
2022 $client->setDefer(false);
2023 } else {
2024 $params = [
2025 'data' => stream_get_contents($fp),
2026 'uploadType' => 'media',
2027 'fields' => self::FETCHFIELDS_GET,
2028 ];
2029 if ($mode === 'insert') {
2030 $obj = $this->service->files->create($file, $params);
2031 } else {
2032 $obj = $this->service->files->update($srcFile->getId(), $file, $params);
2033 }
2034 }
2035 if ($obj instanceof Google_Service_Drive_DriveFile) {
2036 return $this->_joinPath($parent, $obj->getId());
2037 } else {
2038 return false;
2039 }
2040 } catch (Exception $e) {
2041 return $this->setError('GoogleDrive error: ' . $e->getMessage());
2042 }
2043 }
2044
2045 /**
2046 * Get file contents.
2047 *
2048 * @param string $path file path
2049 *
2050 * @return string|false
2051 * @author Dmitry (dio) Levashov
2052 **/
2053 protected function _getContents($path)
2054 {
2055 $contents = '';
2056
2057 try {
2058 list(, $itemId) = $this->_gd_splitPath($path);
2059
2060 $contents = $this->service->files->get($itemId, [
2061 'alt' => 'media',
2062 ]);
2063 $contents = (string)$contents->getBody();
2064 } catch (Exception $e) {
2065 return $this->setError('GoogleDrive error: ' . $e->getMessage());
2066 }
2067
2068 return $contents;
2069 }
2070
2071 /**
2072 * Write a string to a file.
2073 *
2074 * @param string $path file path
2075 * @param string $content new file content
2076 *
2077 * @return bool
2078 * @author Dmitry (dio) Levashov
2079 **/
2080 protected function _filePutContents($path, $content)
2081 {
2082 $res = false;
2083
2084 if ($local = $this->getTempFile($path)) {
2085 if (file_put_contents($local, $content, LOCK_EX) !== false
2086 && ($fp = fopen($local, 'rb'))) {
2087 clearstatcache();
2088 $res = $this->_save($fp, $path, '', []);
2089 fclose($fp);
2090 }
2091 file_exists($local) && unlink($local);
2092 }
2093
2094 return $res;
2095 }
2096
2097 /**
2098 * Detect available archivers.
2099 **/
2100 protected function _checkArchivers()
2101 {
2102 // die('Not yet implemented. (_checkArchivers)');
2103 return [];
2104 }
2105
2106 /**
2107 * chmod implementation.
2108 *
2109 * @return bool
2110 **/
2111 protected function _chmod($path, $mode)
2112 {
2113 return false;
2114 }
2115
2116 /**
2117 * Unpack archive.
2118 *
2119 * @param string $path archive path
2120 * @param array $arc archiver command and arguments (same as in $this->archivers)
2121 *
2122 * @return void
2123 * @author Dmitry (dio) Levashov
2124 * @author Alexey Sukhotin
2125 */
2126 protected function _unpack($path, $arc)
2127 {
2128 die('Not yet implemented. (_unpack)');
2129 //return false;
2130 }
2131
2132 /**
2133 * Extract files from archive.
2134 *
2135 * @param string $path archive path
2136 * @param array $arc archiver command and arguments (same as in $this->archivers)
2137 *
2138 * @return void
2139 * @author Dmitry (dio) Levashov,
2140 * @author Alexey Sukhotin
2141 */
2142 protected function _extract($path, $arc)
2143 {
2144 die('Not yet implemented. (_extract)');
2145 }
2146
2147 /**
2148 * Create archive and return its path.
2149 *
2150 * @param string $dir target dir
2151 * @param array $files files names list
2152 * @param string $name archive name
2153 * @param array $arc archiver options
2154 *
2155 * @return string|bool
2156 * @author Dmitry (dio) Levashov,
2157 * @author Alexey Sukhotin
2158 **/
2159 protected function _archive($dir, $files, $name, $arc)
2160 {
2161 die('Not yet implemented. (_archive)');
2162 }
2163 } // END class
2164