PluginProbe ʕ •ᴥ•ʔ
File Manager Pro – Filester / 1.8
File Manager Pro – Filester v1.8
2.1.1 trunk 1.6.1 1.7.6 1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9 2.0 2.0.1 2.0.2 2.1.0
filester / includes / File_manager / lib / php / elFinderVolumeDropbox2.class.php
filester / includes / File_manager / lib / php Last commit date
.tmp 2 years ago editors 2 years ago libs 2 years ago plugins 2 years ago resources 2 years ago MySQLStorage.sql 2 years ago autoload.php 2 years ago elFinder.class.php 2 years ago elFinderConnector.class.php 2 years ago elFinderFlysystemGoogleDriveNetmount.php 2 years ago elFinderPlugin.php 2 years ago elFinderSession.php 2 years ago elFinderSessionInterface.php 2 years ago elFinderVolumeBox.class.php 2 years ago elFinderVolumeDriver.class.php 2 years ago elFinderVolumeDropbox.class.php 2 years ago elFinderVolumeDropbox2.class.php 2 years ago elFinderVolumeFTP.class.php 2 years ago elFinderVolumeGoogleDrive.class.php 2 years ago elFinderVolumeGroup.class.php 2 years ago elFinderVolumeLocalFileSystem.class.php 2 years ago elFinderVolumeMySQL.class.php 2 years ago elFinderVolumeOneDrive.class.php 2 years ago elFinderVolumeSFTPphpseclib.class.php 2 years ago elFinderVolumeTrash.class.php 2 years ago elFinderVolumeTrashMySQL.class.php 2 years ago index.php 2 years ago mime.types 2 years ago
elFinderVolumeDropbox2.class.php
1517 lines
1 <?php
2
3 use Kunnu\Dropbox\Dropbox;
4 use Kunnu\Dropbox\DropboxApp;
5 use Kunnu\Dropbox\DropboxFile;
6 use Kunnu\Dropbox\Exceptions\DropboxClientException;
7 use Kunnu\Dropbox\Models\FileMetadata;
8 use Kunnu\Dropbox\Models\FolderMetadata;
9
10 /**
11 * Simple elFinder driver for Dropbox
12 * kunalvarma05/dropbox-php-sdk:0.1.5 or above.
13 *
14 * @author Naoki Sawada
15 **/
16 class elFinderVolumeDropbox2 extends elFinderVolumeDriver
17 {
18 /**
19 * Driver id
20 * Must be started from letter and contains [a-z0-9]
21 * Used as part of volume id.
22 *
23 * @var string
24 **/
25 protected $driverId = 'db';
26
27 /**
28 * Dropbox service object.
29 *
30 * @var object
31 **/
32 protected $service = null;
33
34 /**
35 * Fetch options.
36 *
37 * @var string
38 */
39 private $FETCH_OPTIONS = [];
40
41 /**
42 * Directory for tmp files
43 * If not set driver will try to use tmbDir as tmpDir.
44 *
45 * @var string
46 **/
47 protected $tmp = '';
48
49 /**
50 * Net mount key.
51 *
52 * @var string
53 **/
54 public $netMountKey = '';
55
56 /**
57 * Constructor
58 * Extend options with required fields.
59 *
60 * @author Naoki Sawada
61 **/
62 public function __construct()
63 {
64 $opts = [
65 'app_key' => '',
66 'app_secret' => '',
67 'access_token' => '',
68 'aliasFormat' => '%s@Dropbox',
69 'path' => '/',
70 'separator' => '/',
71 'acceptedName' => '#^[^\\\/]+$#',
72 'rootCssClass' => 'elfinder-navbar-root-dropbox',
73 'publishPermission' => [
74 'requested_visibility' => 'public',
75 //'link_password' => '',
76 //'expires' => '',
77 ],
78 'getThumbSize' => 'medium', // Available sizes: 'thumb', 'small', 'medium', 'large', 'huge'
79 ];
80 $this->options = array_merge($this->options, $opts);
81 $this->options['mimeDetect'] = 'internal';
82 }
83
84 /*********************************************************************/
85 /* ORIGINAL FUNCTIONS */
86 /*********************************************************************/
87
88 /**
89 * Get Parent ID, Item ID, Parent Path as an array from path.
90 *
91 * @param string $path
92 *
93 * @return array
94 */
95 protected function _db_splitPath($path)
96 {
97 $path = trim($path, '/');
98 if ($path === '') {
99 $dirname = '/';
100 $basename = '';
101 } else {
102 $pos = strrpos($path, '/');
103 if ($pos === false) {
104 $dirname = '/';
105 $basename = $path;
106 } else {
107 $dirname = '/' . substr($path, 0, $pos);
108 $basename = substr($path, $pos + 1);
109 }
110 }
111
112 return [$dirname, $basename];
113 }
114
115 /**
116 * Get dat(Dropbox metadata) from Dropbox.
117 *
118 * @param string $path
119 *
120 * @return boolean|object Dropbox metadata
121 */
122 private function _db_getFile($path)
123 {
124 if ($path === '/') {
125 return true;
126 }
127
128 $res = false;
129 try {
130 $file = $this->service->getMetadata($path, $this->FETCH_OPTIONS);
131 if ($file instanceof FolderMetadata || $file instanceof FileMetadata) {
132 $res = $file;
133 }
134
135 return $res;
136 } catch (DropboxClientException $e) {
137 return false;
138 }
139 }
140
141 /**
142 * Parse line from Dropbox metadata output and return file stat (array).
143 *
144 * @param object $raw line from ftp_rawlist() output
145 *
146 * @return array
147 * @author Naoki Sawada
148 **/
149 protected function _db_parseRaw($raw)
150 {
151 $stat = [];
152 $isFolder = false;
153 if ($raw === true) {
154 // root folder
155 $isFolder = true;
156 $stat['name'] = '';
157 $stat['iid'] = '0';
158 }
159
160 $data = [];
161 if (is_object($raw)) {
162 $isFolder = $raw instanceof FolderMetadata;
163 $data = $raw->getData();
164 } elseif (is_array($raw)) {
165 $isFolder = $raw['.tag'] === 'folder';
166 $data = $raw;
167 }
168
169 if (isset($data['path_lower'])) {
170 $stat['path'] = $data['path_lower'];
171 }
172
173 if (isset($data['name'])) {
174 $stat['name'] = $data['name'];
175 }
176
177 if (isset($data['id'])) {
178 $stat['iid'] = substr($data['id'], 3);
179 }
180
181 if ($isFolder) {
182 $stat['mime'] = 'directory';
183 $stat['size'] = 0;
184 $stat['ts'] = 0;
185 $stat['dirs'] = -1;
186 } else {
187 $stat['size'] = isset($data['size']) ? (int)$data['size'] : 0;
188 if (isset($data['server_modified'])) {
189 $stat['ts'] = strtotime($data['server_modified']);
190 } elseif (isset($data['client_modified'])) {
191 $stat['ts'] = strtotime($data['client_modified']);
192 } else {
193 $stat['ts'] = 0;
194 }
195 $stat['url'] = '1';
196 }
197
198 return $stat;
199 }
200
201 /**
202 * Get thumbnail from Dropbox.
203 *
204 * @param string $path
205 * @param string $size
206 *
207 * @return string | boolean
208 */
209 protected function _db_getThumbnail($path)
210 {
211 try {
212 return $this->service->getThumbnail($path, $this->options['getThumbSize'])->getContents();
213 } catch (DropboxClientException $e) {
214 return false;
215 }
216 }
217
218 /**
219 * Join dir name and file name(display name) and retur full path.
220 *
221 * @param string $dir
222 * @param string $displayName
223 *
224 * @return string
225 */
226 protected function _db_joinName($dir, $displayName)
227 {
228 return rtrim($dir, '/') . '/' . $displayName;
229 }
230
231 /**
232 * Get OAuth2 access token form OAuth1 tokens.
233 *
234 * @param string $app_key
235 * @param string $app_secret
236 * @param string $oauth1_token
237 * @param string $oauth1_secret
238 *
239 * @return string|false
240 */
241 public static function getTokenFromOauth1($app_key, $app_secret, $oauth1_token, $oauth1_secret)
242 {
243 $data = [
244 'oauth1_token' => $oauth1_token,
245 'oauth1_token_secret' => $oauth1_secret,
246 ];
247 $auth = base64_encode($app_key . ':' . $app_secret);
248
249 $ch = curl_init('https://api.dropboxapi.com/2/auth/token/from_oauth1');
250 curl_setopt($ch, CURLOPT_POST, true);
251 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
252 curl_setopt($ch, CURLOPT_HTTPHEADER, [
253 'Content-Type: application/json',
254 'Authorization: Basic ' . $auth,
255 ]);
256 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
257 $result = curl_exec($ch);
258 curl_close($ch);
259
260 $res = $result ? json_decode($result, true) : [];
261
262 return isset($res['oauth2_token']) ? $res['oauth2_token'] : false;
263 }
264
265 /*********************************************************************/
266 /* EXTENDED FUNCTIONS */
267 /*********************************************************************/
268
269 /**
270 * Prepare
271 * Call from elFinder::netmout() before volume->mount().
272 *
273 * @return array
274 * @author Naoki Sawada
275 **/
276 public function netmountPrepare($options)
277 {
278 if (empty($options['app_key']) && defined('ELFINDER_DROPBOX_APPKEY')) {
279 $options['app_key'] = ELFINDER_DROPBOX_APPKEY;
280 }
281 if (empty($options['app_secret']) && defined('ELFINDER_DROPBOX_APPSECRET')) {
282 $options['app_secret'] = ELFINDER_DROPBOX_APPSECRET;
283 }
284
285 if (!isset($options['pass'])) {
286 $options['pass'] = '';
287 }
288
289 try {
290 $app = new DropboxApp($options['app_key'], $options['app_secret']);
291 $dropbox = new Dropbox($app);
292 $authHelper = $dropbox->getAuthHelper();
293
294 if ($options['pass'] === 'reauth') {
295 $options['pass'] = '';
296 $this->session->set('Dropbox2AuthParams', [])->set('Dropbox2Tokens', []);
297 } elseif ($options['pass'] === 'dropbox2') {
298 $options['pass'] = '';
299 }
300
301 $options = array_merge($this->session->get('Dropbox2AuthParams', []), $options);
302
303 if (!isset($options['tokens'])) {
304 $options['tokens'] = $this->session->get('Dropbox2Tokens', []);
305 $this->session->remove('Dropbox2Tokens');
306 }
307 $aToken = $options['tokens'];
308 if (!is_array($aToken) || !isset($aToken['access_token'])) {
309 $aToken = [];
310 }
311
312 $service = null;
313 if ($aToken) {
314 try {
315 $dropbox->setAccessToken($aToken['access_token']);
316 $this->session->set('Dropbox2AuthParams', $options);
317 } catch (DropboxClientException $e) {
318 $aToken = [];
319 $options['tokens'] = [];
320 if ($options['user'] !== 'init') {
321 $this->session->set('Dropbox2AuthParams', $options);
322
323 return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
324 }
325 }
326 }
327
328 if ((isset($options['user']) && $options['user'] === 'init') || (isset($_GET['host']) && $_GET['host'] == '1')) {
329 if (empty($options['url'])) {
330 $options['url'] = elFinder::getConnectorUrl();
331 }
332
333 if (!empty($options['id'])) {
334 $callback = $options['url']
335 . (strpos($options['url'], '?') !== false? '&' : '?') . 'cmd=netmount&protocol=dropbox2&host=' . ($options['id'] === 'elfinder'? '1' : $options['id']);
336 }
337
338 $itpCare = isset($options['code']);
339 $code = $itpCare? $options['code'] : (isset($_GET['code'])? $_GET['code'] : '');
340 $state = $itpCare? $options['state'] : (isset($_GET['state'])? $_GET['state'] : '');
341 if (!$aToken && empty($code)) {
342 $url = $authHelper->getAuthUrl($callback);
343
344 $html = '<input id="elf-volumedriver-dropbox2-host-btn" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" value="{msg:btnApprove}" type="button">';
345 $html .= '<script>
346 $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", {protocol: "dropbox2", mode: "makebtn", url: "' . $url . '"});
347 </script>';
348 if (empty($options['pass']) && $options['host'] !== '1') {
349 $options['pass'] = 'return';
350 $this->session->set('Dropbox2AuthParams', $options);
351
352 return ['exit' => true, 'body' => $html];
353 } else {
354 $out = [
355 'node' => $options['id'],
356 'json' => '{"protocol": "dropbox2", "mode": "makebtn", "body" : "' . str_replace($html, '"', '\\"') . '", "error" : "' . elFinder::ERROR_ACCESS_DENIED . '"}',
357 'bind' => 'netmount',
358 ];
359
360 return ['exit' => 'callback', 'out' => $out];
361 }
362 } else {
363 if ($code && $state) {
364 if (!empty($options['id'])) {
365 // see https://github.com/kunalvarma05/dropbox-php-sdk/issues/115
366 $authHelper->getPersistentDataStore()->set('state', filter_var($state, FILTER_SANITIZE_STRING));
367 $tokenObj = $authHelper->getAccessToken($code, $state, $callback);
368 $options['tokens'] = [
369 'access_token' => $tokenObj->getToken(),
370 'uid' => $tokenObj->getUid(),
371 ];
372 unset($options['code'], $options['state']);
373 $this->session->set('Dropbox2Tokens', $options['tokens'])->set('Dropbox2AuthParams', $options);
374 $out = [
375 'node' => $options['id'],
376 'json' => '{"protocol": "dropbox2", "mode": "done", "reset": 1}',
377 'bind' => 'netmount',
378 ];
379 } else {
380 $nodeid = ($_GET['host'] === '1')? 'elfinder' : $_GET['host'];
381 $out = [
382 'node' => $nodeid,
383 'json' => json_encode(array(
384 'protocol' => 'dropbox2',
385 'host' => $nodeid,
386 'mode' => 'redirect',
387 'options' => array(
388 'id' => $nodeid,
389 'code' => $code,
390 'state' => $state
391 )
392 )),
393 'bind' => 'netmount'
394 ];
395 }
396 if (!$itpCare) {
397 return array('exit' => 'callback', 'out' => $out);
398 } else {
399 return array('exit' => true, 'body' => $out['json']);
400 }
401 }
402 $path = $options['path'];
403 $folders = [];
404 $listFolderContents = $dropbox->listFolder($path);
405 $items = $listFolderContents->getItems();
406 foreach ($items as $item) {
407 $data = $item->getData();
408 if ($data['.tag'] === 'folder') {
409 $folders[$data['path_lower']] = $data['name'];
410 }
411 }
412 natcasesort($folders);
413
414 if ($options['pass'] === 'folders') {
415 return ['exit' => true, 'folders' => $folders];
416 }
417
418 $folders = ['/' => '/'] + $folders;
419 $folders = json_encode($folders);
420 $json = '{"protocol": "dropbox2", "mode": "done", "folders": ' . $folders . '}';
421 $options['pass'] = 'return';
422 $html = 'Dropbox.com';
423 $html .= '<script>
424 $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", ' . $json . ');
425 </script>';
426 $this->session->set('Dropbox2AuthParams', $options);
427
428 return ['exit' => true, 'body' => $html];
429 }
430 }
431 } catch (DropboxClientException $e) {
432 $this->session->remove('Dropbox2AuthParams')->remove('Dropbox2Tokens');
433 if (empty($options['pass'])) {
434 return ['exit' => true, 'body' => '{msg:' . elFinder::ERROR_ACCESS_DENIED . '}' . ' ' . $e->getMessage()];
435 } else {
436 return ['exit' => true, 'error' => [elFinder::ERROR_ACCESS_DENIED, $e->getMessage()]];
437 }
438 }
439
440 if (!$aToken) {
441 return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
442 }
443
444 if ($options['path'] === 'root') {
445 $options['path'] = '/';
446 }
447
448 try {
449 if ($options['path'] !== '/') {
450 $file = $dropbox->getMetadata($options['path']);
451 $name = $file->getName();
452 } else {
453 $name = 'root';
454 }
455 $options['alias'] = sprintf($this->options['aliasFormat'], $name);
456 } catch (DropboxClientException $e) {
457 return ['exit' => true, 'error' => $e->getMessage()];
458 }
459
460 foreach (['host', 'user', 'pass', 'id', 'offline'] as $key) {
461 unset($options[$key]);
462 }
463
464 return $options;
465 }
466
467 /**
468 * process of on netunmount
469 * Drop `Dropbox` & rm thumbs.
470 *
471 * @param array $options
472 *
473 * @return bool
474 */
475 public function netunmount($netVolumes, $key)
476 {
477 if ($tmbs = glob(rtrim($this->options['tmbPath'], '\\/') . DIRECTORY_SEPARATOR . $this->driverId . '_' . $this->options['tokens']['uid'] . '*.png')) {
478 foreach ($tmbs as $file) {
479 unlink($file);
480 }
481 }
482
483 return true;
484 }
485
486 /*********************************************************************/
487 /* INIT AND CONFIGURE */
488 /*********************************************************************/
489
490 /**
491 * Prepare Dropbox connection
492 * Connect to remote server and check if credentials are correct, if so, store the connection id in $this->service.
493 *
494 * @return bool
495 * @author Naoki Sawada
496 **/
497 protected function init()
498 {
499 if (empty($this->options['app_key'])) {
500 if (defined('ELFINDER_DROPBOX_APPKEY') && ELFINDER_DROPBOX_APPKEY) {
501 $this->options['app_key'] = ELFINDER_DROPBOX_APPKEY;
502 } else {
503 return $this->setError('Required option "app_key" is undefined.');
504 }
505 }
506 if (empty($this->options['app_secret'])) {
507 if (defined('ELFINDER_DROPBOX_APPSECRET') && ELFINDER_DROPBOX_APPSECRET) {
508 $this->options['app_secret'] = ELFINDER_DROPBOX_APPSECRET;
509 } else {
510 return $this->setError('Required option "app_secret" is undefined.');
511 }
512 }
513 if (isset($this->options['tokens']) && is_array($this->options['tokens']) && !empty($this->options['tokens']['access_token'])) {
514 $this->options['access_token'] = $this->options['tokens']['access_token'];
515 }
516 if (!$this->options['access_token']) {
517 return $this->setError('Required option "access_token" or "refresh_token" is undefined.');
518 }
519
520 try {
521 // make net mount key for network mount
522 $aToken = $this->options['access_token'];
523 $this->netMountKey = md5($aToken . '-' . $this->options['path']);
524
525 $errors = [];
526 if ($this->needOnline && !$this->service) {
527 $app = new DropboxApp($this->options['app_key'], $this->options['app_secret'], $aToken);
528 $this->service = new Dropbox($app);
529 // to check access_token
530 $this->service->getCurrentAccount();
531 }
532 } catch (DropboxClientException $e) {
533 $errors[] = 'Dropbox error: ' . $e->getMessage();
534 } catch (Exception $e) {
535 $errors[] = $e->getMessage();
536 }
537
538 if ($this->needOnline && !$this->service) {
539 $errors[] = 'Dropbox Service could not be loaded.';
540 }
541
542 if ($errors) {
543 return $this->setError($errors);
544 }
545
546 // normalize root path
547 $this->options['path'] = strtolower($this->options['path']);
548 if ($this->options['path'] == 'root') {
549 $this->options['path'] = '/';
550 }
551 $this->root = $this->options['path'] = $this->_normpath($this->options['path']);
552
553 if (empty($this->options['alias'])) {
554 $this->options['alias'] = sprintf($this->options['aliasFormat'], ($this->options['path'] === '/') ? 'Root' : $this->_basename($this->options['path']));
555 if (!empty($this->options['netkey'])) {
556 elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'alias', $this->options['alias']);
557 }
558 }
559
560 $this->rootName = $this->options['alias'];
561
562 if (!empty($this->options['tmpPath'])) {
563 if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'])) && is_writable($this->options['tmpPath'])) {
564 $this->tmp = $this->options['tmpPath'];
565 }
566 }
567
568 if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
569 $this->tmp = $tmp;
570 }
571
572 // This driver dose not support `syncChkAsTs`
573 $this->options['syncChkAsTs'] = false;
574
575 // 'lsPlSleep' minmum 10 sec
576 $this->options['lsPlSleep'] = max(10, $this->options['lsPlSleep']);
577
578 // enable command archive
579 $this->options['useRemoteArchive'] = true;
580
581 return true;
582 }
583
584 /**
585 * Configure after successfull mount.
586 *
587 * @author Naoki Sawada
588 * @throws elFinderAbortException
589 */
590 protected function configure()
591 {
592 parent::configure();
593
594 // fallback of $this->tmp
595 if (!$this->tmp && $this->tmbPathWritable) {
596 $this->tmp = $this->tmbPath;
597 }
598
599 if ($this->isMyReload()) {
600 //$this->_db_getDirectoryData(false);
601 }
602 }
603
604 /*********************************************************************/
605 /* FS API */
606 /*********************************************************************/
607
608 /**
609 * Close opened connection.
610 **/
611 public function umount()
612 {
613 }
614
615 /**
616 * Cache dir contents.
617 *
618 * @param string $path dir path
619 *
620 * @return
621 * @author Naoki Sawada
622 */
623 protected function cacheDir($path)
624 {
625 $this->dirsCache[$path] = [];
626 $hasDir = false;
627
628 $res = $this->service->listFolder($path, $this->FETCH_OPTIONS);
629
630 if ($res) {
631 $items = $res->getItems()->all();
632 foreach ($items as $raw) {
633 if ($stat = $this->_db_parseRaw($raw)) {
634 $mountPath = $this->_joinPath($path, $stat['name']);
635 $stat = $this->updateCache($mountPath, $stat);
636 if (empty($stat['hidden']) && $path !== $mountPath) {
637 if (!$hasDir && $stat['mime'] === 'directory') {
638 $hasDir = true;
639 }
640 $this->dirsCache[$path][] = $mountPath;
641 }
642 }
643 }
644 }
645
646 if (isset($this->sessionCache['subdirs'])) {
647 $this->sessionCache['subdirs'][$path] = $hasDir;
648 }
649
650 return $this->dirsCache[$path];
651 }
652
653 /**
654 * Recursive files search.
655 *
656 * @param string $path dir path
657 * @param string $q search string
658 * @param array $mimes
659 *
660 * @return array
661 * @throws elFinderAbortException
662 * @author Naoki Sawada
663 */
664 protected function doSearch($path, $q, $mimes)
665 {
666 if (!empty($this->doSearchCurrentQuery['matchMethod']) || $mimes) {
667 // has custom match method or mimes, use elFinderVolumeDriver::doSearch()
668 return parent::doSearch($path, $q, $mimes);
669 }
670
671 $timeout = $this->options['searchTimeout'] ? $this->searchStart + $this->options['searchTimeout'] : 0;
672
673 $searchRes = $this->service->search($path, $q, ['start' => 0, 'max_results' => 1000]);
674 $items = $searchRes->getItems();
675 $more = $searchRes->hasMoreItems();
676 while ($more) {
677 if ($timeout && $timeout < time()) {
678 $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->_path($path));
679 break;
680 }
681 $searchRes = $this->service->search($path, $q, ['start' => $searchRes->getCursor(), 'max_results' => 1000]);
682 $more = $searchRes->hasMoreItems();
683 $items = $items->merge($searchRes->getItems());
684 }
685
686 $result = [];
687 foreach ($items as $raw) {
688 if ($stat = $this->_db_parseRaw($raw->getMetadata())) {
689 $stat = $this->updateCache($stat['path'], $stat);
690 if (empty($stat['hidden'])) {
691 $result[] = $stat;
692 }
693 }
694 }
695
696 return $result;
697 }
698
699 /**
700 * Copy file/recursive copy dir only in current volume.
701 * Return new file path or false.
702 *
703 * @param string $src source path
704 * @param string $dst destination dir path
705 * @param string $name new file name (optionaly)
706 *
707 * @return string|false
708 * @throws elFinderAbortException
709 * @author Naoki Sawada
710 */
711 protected function copy($src, $dst, $name)
712 {
713 $srcStat = $this->stat($src);
714 $target = $this->_joinPath($dst, $name);
715 $tgtStat = $this->stat($target);
716 if ($tgtStat) {
717 if ($srcStat['mime'] === 'directory') {
718 return parent::copy($src, $dst, $name);
719 } else {
720 $this->_unlink($target);
721 }
722 }
723 $this->clearcache();
724 if ($res = $this->_copy($src, $dst, $name)) {
725 $this->added[] = $this->stat($target);
726 $res = $target;
727 }
728
729 return $res;
730 }
731
732 /**
733 * Remove file/ recursive remove dir.
734 *
735 * @param string $path file path
736 * @param bool $force try to remove even if file locked
737 * @param bool $recursive
738 *
739 * @return bool
740 * @throws elFinderAbortException
741 * @author Naoki Sawada
742 */
743 protected function remove($path, $force = false, $recursive = false)
744 {
745 $stat = $this->stat($path);
746 $stat['realpath'] = $path;
747 $this->rmTmb($stat);
748 $this->clearcache();
749
750 if (empty($stat)) {
751 return $this->setError(elFinder::ERROR_RM, $this->_path($path), elFinder::ERROR_FILE_NOT_FOUND);
752 }
753
754 if (!$force && !empty($stat['locked'])) {
755 return $this->setError(elFinder::ERROR_LOCKED, $this->_path($path));
756 }
757
758 if ($stat['mime'] == 'directory') {
759 if (!$recursive && !$this->_rmdir($path)) {
760 return $this->setError(elFinder::ERROR_RM, $this->_path($path));
761 }
762 } else {
763 if (!$recursive && !$this->_unlink($path)) {
764 return $this->setError(elFinder::ERROR_RM, $this->_path($path));
765 }
766 }
767
768 $this->removed[] = $stat;
769
770 return true;
771 }
772
773 /**
774 * Create thumnbnail and return it's URL on success.
775 *
776 * @param string $path file path
777 * @param $stat
778 *
779 * @return string|false
780 * @throws ImagickException
781 * @throws elFinderAbortException
782 * @author Naoki Sawada
783 */
784 protected function createTmb($path, $stat)
785 {
786 if (!$stat || !$this->canCreateTmb($path, $stat)) {
787 return false;
788 }
789
790 $name = $this->tmbname($stat);
791 $tmb = $this->tmbPath . DIRECTORY_SEPARATOR . $name;
792
793 // copy image into tmbPath so some drivers does not store files on local fs
794 if (!$data = $this->_db_getThumbnail($path)) {
795 return false;
796 }
797 if (!file_put_contents($tmb, $data)) {
798 return false;
799 }
800
801 $tmbSize = $this->tmbSize;
802
803 if (($s = getimagesize($tmb)) == false) {
804 return false;
805 }
806
807 $result = true;
808
809 /* If image smaller or equal thumbnail size - just fitting to thumbnail square */
810 if ($s[0] <= $tmbSize && $s[1] <= $tmbSize) {
811 $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png');
812 } else {
813 if ($this->options['tmbCrop']) {
814
815 /* Resize and crop if image bigger than thumbnail */
816 if (!(($s[0] > $tmbSize && $s[1] <= $tmbSize) || ($s[0] <= $tmbSize && $s[1] > $tmbSize)) || ($s[0] > $tmbSize && $s[1] > $tmbSize)) {
817 $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, false, 'png');
818 }
819
820 if ($result && ($s = getimagesize($tmb)) != false) {
821 $x = $s[0] > $tmbSize ? intval(($s[0] - $tmbSize) / 2) : 0;
822 $y = $s[1] > $tmbSize ? intval(($s[1] - $tmbSize) / 2) : 0;
823 $result = $this->imgCrop($tmb, $tmbSize, $tmbSize, $x, $y, 'png');
824 }
825 } else {
826 $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, true, 'png');
827 }
828
829 if ($result) {
830 $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png');
831
832 }
833 }
834
835 if (!$result) {
836 unlink($tmb);
837
838 return false;
839 }
840
841 return $name;
842 }
843
844 /**
845 * Return thumbnail file name for required file.
846 *
847 * @param array $stat file stat
848 *
849 * @return string
850 * @author Naoki Sawada
851 **/
852 protected function tmbname($stat)
853 {
854 $name = $this->driverId . '_';
855 if (isset($this->options['tokens']) && is_array($this->options['tokens'])) {
856 $name .= $this->options['tokens']['uid'];
857 }
858
859 return $name . md5($stat['iid']) . $stat['ts'] . '.png';
860 }
861
862 /**
863 * Return content URL (for netmout volume driver)
864 * If file.url == 1 requests from JavaScript client with XHR.
865 *
866 * @param string $hash file hash
867 * @param array $options options array
868 *
869 * @return bool|string
870 * @author Naoki Sawada
871 */
872 public function getContentUrl($hash, $options = [])
873 {
874 if (!empty($options['onetime']) && $this->options['onetimeUrl']) {
875 return parent::getContentUrl($hash, $options);
876 }
877 if (!empty($options['temporary'])) {
878 // try make temporary file
879 $url = parent::getContentUrl($hash, $options);
880 if ($url) {
881 return $url;
882 }
883 }
884 $file = $this->file($hash);
885 if (($file = $this->file($hash)) !== false && (!$file['url'] || $file['url'] == 1)) {
886 $path = $this->decode($hash);
887 $url = '';
888 try {
889 $res = $this->service->postToAPI('/sharing/list_shared_links', ['path' => $path, 'direct_only' => true])->getDecodedBody();
890 if ($res && !empty($res['links'])) {
891 foreach ($res['links'] as $link) {
892 if (isset($link['link_permissions'])
893 && isset($link['link_permissions']['requested_visibility'])
894 && $link['link_permissions']['requested_visibility']['.tag'] === $this->options['publishPermission']['requested_visibility']) {
895 $url = $link['url'];
896 break;
897 }
898 }
899 }
900 if (!$url) {
901 $res = $this->service->postToAPI('/sharing/create_shared_link_with_settings', ['path' => $path, 'settings' => $this->options['publishPermission']])->getDecodedBody();
902 if (isset($res['url'])) {
903 $url = $res['url'];
904 }
905 }
906 if ($url) {
907 $url = str_replace('www.dropbox.com', 'dl.dropboxusercontent.com', $url);
908 $url = str_replace('?dl=0', '', $url);
909
910 return $url;
911 }
912 } catch (DropboxClientException $e) {
913 return $this->setError('Dropbox error: ' . $e->getMessage());
914 }
915 }
916
917 return false;
918 }
919
920 /**
921 * Return debug info for client.
922 *
923 * @return array
924 **/
925 public function debug()
926 {
927 $res = parent::debug();
928 if (!empty($this->options['netkey']) && isset($this->options['tokens']) && !empty($this->options['tokens']['uid'])) {
929 $res['Dropbox uid'] = $this->options['tokens']['uid'];
930 $res['access_token'] = $this->options['tokens']['access_token'];
931 }
932
933 return $res;
934 }
935
936 /*********************** paths/urls *************************/
937
938 /**
939 * Return parent directory path.
940 *
941 * @param string $path file path
942 *
943 * @return string
944 * @author Naoki Sawada
945 **/
946 protected function _dirname($path)
947 {
948 list($dirname) = $this->_db_splitPath($path);
949
950 return $dirname;
951 }
952
953 /**
954 * Return file name.
955 *
956 * @param string $path file path
957 *
958 * @return string
959 * @author Naoki Sawada
960 **/
961 protected function _basename($path)
962 {
963 list(, $basename) = $this->_db_splitPath($path);
964
965 return $basename;
966 }
967
968 /**
969 * Join dir name and file name and retur full path.
970 *
971 * @param string $dir
972 * @param string $name
973 *
974 * @return string
975 * @author Dmitry (dio) Levashov
976 **/
977 protected function _joinPath($dir, $name)
978 {
979 return rtrim($dir, '/') . '/' . strtolower($name);
980 }
981
982 /**
983 * Return normalized path, this works the same as os.path.normpath() in Python.
984 *
985 * @param string $path path
986 *
987 * @return string
988 * @author Naoki Sawada
989 **/
990 protected function _normpath($path)
991 {
992 return '/' . ltrim($path, '/');
993 }
994
995 /**
996 * Return file path related to root dir.
997 *
998 * @param string $path file path
999 *
1000 * @return string
1001 * @author Dmitry (dio) Levashov
1002 **/
1003 protected function _relpath($path)
1004 {
1005 if ($path === $this->root) {
1006 return '';
1007 } else {
1008 return ltrim(substr($path, strlen($this->root)), '/');
1009 }
1010 }
1011
1012 /**
1013 * Convert path related to root dir into real path.
1014 *
1015 * @param string $path file path
1016 *
1017 * @return string
1018 * @author Naoki Sawada
1019 **/
1020 protected function _abspath($path)
1021 {
1022 if ($path === '/') {
1023 return $this->root;
1024 } else {
1025 return $this->_joinPath($this->root, $path);
1026 }
1027 }
1028
1029 /**
1030 * Return fake path started from root dir.
1031 *
1032 * @param string $path file path
1033 *
1034 * @return string
1035 * @author Naoki Sawada
1036 **/
1037 protected function _path($path)
1038 {
1039 $path = $this->_normpath(substr($path, strlen($this->root)));
1040
1041 return $path;
1042 }
1043
1044 /**
1045 * Return true if $path is children of $parent.
1046 *
1047 * @param string $path path to check
1048 * @param string $parent parent path
1049 *
1050 * @return bool
1051 * @author Naoki Sawada
1052 **/
1053 protected function _inpath($path, $parent)
1054 {
1055 return $path == $parent || strpos($path, $parent . '/') === 0;
1056 }
1057
1058 /***************** file stat ********************/
1059 /**
1060 * Return stat for given path.
1061 * Stat contains following fields:
1062 * - (int) size file size in b. required
1063 * - (int) ts file modification time in unix time. required
1064 * - (string) mime mimetype. required for folders, others - optionally
1065 * - (bool) read read permissions. required
1066 * - (bool) write write permissions. required
1067 * - (bool) locked is object locked. optionally
1068 * - (bool) hidden is object hidden. optionally
1069 * - (string) alias for symlinks - link target path relative to root path. optionally
1070 * - (string) target for symlinks - link target path. optionally.
1071 * If file does not exists - returns empty array or false.
1072 *
1073 * @param string $path file path
1074 *
1075 * @return array|false
1076 * @author Dmitry (dio) Levashov
1077 **/
1078 protected function _stat($path)
1079 {
1080 if ($raw = $this->_db_getFile($path)) {
1081 return $this->_db_parseRaw($raw);
1082 }
1083
1084 return false;
1085 }
1086
1087 /**
1088 * Return true if path is dir and has at least one childs directory.
1089 *
1090 * @param string $path dir path
1091 *
1092 * @return bool
1093 * @author Naoki Sawada
1094 **/
1095 protected function _subdirs($path)
1096 {
1097 $hasdir = false;
1098
1099 try {
1100 $res = $this->service->listFolder($path);
1101 if ($res) {
1102 $items = $res->getItems();
1103 foreach ($items as $raw) {
1104 if ($raw instanceof FolderMetadata) {
1105 $hasdir = true;
1106 break;
1107 }
1108 }
1109 }
1110 } catch (DropboxClientException $e) {
1111 $this->setError('Dropbox error: ' . $e->getMessage());
1112 }
1113
1114 return $hasdir;
1115 }
1116
1117 /**
1118 * Return object width and height
1119 * Ususaly used for images, but can be realize for video etc...
1120 *
1121 * @param string $path file path
1122 * @param string $mime file mime type
1123 *
1124 * @return string
1125 * @throws ImagickException
1126 * @throws elFinderAbortException
1127 * @author Naoki Sawada
1128 */
1129 protected function _dimensions($path, $mime)
1130 {
1131 if (strpos($mime, 'image') !== 0) {
1132 return '';
1133 }
1134 $ret = '';
1135
1136 if ($data = $this->_getContents($path)) {
1137 $tmp = $this->getTempFile();
1138 file_put_contents($tmp, $data);
1139 $size = getimagesize($tmp);
1140 if ($size) {
1141 $ret = array('dim' => $size[0] . 'x' . $size[1]);
1142 $srcfp = fopen($tmp, 'rb');
1143 $target = isset(elFinder::$currentArgs['target'])? elFinder::$currentArgs['target'] : '';
1144 if ($subImgLink = $this->getSubstituteImgLink($target, $size, $srcfp)) {
1145 $ret['url'] = $subImgLink;
1146 }
1147 }
1148 }
1149
1150 return $ret;
1151 }
1152
1153 /******************** file/dir content *********************/
1154
1155 /**
1156 * Return files list in directory.
1157 *
1158 * @param string $path dir path
1159 *
1160 * @return array
1161 * @author Naoki Sawada
1162 **/
1163 protected function _scandir($path)
1164 {
1165 return isset($this->dirsCache[$path])
1166 ? $this->dirsCache[$path]
1167 : $this->cacheDir($path);
1168 }
1169
1170 /**
1171 * Open file and return file pointer.
1172 *
1173 * @param string $path file path
1174 * @param bool $write open file for writing
1175 *
1176 * @return resource|false
1177 * @author Naoki Sawada
1178 **/
1179 protected function _fopen($path, $mode = 'rb')
1180 {
1181 if ($mode === 'rb' || $mode === 'r') {
1182 if ($link = $this->service->getTemporaryLink($path)) {
1183 $access_token = $this->service->getAccessToken();
1184 if ($access_token) {
1185 $data = array(
1186 'target' => $link->getLink(),
1187 'headers' => array('Authorization: Bearer ' . $access_token),
1188 );
1189
1190 // to support range request
1191 if (func_num_args() > 2) {
1192 $opts = func_get_arg(2);
1193 } else {
1194 $opts = array();
1195 }
1196 if (!empty($opts['httpheaders'])) {
1197 $data['headers'] = array_merge($opts['httpheaders'], $data['headers']);
1198 }
1199
1200 return elFinder::getStreamByUrl($data);
1201 }
1202 }
1203 }
1204
1205 return false;
1206 }
1207
1208 /**
1209 * Close opened file.
1210 *
1211 * @param resource $fp file pointer
1212 *
1213 * @return bool
1214 * @author Naoki Sawada
1215 **/
1216 protected function _fclose($fp, $path = '')
1217 {
1218 is_resource($fp) && fclose($fp);
1219 }
1220
1221 /******************** file/dir manipulations *************************/
1222
1223 /**
1224 * Create dir and return created dir path or false on failed.
1225 *
1226 * @param string $path parent dir path
1227 * @param string $name new directory name
1228 *
1229 * @return string|bool
1230 * @author Naoki Sawada
1231 **/
1232 protected function _mkdir($path, $name)
1233 {
1234 try {
1235 return $this->service->createFolder($this->_db_joinName($path, $name))->getPathLower();
1236 } catch (DropboxClientException $e) {
1237 return $this->setError('Dropbox error: ' . $e->getMessage());
1238 }
1239 }
1240
1241 /**
1242 * Create file and return it's path or false on failed.
1243 *
1244 * @param string $path parent dir path
1245 * @param string $name new file name
1246 *
1247 * @return string|bool
1248 * @author Naoki Sawada
1249 **/
1250 protected function _mkfile($path, $name)
1251 {
1252 return $this->_save($this->tmpfile(), $path, $name, []);
1253 }
1254
1255 /**
1256 * Create symlink. FTP driver does not support symlinks.
1257 *
1258 * @param string $target link target
1259 * @param string $path symlink path
1260 *
1261 * @return bool
1262 * @author Naoki Sawada
1263 **/
1264 protected function _symlink($target, $path, $name)
1265 {
1266 return false;
1267 }
1268
1269 /**
1270 * Copy file into another file.
1271 *
1272 * @param string $source source file path
1273 * @param string $targetDir target directory path
1274 * @param string $name new file name
1275 *
1276 * @return bool
1277 * @author Naoki Sawada
1278 **/
1279 protected function _copy($source, $targetDir, $name)
1280 {
1281 try {
1282 $this->service->copy($source, $this->_db_joinName($targetDir, $name))->getPathLower();
1283 } catch (DropboxClientException $e) {
1284 return $this->setError('Dropbox error: ' . $e->getMessage());
1285 }
1286
1287 return true;
1288 }
1289
1290 /**
1291 * Move file into another parent dir.
1292 * Return new file path or false.
1293 *
1294 * @param string $source source file path
1295 * @param string $target target dir path
1296 * @param string $name file name
1297 *
1298 * @return string|bool
1299 * @author Naoki Sawada
1300 **/
1301 protected function _move($source, $targetDir, $name)
1302 {
1303 try {
1304 return $this->service->move($source, $this->_db_joinName($targetDir, $name))->getPathLower();
1305 } catch (DropboxClientException $e) {
1306 return $this->setError('Dropbox error: ' . $e->getMessage());
1307 }
1308 }
1309
1310 /**
1311 * Remove file.
1312 *
1313 * @param string $path file path
1314 *
1315 * @return bool
1316 * @author Naoki Sawada
1317 **/
1318 protected function _unlink($path)
1319 {
1320 try {
1321 $this->service->delete($path);
1322
1323 return true;
1324 } catch (DropboxClientException $e) {
1325 return $this->setError('Dropbox error: ' . $e->getMessage());
1326 }
1327
1328 return true;
1329 }
1330
1331 /**
1332 * Remove dir.
1333 *
1334 * @param string $path dir path
1335 *
1336 * @return bool
1337 * @author Naoki Sawada
1338 **/
1339 protected function _rmdir($path)
1340 {
1341 return $this->_unlink($path);
1342 }
1343
1344 /**
1345 * Create new file and write into it from file pointer.
1346 * Return new file path or false on error.
1347 *
1348 * @param resource $fp file pointer
1349 * @param string $dir target dir path
1350 * @param string $name file name
1351 * @param array $stat file stat (required by some virtual fs)
1352 *
1353 * @return bool|string
1354 * @author Naoki Sawada
1355 **/
1356 protected function _save($fp, $path, $name, $stat)
1357 {
1358 try {
1359 $info = stream_get_meta_data($fp);
1360 if (empty($info['uri']) || preg_match('#^[a-z0-9.-]+://#', $info['uri'])) {
1361 if ($filepath = $this->getTempFile()) {
1362 $_fp = fopen($filepath, 'wb');
1363 stream_copy_to_stream($fp, $_fp);
1364 fclose($_fp);
1365 }
1366 } else {
1367 $filepath = $info['uri'];
1368 }
1369 $dropboxFile = new DropboxFile($filepath);
1370 if ($name === '') {
1371 $fullpath = $path;
1372 } else {
1373 $fullpath = $this->_db_joinName($path, $name);
1374 }
1375
1376 return $this->service->upload($dropboxFile, $fullpath, ['mode' => 'overwrite'])->getPathLower();
1377 } catch (DropboxClientException $e) {
1378 return $this->setError('Dropbox error: ' . $e->getMessage());
1379 }
1380 }
1381
1382 /**
1383 * Get file contents.
1384 *
1385 * @param string $path file path
1386 *
1387 * @return string|false
1388 * @author Naoki Sawada
1389 **/
1390 protected function _getContents($path)
1391 {
1392 $contents = '';
1393
1394 try {
1395 $file = $this->service->download($path);
1396 $contents = $file->getContents();
1397 } catch (Exception $e) {
1398 return $this->setError('Dropbox error: ' . $e->getMessage());
1399 }
1400
1401 return $contents;
1402 }
1403
1404 /**
1405 * Write a string to a file.
1406 *
1407 * @param string $path file path
1408 * @param string $content new file content
1409 *
1410 * @return bool
1411 * @author Naoki Sawada
1412 **/
1413 protected function _filePutContents($path, $content)
1414 {
1415 $res = false;
1416
1417 if ($local = $this->getTempFile($path)) {
1418 if (file_put_contents($local, $content, LOCK_EX) !== false
1419 && ($fp = fopen($local, 'rb'))) {
1420 clearstatcache();
1421 $name = '';
1422 $stat = $this->stat($path);
1423 if ($stat) {
1424 // keep real name
1425 $path = $this->_dirname($path);
1426 $name = $stat['name'];
1427 }
1428 $res = $this->_save($fp, $path, $name, []);
1429 fclose($fp);
1430 }
1431 file_exists($local) && unlink($local);
1432 }
1433
1434 return $res;
1435 }
1436
1437 /**
1438 * Detect available archivers.
1439 **/
1440 protected function _checkArchivers()
1441 {
1442 // die('Not yet implemented. (_checkArchivers)');
1443 return [];
1444 }
1445
1446 /**
1447 * chmod implementation.
1448 *
1449 * @return bool
1450 **/
1451 protected function _chmod($path, $mode)
1452 {
1453 return false;
1454 }
1455
1456 /**
1457 * Unpack archive.
1458 *
1459 * @param string $path archive path
1460 * @param array $arc archiver command and arguments (same as in $this->archivers)
1461 *
1462 * @return true
1463 * @author Dmitry (dio) Levashov
1464 * @author Alexey Sukhotin
1465 **/
1466 protected function _unpack($path, $arc)
1467 {
1468 die('Not yet implemented. (_unpack)');
1469 //return false;
1470 }
1471
1472 /**
1473 * Recursive symlinks search.
1474 *
1475 * @param string $path file/dir path
1476 *
1477 * @return bool
1478 * @author Dmitry (dio) Levashov
1479 **/
1480 protected function _findSymlinks($path)
1481 {
1482 die('Not yet implemented. (_findSymlinks)');
1483 }
1484
1485 /**
1486 * Extract files from archive.
1487 *
1488 * @param string $path archive path
1489 * @param array $arc archiver command and arguments (same as in $this->archivers)
1490 *
1491 * @return true
1492 * @author Dmitry (dio) Levashov,
1493 * @author Alexey Sukhotin
1494 **/
1495 protected function _extract($path, $arc)
1496 {
1497 die('Not yet implemented. (_extract)');
1498 }
1499
1500 /**
1501 * Create archive and return its path.
1502 *
1503 * @param string $dir target dir
1504 * @param array $files files names list
1505 * @param string $name archive name
1506 * @param array $arc archiver options
1507 *
1508 * @return string|bool
1509 * @author Dmitry (dio) Levashov,
1510 * @author Alexey Sukhotin
1511 **/
1512 protected function _archive($dir, $files, $name, $arc)
1513 {
1514 die('Not yet implemented. (_archive)');
1515 }
1516 } // END class
1517