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