PluginProbe ʕ •ᴥ•ʔ
Advanced File Manager – Ultimate File Manager for WordPress And Document Library Solution / trunk
Advanced File Manager – Ultimate File Manager for WordPress And Document Library Solution vtrunk
trunk 4.1.5 4.1.6 5.0 5.2.13 5.2.14 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.4.0 5.4.1 5.4.10 5.4.11 5.4.12 5.4.2 5.4.3 5.4.4 5.4.5 5.4.6 5.4.7 5.4.8
file-manager-advanced / application / library / php / elFinderVolumeGoogleDrive.class.php
file-manager-advanced / application / library / php Last commit date
.tmp 10 months ago editors 4 weeks ago libs 7 years ago plugins 3 years ago resources 7 years ago MySQLStorage.sql 1 year ago autoload.php 10 months ago connector.maximal.php-dist 1 year ago connector.minimal.php-dist 1 year ago elFinder.class.php 4 weeks ago elFinderConnector.class.php 4 weeks ago elFinderFlysystemGoogleDriveNetmount.php 5 years ago elFinderPlugin.php 7 years ago elFinderSession.php 4 years ago elFinderSessionInterface.php 7 years ago elFinderVolumeBox.class.php 5 years ago elFinderVolumeDriver.class.php 4 weeks ago elFinderVolumeDropbox.class.php 1 year ago elFinderVolumeDropbox2.class.php 4 weeks ago elFinderVolumeFTP.class.php 5 years ago elFinderVolumeGoogleDrive.class.php 5 years ago elFinderVolumeGroup.class.php 7 years ago elFinderVolumeLocalFileSystem.class.php 2 years ago elFinderVolumeMySQL.class.php 10 months ago elFinderVolumeOneDrive.class.php 4 weeks ago elFinderVolumeSFTPphpseclib.class.php 4 weeks ago elFinderVolumeTrash.class.php 7 years ago elFinderVolumeTrashMySQL.class.php 7 years ago mime.types 3 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