Issues (1107)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/elFinderVolumeGoogleDrive.class.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1955
        return [];
1956
    }
1957
1958
    /**
1959
     * chmod implementation.
1960
     *
1961
     * @return bool
1962
     **/
1963
    protected function _chmod($path, $mode)
1964
    {
1965
        return false;
1966
    }
1967
1968
    /**
1969
     * Unpack archive.
1970
     *
1971
     * @param string $path archive path
1972
     * @param array  $arc  archiver command and arguments (same as in $this->archivers)
1973
     *
1974
     * @return true
1975
     *
1976
     * @author Dmitry (dio) Levashov
1977
     * @author Alexey Sukhotin
1978
     **/
1979
    protected function _unpack($path, $arc)
1980
    {
1981
        die('Not yet implemented. (_unpack)');
1982
        //return false;
1983
    }
1984
1985
    /**
1986
     * Recursive symlinks search.
1987
     *
1988
     * @param string $path file/dir path
1989
     *
1990
     * @return bool
1991
     *
1992
     * @author Dmitry (dio) Levashov
1993
     **/
1994
    protected function _findSymlinks($path)
1995
    {
1996
        die('Not yet implemented. (_findSymlinks)');
1997
    }
1998
1999
    /**
2000
     * Extract files from archive.
2001
     *
2002
     * @param string $path archive path
2003
     * @param array  $arc  archiver command and arguments (same as in $this->archivers)
2004
     *
2005
     * @return true
2006
     *
2007
     * @author Dmitry (dio) Levashov,
2008
     * @author Alexey Sukhotin
2009
     **/
2010
    protected function _extract($path, $arc)
2011
    {
2012
        die('Not yet implemented. (_extract)');
2013
    }
2014
2015
    /**
2016
     * Create archive and return its path.
2017
     *
2018
     * @param string $dir   target dir
2019
     * @param array  $files files names list
2020
     * @param string $name  archive name
2021
     * @param array  $arc   archiver options
2022
     *
2023
     * @return string|bool
2024
     *
2025
     * @author Dmitry (dio) Levashov,
2026
     * @author Alexey Sukhotin
2027
     **/
2028
    protected function _archive($dir, $files, $name, $arc)
2029
    {
2030
        die('Not yet implemented. (_archive)');
2031
    }
2032
2033
    /**
2034
     * Drive query and fetchAll.
2035
     *
2036
     * @param string $sql
2037
     *
2038
     * @return bool|array
2039
     */
2040
    private function _gd_query($opts)
2041
    {
2042
        $result = [];
2043
        $pageToken = null;
2044
        $parameters = [
2045
                'fields' => self::FETCHFIELDS_LIST,
2046
                'pageSize' => 1000,
2047
                'spaces' => 'drive',
2048
        ];
2049
2050
        if (is_array($opts)) {
2051
            $parameters = array_merge($parameters, $opts);
2052
        }
2053
        do {
2054
            try {
2055
                if ($pageToken) {
2056
                    $parameters['pageToken'] = $pageToken;
2057
                }
2058
                $files = $this->service->files->listFiles($parameters);
2059
2060
                $result = array_merge($result, $files->getFiles());
2061
                $pageToken = $files->getNextPageToken();
2062
            } catch (Exception $e) {
2063
                $pageToken = null;
2064
            }
2065
        } while ($pageToken);
2066
2067
        return $result;
2068
    }
2069
2070
    /**
2071
     * Get dat(googledrive metadata) from GoogleDrive.
2072
     *
2073
     * @param string $path
2074
     *
2075
     * @return array googledrive metadata
2076
     */
2077
    private function _gd_getFile($path, $fields = '')
2078
    {
2079
        list(, $itemId) = $this->_gd_splitPath($path);
2080
        if (! $fields) {
2081
            $fields = self::FETCHFIELDS_GET;
2082
        }
2083
        try {
2084
            $file = $this->service->files->get($itemId, ['fields' => $fields]);
2085
            if ($file instanceof Google_Service_Drive_DriveFile) {
2086
                return $file;
2087
            } else {
2088
                return [];
2089
            }
2090
        } catch (Exception $e) {
2091
            return [];
2092
        }
2093
    }
2094
2095
    /**
2096
     * Get dat(googledrive metadata) from GoogleDrive.
2097
     *
2098
     * @param string $path
2099
     *
2100
     * @return array googledrive metadata
2101
     */
2102
    private function _gd_getNameByPath($path)
2103
    {
2104
        list(, $itemId) = $this->_gd_splitPath($path);
2105
        if (! $this->names) {
2106
            $this->_gd_getDirectoryData();
2107
        }
2108
2109
        return isset($this->names[$itemId]) ? $this->names[$itemId] : '';
2110
    }
2111
} // END class
2112