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 **/ 10class 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