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