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