Advertisement
claukiller

Untitled

Oct 7th, 2019
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 44.04 KB | None | 0 0
  1. <?php
  2. namespace Hypweb\Flysystem\GoogleDrive;
  3.  
  4. use Google_Service_Drive;
  5. use Google_Service_Drive_DriveFile;
  6. use Google_Service_Drive_FileList;
  7. use Google_Service_Drive_Permission;
  8. use Google_Http_MediaFileUpload;
  9. use League\Flysystem\Adapter\AbstractAdapter;
  10. use League\Flysystem\AdapterInterface;
  11. use League\Flysystem\Config;
  12. use League\Flysystem\Util;
  13.  
  14. class GoogleDriveAdapter extends AbstractAdapter
  15. {
  16.  
  17. /**
  18. * Fetch fields setting for get
  19. *
  20. * @var string
  21. */
  22. const FETCHFIELDS_GET = 'id,name,mimeType,modifiedTime,parents,permissions,size,webContentLink,webViewLink';
  23. /**
  24. * Fetch fields setting for list
  25. *
  26. * @var string
  27. */
  28. const FETCHFIELDS_LIST = 'files(FETCHFIELDS_GET),nextPageToken';
  29.  
  30.  
  31. /**
  32. * MIME tyoe of directory
  33. *
  34. * @var string
  35. */
  36. const DIRMIME = 'application/vnd.google-apps.folder';
  37.  
  38. /**
  39. * Google_Service_Drive instance
  40. *
  41. * @var Google_Service_Drive
  42. */
  43. protected $service;
  44.  
  45. /**
  46. * Default options
  47. *
  48. * @var array
  49. */
  50. protected static $defaultOptions = [
  51. 'spaces' => 'drive',
  52. 'useHasDir' => false,
  53. 'additionalFetchField' => 'webViewLink,id',
  54. 'publishPermission' => [
  55. 'type' => 'anyone',
  56. 'role' => 'reader',
  57. 'withLink' => true
  58. ],
  59. 'appsExportMap' => [
  60. 'application/vnd.google-apps.document' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  61. 'application/vnd.google-apps.spreadsheet' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  62. 'application/vnd.google-apps.drawing' => 'application/pdf',
  63. 'application/vnd.google-apps.presentation' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  64. 'application/vnd.google-apps.script' => 'application/vnd.google-apps.script+json',
  65. 'default' => 'application/pdf'
  66. ],
  67. // Default parameters for each command
  68. // see https://developers.google.com/drive/v3/reference/files
  69. // ex. 'defaultParams' => ['files.list' => ['includeTeamDriveItems' => true]]
  70. 'defaultParams' => [],
  71. // Team Drive Id
  72. 'teamDriveId' => null,
  73. // Corpora value for files.list with the Team Drive
  74. 'corpora' => 'teamDrive',
  75. // Delete action 'trash' (Into trash) or 'delete' (Permanently delete)
  76. 'deleteAction' => 'trash'
  77. ];
  78.  
  79. /**
  80. * A comma-separated list of spaces to query
  81. * Supported values are 'drive', 'appDataFolder' and 'photos'
  82. *
  83. * @var string
  84. */
  85. protected $spaces;
  86.  
  87. /**
  88. * Permission array as published item
  89. *
  90. * @var array
  91. */
  92. protected $publishPermission;
  93.  
  94. /**
  95. * Cache of file objects
  96. *
  97. * @var array
  98. */
  99. private $cacheFileObjects = [];
  100.  
  101. /**
  102. * Cache of file objects by ParentId/Name based
  103. *
  104. * @var array
  105. */
  106. private $cacheFileObjectsByName = [];
  107.  
  108. /**
  109. * Cache of hasDir
  110. *
  111. * @var array
  112. */
  113. private $cacheHasDirs = [];
  114.  
  115. /**
  116. * Use hasDir function
  117. *
  118. * @var bool
  119. */
  120. private $useHasDir = false;
  121.  
  122. /**
  123. * List of fetch field for get
  124. *
  125. * @var string
  126. */
  127. private $fetchfieldsGet = '';
  128.  
  129. /**
  130. * List of fetch field for lest
  131. *
  132. * @var string
  133. */
  134. private $fetchfieldsList = '';
  135.  
  136. /**
  137. * Additional fetch fields array
  138. *
  139. * @var array
  140. */
  141. private $additionalFields = [];
  142.  
  143. /**
  144. * Options array
  145. *
  146. * @var array
  147. */
  148. private $options = [];
  149.  
  150. /**
  151. * Default parameters of each commands
  152. *
  153. * @var array
  154. */
  155. private $defaultParams = [];
  156.  
  157. public function __construct(Google_Service_Drive $service, $root = null, $options = [])
  158. {
  159. if (! $root) {
  160. $root = 'root';
  161. }
  162. $this->service = $service;
  163. $this->setPathPrefix($root);
  164. $this->root = $root;
  165.  
  166. $this->options = array_replace_recursive(static::$defaultOptions, $options);
  167.  
  168. $this->spaces = $this->options['spaces'];
  169. $this->useHasDir = $this->options['useHasDir'];
  170. $this->publishPermission = $this->options['publishPermission'];
  171.  
  172. $this->fetchfieldsGet = self::FETCHFIELDS_GET;
  173. if ($this->options['additionalFetchField']) {
  174. $this->fetchfieldsGet .= ',' . $this->options['additionalFetchField'];
  175. $this->additionalFields = explode(',', $this->options['additionalFetchField']);
  176. }
  177. $this->fetchfieldsList = str_replace('FETCHFIELDS_GET', $this->fetchfieldsGet, self::FETCHFIELDS_LIST);
  178. if (isset($this->options['defaultParams']) && is_array($this->options['defaultParams'])) {
  179. $this->defaultParams = $this->options['defaultParams'];
  180. }
  181.  
  182. if ($this->options['teamDriveId']) {
  183. $this->setTeamDriveId($this->options['teamDriveId'], $this->options['corpora']);
  184. }
  185. }
  186.  
  187. /**
  188. * Gets the service (Google_Service_Drive)
  189. *
  190. * @return object Google_Service_Drive
  191. */
  192. public function getService()
  193. {
  194. return $this->service;
  195. }
  196.  
  197. /**
  198. * Write a new file.
  199. *
  200. * @param string $path
  201. * @param string $contents
  202. * @param Config $config
  203. * Config object
  204. *
  205. * @return array|false false on failure file meta data on success
  206. */
  207. public function write($path, $contents, Config $config)
  208. {
  209. return $this->upload($path, $contents, $config);
  210. }
  211.  
  212. /**
  213. * Write a new file using a stream.
  214. *
  215. * @param string $path
  216. * @param resource $resource
  217. * @param Config $config
  218. * Config object
  219. *
  220. * @return array|false false on failure file meta data on success
  221. */
  222. public function writeStream($path, $resource, Config $config)
  223. {
  224. return $this->write($path, $resource, $config);
  225. }
  226.  
  227. /**
  228. * Update a file.
  229. *
  230. * @param string $path
  231. * @param string $contents
  232. * @param Config $config
  233. * Config object
  234. *
  235. * @return array|false false on failure file meta data on success
  236. */
  237. public function update($path, $contents, Config $config)
  238. {
  239. return $this->write($path, $contents, $config);
  240. }
  241.  
  242. /**
  243. * Update a file using a stream.
  244. *
  245. * @param string $path
  246. * @param resource $resource
  247. * @param Config $config
  248. * Config object
  249. *
  250. * @return array|false false on failure file meta data on success
  251. */
  252. public function updateStream($path, $resource, Config $config)
  253. {
  254. return $this->write($path, $resource, $config);
  255. }
  256.  
  257. /**
  258. * Rename a file.
  259. *
  260. * @param string $path
  261. * @param string $newpath
  262. *
  263. * @return bool
  264. */
  265. public function rename($path, $newpath)
  266. {
  267. list ($oldParent, $fileId) = $this->splitPath($path);
  268. list ($newParent, $newName) = $this->splitPath($newpath);
  269.  
  270. $file = new Google_Service_Drive_DriveFile();
  271. $file->setName($newName);
  272. $opts = [
  273. 'fields' => $this->fetchfieldsGet
  274. ];
  275. if ($newParent !== $oldParent) {
  276. $opts['addParents'] = $newParent;
  277. $opts['removeParents'] = $oldParent;
  278. }
  279.  
  280. $updatedFile = $this->service->files->update($fileId, $file, $this->applyDefaultParams($opts, 'files.update'));
  281.  
  282. if ($updatedFile) {
  283. $this->cacheFileObjects[$updatedFile->getId()] = $updatedFile;
  284. $this->cacheFileObjectsByName[$newParent . '/' . $newName] = $updatedFile;
  285. return true;
  286. }
  287.  
  288. return false;
  289. }
  290.  
  291. /**
  292. * Copy a file.
  293. *
  294. * @param string $path
  295. * @param string $newpath
  296. *
  297. * @return bool
  298. */
  299. public function copy($path, $newpath)
  300. {
  301. list (, $srcId) = $this->splitPath($path);
  302.  
  303. list ($newParentId, $fileName) = $this->splitPath($newpath);
  304.  
  305. $file = new Google_Service_Drive_DriveFile();
  306. $file->setName($fileName);
  307. $file->setParents([
  308. $newParentId
  309. ]);
  310.  
  311. $newFile = $this->service->files->copy($srcId, $file, $this->applyDefaultParams([
  312. 'fields' => $this->fetchfieldsGet
  313. ], 'files.copy'));
  314.  
  315. if ($newFile instanceof Google_Service_Drive_DriveFile) {
  316. $this->cacheFileObjects[$newFile->getId()] = $newFile;
  317. $this->cacheFileObjectsByName[$newParentId . '/' . $fileName] = $newFile;
  318. list ($newDir) = $this->splitPath($newpath);
  319. $newpath = (($newDir === $this->root) ? '' : ($newDir . '/')) . $newFile->getId();
  320. if ($this->getRawVisibility($path) === AdapterInterface::VISIBILITY_PUBLIC) {
  321. $this->publish($newpath);
  322. } else {
  323. $this->unPublish($newpath);
  324. }
  325. return true;
  326. }
  327.  
  328. return false;
  329. }
  330.  
  331. /**
  332. * Delete a file.
  333. *
  334. * @param string $path
  335. *
  336. * @return bool
  337. */
  338. public function delete($path)
  339. {
  340. if ($file = $this->getFileObject($path)) {
  341. $name = $file->getName();
  342. list ($parentId, $id) = $this->splitPath($path);
  343. if ($parents = $file->getParents()) {
  344. $file = new Google_Service_Drive_DriveFile();
  345. $opts = [];
  346. $res = false;
  347. if (count($parents) > 1) {
  348. $opts['removeParents'] = $parentId;
  349. } else {
  350. if ($this->options['deleteAction'] === 'delete') {
  351. try {
  352. $this->service->files->delete($id);
  353. } catch (Google_Exception $e) {
  354. return false;
  355. }
  356. $res = true;
  357. } else {
  358. $file->setTrashed(true);
  359. }
  360. }
  361. if (!$res) {
  362. try {
  363. $this->service->files->update($id, $file, $this->applyDefaultParams($opts, 'files.update'));
  364. } catch (Google_Exception $e) {
  365. return false;
  366. }
  367. }
  368. unset($this->cacheFileObjects[$id], $this->cacheHasDirs[$id], $this->cacheFileObjectsByName[$parentId . '/' . $name]);
  369. return true;
  370. }
  371. }
  372. return false;
  373. }
  374.  
  375. /**
  376. * Delete a directory.
  377. *
  378. * @param string $dirname
  379. *
  380. * @return bool
  381. */
  382. public function deleteDir($dirname)
  383. {
  384. return $this->delete($dirname);
  385. }
  386.  
  387. /**
  388. * Create a directory.
  389. *
  390. * @param string $dirname
  391. * directory name
  392. * @param Config $config
  393. *
  394. * @return array|false
  395. */
  396. public function createDir($dirname, Config $config)
  397. {
  398. list ($pdirId, $name) = $this->splitPath($dirname);
  399.  
  400. $folder = $this->createDirectory($name, $pdirId);
  401. if ($folder) {
  402. $itemId = $folder->getId();
  403. $this->cacheFileObjectsByName[$pdirId . '/' . $name] = $folder; // for confirmation by getMetaData() oe has() while in this connection
  404. $this->cacheFileObjects[$itemId] = $folder;
  405. $this->cacheHasDirs[$itemId] = false;
  406. $path_parts = $this->splitFileExtension($name);
  407. $result = [
  408. 'path' => Util::dirname($dirname) . '/' . $itemId,
  409. 'filename' => $path_parts['filename'],
  410. 'extension' => $path_parts['extension']
  411. ];
  412. return $result;
  413. }
  414.  
  415. return false;
  416. }
  417.  
  418. /**
  419. * Check whether a file exists.
  420. *
  421. * @param string $path
  422. *
  423. * @return array|bool|null
  424. */
  425. public function has($path)
  426. {
  427. return ($this->getFileObject($path, true) instanceof Google_Service_Drive_DriveFile);
  428. }
  429.  
  430. /**
  431. * Read a file.
  432. *
  433. * @param string $path
  434. *
  435. * @return array|false
  436. */
  437. public function read($path)
  438. {
  439. list (, $fileId) = $this->splitPath($path);
  440. if ($response = $this->service->files->get($fileId, $this->applyDefaultParams([
  441. 'alt' => 'media'
  442. ], 'files.get'))) {
  443. return [
  444. 'contents' => (string) $response->getBody()
  445. ];
  446. }
  447.  
  448. return false;
  449. }
  450.  
  451. /**
  452. * Read a file as a stream.
  453. *
  454. * @param string $path
  455. *
  456. * @return array|false
  457. */
  458. public function readStream($path)
  459. {
  460. $redirect = [];
  461. if (func_num_args() > 1) {
  462. $redirect = func_get_arg(1);
  463. }
  464. if (! $redirect) {
  465. $redirect = [
  466. 'cnt' => 0,
  467. 'url' => '',
  468. 'token' => '',
  469. 'cookies' => []
  470. ];
  471. if ($file = $this->getFileObject($path)) {
  472. $dlurl = $this->getDownloadUrl($file);
  473. $client = $this->service->getClient();
  474. if ($client->isUsingApplicationDefaultCredentials()) {
  475. $token = $client->fetchAccessTokenWithAssertion();
  476. } else {
  477. $token = $client->getAccessToken();
  478. }
  479. $access_token = '';
  480. if (is_array($token)) {
  481. if (empty($token['access_token']) && !empty($token['refresh_token'])) {
  482. $token = $client->fetchAccessTokenWithRefreshToken();
  483. }
  484. $access_token = $token['access_token'];
  485. } else {
  486. if ($token = @json_decode($client->getAccessToken())) {
  487. $access_token = $token->access_token;
  488. }
  489. }
  490. $redirect = [
  491. 'cnt' => 0,
  492. 'url' => '',
  493. 'token' => $access_token,
  494. 'cookies' => []
  495. ];
  496. }
  497. } else {
  498. if ($redirect['cnt'] > 5) {
  499. return false;
  500. }
  501. $dlurl = $redirect['url'];
  502. $redirect['url'] = '';
  503. $access_token = $redirect['token'];
  504. }
  505.  
  506. if ($dlurl) {
  507. $url = parse_url($dlurl);
  508. $cookies = [];
  509. if ($redirect['cookies']) {
  510. foreach ($redirect['cookies'] as $d => $c) {
  511. if (strpos($url['host'], $d) !== false) {
  512. $cookies[] = $c;
  513. }
  514. }
  515. }
  516. if ($access_token) {
  517. $query = isset($url['query']) ? '?' . $url['query'] : '';
  518. $stream = stream_socket_client('ssl://' . $url['host'] . ':443');
  519. stream_set_timeout($stream, 300);
  520. fputs($stream, "GET {$url['path']}{$query} HTTP/1.1\r\n");
  521. fputs($stream, "Host: {$url['host']}\r\n");
  522. fputs($stream, "Authorization: Bearer {$access_token}\r\n");
  523. fputs($stream, "Connection: Close\r\n");
  524. if ($cookies) {
  525. fputs($stream, "Cookie: " . join('; ', $cookies) . "\r\n");
  526. }
  527. fputs($stream, "\r\n");
  528. while (($res = trim(fgets($stream))) !== '') {
  529. // find redirect
  530. if (preg_match('/^Location: (.+)$/', $res, $m)) {
  531. $redirect['url'] = $m[1];
  532. }
  533. // fetch cookie
  534. if (strpos($res, 'Set-Cookie:') === 0) {
  535. $domain = $url['host'];
  536. if (preg_match('/^Set-Cookie:(.+)(?:domain=\s*([^ ;]+))?/i', $res, $c1)) {
  537. if (! empty($c1[2])) {
  538. $domain = trim($c1[2]);
  539. }
  540. if (preg_match('/([^ ]+=[^;]+)/', $c1[1], $c2)) {
  541. $redirect['cookies'][$domain] = $c2[1];
  542. }
  543. }
  544. }
  545. }
  546. if ($redirect['url']) {
  547. $redirect['cnt'] ++;
  548. fclose($stream);
  549. return $this->readStream($path, $redirect);
  550. }
  551. return compact('stream');
  552. }
  553. }
  554. return false;
  555. }
  556.  
  557. /**
  558. * List contents of a directory.
  559. *
  560. * @param string $dirname
  561. * @param bool $recursive
  562. *
  563. * @return array
  564. */
  565. public function listContents($dirname = '', $recursive = false)
  566. {
  567. return $this->getItems($dirname, $recursive);
  568. }
  569.  
  570. public function listFolderContents($id)
  571. {
  572. return $service->files->list(array('q' => "'$id' in parents"));
  573. }
  574.  
  575. /**
  576. * Get all the meta data of a file or directory.
  577. *
  578. * @param string $path
  579. *
  580. * @return array|false
  581. */
  582. public function getMetadata($path)
  583. {
  584. if ($obj = $this->getFileObject($path, true)) {
  585. if ($obj instanceof Google_Service_Drive_DriveFile) {
  586. return $this->normaliseObject($obj, Util::dirname($path));
  587. }
  588. }
  589. return false;
  590. }
  591.  
  592. /**
  593. * Get all the meta data of a file or directory.
  594. *
  595. * @param string $path
  596. *
  597. * @return array|false
  598. */
  599. public function getSize($path)
  600. {
  601. $meta = $this->getMetadata($path);
  602. return ($meta && isset($meta['size'])) ? $meta : false;
  603. }
  604.  
  605. /**
  606. * Get the mimetype of a file.
  607. *
  608. * @param string $path
  609. *
  610. * @return array|false
  611. */
  612. public function getMimetype($path)
  613. {
  614. $meta = $this->getMetadata($path);
  615. return ($meta && isset($meta['mimetype'])) ? $meta : false;
  616. }
  617.  
  618. /**
  619. * Get the timestamp of a file.
  620. *
  621. * @param string $path
  622. *
  623. * @return array|false
  624. */
  625. public function getTimestamp($path)
  626. {
  627. $meta = $this->getMetadata($path);
  628. return ($meta && isset($meta['timestamp'])) ? $meta : false;
  629. }
  630.  
  631. /**
  632. * Set the visibility for a file.
  633. *
  634. * @param string $path
  635. * @param string $visibility
  636. *
  637. * @return array|false file meta data
  638. */
  639. public function setVisibility($path, $visibility)
  640. {
  641. $result = ($visibility === AdapterInterface::VISIBILITY_PUBLIC) ? $this->publish($path) : $this->unPublish($path);
  642.  
  643. if ($result) {
  644. return compact('path', 'visibility');
  645. }
  646.  
  647. return false;
  648. }
  649.  
  650. /**
  651. * Get the visibility of a file.
  652. *
  653. * @param string $path
  654. *
  655. * @return array|false
  656. */
  657. public function getVisibility($path)
  658. {
  659. return [
  660. 'visibility' => $this->getRawVisibility($path)
  661. ];
  662. }
  663.  
  664. // /////////////////- ORIGINAL METHODS -///////////////////
  665.  
  666. /**
  667. * Get contents parmanent URL
  668. *
  669. * @param string $path
  670. * itemId path
  671. *
  672. * @return string|false
  673. */
  674. public function getUrl($path)
  675. {
  676. if ($this->publish($path)) {
  677. $obj = $this->getFileObject($path);
  678. if ($url = $obj->getWebContentLink()) {
  679. return str_replace('export=download', 'export=media', $url);
  680. }
  681. if ($url = $obj->getWebViewLink()) {
  682. return $url;
  683. }
  684. }
  685. return false;
  686. }
  687.  
  688. /**
  689. * Has child directory
  690. *
  691. * @param string $path
  692. * itemId path
  693. *
  694. * @return array
  695. */
  696. public function hasDir($path)
  697. {
  698. $meta = $this->getMetadata($path);
  699. return ($meta && isset($meta['hasdir'])) ? $meta : [
  700. 'hasdir' => true
  701. ];
  702. }
  703.  
  704. /**
  705. * Do cache cacheHasDirs with batch request
  706. *
  707. * @param array $targets
  708. * [[path => id],...]
  709. *
  710. * @return void
  711. */
  712. protected function setHasDir($targets, $object)
  713. {
  714. $service = $this->service;
  715. $client = $service->getClient();
  716. $gFiles = $service->files;
  717. $opts = [
  718. 'pageSize' => 1
  719. ];
  720. $paths = [];
  721. $client->setUseBatch(true);
  722. $batch = $service->createBatch();
  723. $i = 0;
  724. foreach ($targets as $id) {
  725. $opts['q'] = sprintf('trashed = false and "%s" in parents and mimeType = "%s"', $id, self::DIRMIME);
  726. $request = $gFiles->listFiles($this->applyDefaultParams($opts, 'files.list'));
  727. $key = ++ $i;
  728. $batch->add($request, (string) $key);
  729. $paths['response-' . $key] = $id;
  730. }
  731. $results = $batch->execute();
  732. foreach ($results as $key => $result) {
  733. if ($result instanceof Google_Service_Drive_FileList) {
  734. $object[$paths[$key]]['hasdir'] = $this->cacheHasDirs[$paths[$key]] = (bool) $result->getFiles();
  735. }
  736. }
  737. $client->setUseBatch(false);
  738. return $object;
  739. }
  740.  
  741. /**
  742. * Get the object permissions presented as a visibility.
  743. *
  744. * @param string $path
  745. * itemId path
  746. *
  747. * @return string
  748. */
  749. protected function getRawVisibility($path)
  750. {
  751. $file = $this->getFileObject($path);
  752. $permissions = $file->getPermissions();
  753. $visibility = AdapterInterface::VISIBILITY_PRIVATE;
  754. foreach ($permissions as $permission) {
  755. if ($permission->type === $this->publishPermission['type'] && $permission->role === $this->publishPermission['role']) {
  756. $visibility = AdapterInterface::VISIBILITY_PUBLIC;
  757. break;
  758. }
  759. }
  760. return $visibility;
  761. }
  762.  
  763. /**
  764. * Publish specified path item
  765. *
  766. * @param string $path
  767. * itemId path
  768. *
  769. * @return bool
  770. */
  771. protected function publish($path)
  772. {
  773. if (($file = $this->getFileObject($path))) {
  774. if ($this->getRawVisibility($path) === AdapterInterface::VISIBILITY_PUBLIC) {
  775. return true;
  776. }
  777. try {
  778. $permission = new Google_Service_Drive_Permission($this->publishPermission);
  779. if ($this->service->permissions->create($file->getId(), $permission)) {
  780. return true;
  781. }
  782. } catch (Exception $e) {
  783. return false;
  784. }
  785. }
  786.  
  787. return false;
  788. }
  789.  
  790. /**
  791. * Un-publish specified path item
  792. *
  793. * @param string $path
  794. * itemId path
  795. *
  796. * @return bool
  797. */
  798. protected function unPublish($path)
  799. {
  800. if (($file = $this->getFileObject($path))) {
  801. $permissions = $file->getPermissions();
  802. try {
  803. foreach ($permissions as $permission) {
  804. if ($permission->type === 'anyone' && $permission->role === 'reader') {
  805. $this->service->permissions->delete($file->getId(), $permission->getId());
  806. }
  807. }
  808. return true;
  809. } catch (Exception $e) {
  810. return false;
  811. }
  812. }
  813.  
  814. return false;
  815. }
  816.  
  817. /**
  818. * Path splits to dirId, fileId or newName
  819. *
  820. * @param string $path
  821. *
  822. * @return array [ $dirId , $fileId|newName ]
  823. */
  824. protected function splitPath($path, $getParentId = true)
  825. {
  826. if ($path === '' || $path === '/') {
  827. $fileName = $this->root;
  828. $dirName = '';
  829. } else {
  830. $paths = explode('/', $path);
  831. $fileName = array_pop($paths);
  832. if ($getParentId) {
  833. $dirName = $paths ? array_pop($paths) : '';
  834. } else {
  835. $dirName = join('/', $paths);
  836. }
  837. if ($dirName === '') {
  838. $dirName = $this->root;
  839. }
  840. }
  841. return [
  842. $dirName,
  843. $fileName
  844. ];
  845. }
  846.  
  847. /**
  848. * Item name splits to filename and extension
  849. * This function supported include '/' in item name
  850. *
  851. * @param string $name
  852. *
  853. * @return array [ 'filename' => $filename , 'extension' => $extension ]
  854. */
  855. protected function splitFileExtension($name)
  856. {
  857. $extension = '';
  858. $name_parts = explode('.', $name);
  859. if (isset($name_parts[1])) {
  860. $extension = array_pop($name_parts);
  861. }
  862. $filename = join('.', $name_parts);
  863. return compact('filename', 'extension');
  864. }
  865.  
  866. /**
  867. * Get normalised files array from Google_Service_Drive_DriveFile
  868. *
  869. * @param Google_Service_Drive_DriveFile $object
  870. * @param String $dirname
  871. * Parent directory itemId path
  872. *
  873. * @return array Normalised files array
  874. */
  875. protected function normaliseObject(Google_Service_Drive_DriveFile $object, $dirname)
  876. {
  877. $id = $object->getId();
  878. $path_parts = $this->splitFileExtension($object->getName());
  879. //$result['idGoogle'] = $object['id'];
  880. //$result['webViewLink'] = $object['webViewLink'];
  881. $result['size'] = $object['size'];
  882. $result['modifiedTime'] = $object['modifiedTime'];
  883. $result = ['name' => $object->getName()];
  884. $result['type'] = $object->mimeType === self::DIRMIME ? 'dir' : 'file';
  885. $result['path'] = ($dirname ? ($dirname . '/') : '') . $id;
  886. $result['filename'] = $path_parts['filename'];
  887. $result['extension'] = $path_parts['extension'];
  888. $result['timestamp'] = strtotime($object->getModifiedTime());
  889. if ($result['type'] === 'file') {
  890. $result['mimetype'] = $object->mimeType;
  891. $result['size'] = (int) $object->getSize();
  892. }
  893. if ($result['type'] === 'dir') {
  894. $result['size'] = 0;
  895. if ($this->useHasDir) {
  896. $result['hasdir'] = isset($this->cacheHasDirs[$id]) ? $this->cacheHasDirs[$id] : false;
  897. }
  898. }
  899. // attach additional fields
  900. if ($this->additionalFields) {
  901. foreach($this->additionalFields as $field) {
  902. if (property_exists($object, $field)) {
  903. $result[$field] = $object->$field;
  904. }
  905. }
  906. }
  907. return $result;
  908. }
  909.  
  910. /**
  911. * Get items array of target dirctory
  912. *
  913. * @param string $dirname
  914. * itemId path
  915. * @param bool $recursive
  916. * @param number $maxResults
  917. * @param string $query
  918. *
  919. * @return array Items array
  920. */
  921. protected function getItems($dirname, $recursive = false, $maxResults = 0, $query = '')
  922. {
  923. list (, $itemId) = $this->splitPath($dirname);
  924.  
  925. $maxResults = min($maxResults, 1000);
  926. $results = [];
  927. $parameters = [
  928. 'pageSize' => $maxResults ?: 1000,
  929. 'fields' => $this->fetchfieldsList,
  930. 'spaces' => $this->spaces,
  931. 'q' => sprintf('trashed = false and "%s" in parents', $itemId)
  932. ];
  933. if ($query) {
  934. $parameters['q'] .= ' and (' . $query . ')';
  935. }
  936. $parameters = $this->applyDefaultParams($parameters, 'files.list');
  937. $pageToken = NULL;
  938. $gFiles = $this->service->files;
  939. $this->cacheHasDirs[$itemId] = false;
  940. $setHasDir = [];
  941.  
  942. do {
  943. try {
  944. if ($pageToken) {
  945. $parameters['pageToken'] = $pageToken;
  946. }
  947. $fileObjs = $gFiles->listFiles($parameters);
  948. if ($fileObjs instanceof Google_Service_Drive_FileList) {
  949. foreach ($fileObjs as $obj) {
  950. $id = $obj->getId();
  951. $this->cacheFileObjects[$id] = $obj;
  952. $result = $this->normaliseObject($obj, $dirname);
  953. $results[$id] = $result;
  954. if ($result['type'] === 'dir') {
  955. if ($this->useHasDir) {
  956. $setHasDir[$id] = $id;
  957. }
  958. if ($this->cacheHasDirs[$itemId] === false) {
  959. $this->cacheHasDirs[$itemId] = true;
  960. unset($setHasDir[$itemId]);
  961. }
  962. if ($recursive) {
  963. $results = array_merge($results, $this->getItems($result['path'], true, $maxResults, $query));
  964. }
  965. }
  966. }
  967. $pageToken = $fileObjs->getNextPageToken();
  968. } else {
  969. $pageToken = NULL;
  970. }
  971. } catch (Exception $e) {
  972. $pageToken = NULL;
  973. }
  974. } while ($pageToken && $maxResults === 0);
  975.  
  976. if ($setHasDir) {
  977. $results = $this->setHasDir($setHasDir, $results);
  978. }
  979. return array_values($results);
  980. }
  981.  
  982. /**
  983. * Get file oblect Google_Service_Drive_DriveFile
  984. *
  985. * @param string $path
  986. * itemId path
  987. * @param string $checkDir
  988. * do check hasdir
  989. *
  990. * @return Google_Service_Drive_DriveFile|null
  991. */
  992. protected function getFileObject($path, $checkDir = false)
  993. {
  994. list ($parentId, $itemId) = $this->splitPath($path, true);
  995. if (isset($this->cacheFileObjects[$itemId])) {
  996. return $this->cacheFileObjects[$itemId];
  997. } else if (isset($this->cacheFileObjectsByName[$parentId . '/' . $itemId])) {
  998. return $this->cacheFileObjectsByName[$parentId . '/' . $itemId];
  999. }
  1000.  
  1001. $service = $this->service;
  1002. $client = $service->getClient();
  1003.  
  1004. $client->setUseBatch(true);
  1005. $batch = $service->createBatch();
  1006.  
  1007. $opts = [
  1008. 'fields' => $this->fetchfieldsGet
  1009. ];
  1010.  
  1011. $batch->add($this->service->files->get($itemId, $this->applyDefaultParams($opts, 'files.get')), 'obj');
  1012. if ($checkDir && $this->useHasDir) {
  1013. $batch->add($service->files->listFiles($this->applyDefaultParams([
  1014. 'pageSize' => 1,
  1015. 'q' => sprintf('trashed = false and "%s" in parents and mimeType = "%s"', $itemId, self::DIRMIME)
  1016. ], 'files.list')), 'hasdir');
  1017. }
  1018. $results = array_values($batch->execute());
  1019.  
  1020. list ($fileObj, $hasdir) = array_pad($results, 2, null);
  1021. $client->setUseBatch(false);
  1022.  
  1023. if ($fileObj instanceof Google_Service_Drive_DriveFile) {
  1024. if ($hasdir && $fileObj->mimeType === self::DIRMIME) {
  1025. if ($hasdir instanceof Google_Service_Drive_FileList) {
  1026. $this->cacheHasDirs[$fileObj->getId()] = (bool) $hasdir->getFiles();
  1027. }
  1028. }
  1029. } else {
  1030. $fileObj = NULL;
  1031. }
  1032. $this->cacheFileObjects[$itemId] = $fileObj;
  1033.  
  1034. return $fileObj;
  1035. }
  1036.  
  1037. /**
  1038. * Get download url
  1039. *
  1040. * @param Google_Service_Drive_DriveFile $file
  1041. *
  1042. * @return string|false
  1043. */
  1044. protected function getDownloadUrl($file)
  1045. {
  1046. if (strpos($file->mimeType, 'application/vnd.google-apps') !== 0) {
  1047. return 'https://www.googleapis.com/drive/v3/files/' . $file->getId() . '?alt=media';
  1048. } else {
  1049. $mimeMap = $this->options['appsExportMap'];
  1050. if (isset($mimeMap[$file->getMimeType()])) {
  1051. $mime = $mimeMap[$file->getMimeType()];
  1052. } else {
  1053. $mime = $mimeMap['default'];
  1054. }
  1055. $mime = rawurlencode($mime);
  1056.  
  1057. return 'https://www.googleapis.com/drive/v3/files/' . $file->getId() . '/export?mimeType=' . $mime;
  1058. }
  1059.  
  1060. return false;
  1061. }
  1062.  
  1063. /**
  1064. * Create dirctory
  1065. *
  1066. * @param string $name
  1067. * @param string $parentId
  1068. *
  1069. * @return Google_Service_Drive_DriveFile|NULL
  1070. */
  1071. protected function createDirectory($name, $parentId)
  1072. {
  1073. $file = new Google_Service_Drive_DriveFile();
  1074. $file->setName($name);
  1075. $file->setParents([
  1076. $parentId
  1077. ]);
  1078. $file->setMimeType(self::DIRMIME);
  1079.  
  1080. $obj = $this->service->files->create($file, $this->applyDefaultParams([
  1081. 'fields' => $this->fetchfieldsGet
  1082. ], 'files.create'));
  1083.  
  1084. return ($obj instanceof Google_Service_Drive_DriveFile) ? $obj : false;
  1085. }
  1086.  
  1087. /**
  1088. * Upload|Update item
  1089. *
  1090. * @param string $path
  1091. * @param string|resource $contents
  1092. * @param Config $config
  1093. *
  1094. * @return array|false item info array
  1095. */
  1096. protected function upload($path, $contents, Config $config)
  1097. {
  1098. list ($parentId, $fileName) = $this->splitPath($path);
  1099. $srcDriveFile = $this->getFileObject($path);
  1100. if (is_resource($contents)) {
  1101. $uploadedDriveFile = $this->uploadResourceToGoogleDrive($contents, $parentId, $fileName, $srcDriveFile, $config->get('mimetype'));
  1102. } else {
  1103. $uploadedDriveFile = $this->uploadStringToGoogleDrive($contents, $parentId, $fileName, $srcDriveFile, $config->get('mimetype'));
  1104. }
  1105.  
  1106. return $this->normaliseUploadedFile($uploadedDriveFile, $path, $config->get('visibility'));
  1107. }
  1108.  
  1109. /**
  1110. * Detect the largest chunk size that can be used for uploading a file
  1111. *
  1112. * @return int
  1113. */
  1114. protected function detectChunkSizeBytes()
  1115. {
  1116. // Max and default chunk size of 100MB
  1117. $chunkSizeBytes = 100 * 1024 * 1024;
  1118. $memoryLimit = $this->getIniBytes('memory_limit');
  1119. if ($memoryLimit > 0) {
  1120. $availableMemory = $memoryLimit - $this->getMemoryUsedBytes();
  1121. /*
  1122. * We need some breathing room, so we only take 1/4th of the available memory for use in chunking (the divide by 4 does this).
  1123. * The chunk size must be a multiple of 256KB(262144).
  1124. * An example of why we need the breathing room is detecting the mime type for a file that is just small enough to fit into one chunk.
  1125. * In this scenario, we send the entire file off as a string to have the mime type detected. Unfortunately, this leads to the entire
  1126. * file being loaded into memory again, separately from the copy we're holding.
  1127. */
  1128. $chunkSizeBytes = max(262144, min($chunkSizeBytes, floor($availableMemory / 4 / 262144) * 262144));
  1129. }
  1130.  
  1131. return (int)$chunkSizeBytes;
  1132. }
  1133.  
  1134. /**
  1135. * Normalise a Drive File that has been created
  1136. *
  1137. * @param Google_Service_Drive_DriveFile $uploadedFile
  1138. * @param string $localPath
  1139. * @param string $visibility
  1140. * @return array|bool
  1141. */
  1142. protected function normaliseUploadedFile($uploadedFile, $localPath, $visibility)
  1143. {
  1144. list ($parentId, $fileName) = $this->splitPath($localPath);
  1145.  
  1146. if (!($uploadedFile instanceof Google_Service_Drive_DriveFile)) {
  1147. return false;
  1148. }
  1149.  
  1150. $this->cacheFileObjects[$uploadedFile->getId()] = $uploadedFile;
  1151. if (! $this->getFileObject($localPath)) {
  1152. $this->cacheFileObjectsByName[$parentId . '/' . $fileName] = $uploadedFile;
  1153. }
  1154. $result = $this->normaliseObject($uploadedFile, Util::dirname($localPath));
  1155.  
  1156. if ($visibility && $this->setVisibility($localPath, $visibility)) {
  1157. $result['visibility'] = $visibility;
  1158. }
  1159.  
  1160. return $result;
  1161. }
  1162.  
  1163. /**
  1164. * Upload a PHP resource stream to Google Drive
  1165. *
  1166. * @param resource $resource
  1167. * @param string $parentId
  1168. * @param string $fileName
  1169. * @param string $mime
  1170. * @return bool|Google_Service_Drive_DriveFile
  1171. */
  1172. protected function uploadResourceToGoogleDrive($resource, $parentId, $fileName, $srcDriveFile, $mime)
  1173. {
  1174. $chunkSizeBytes = $this->detectChunkSizeBytes();
  1175. $fileSize = $this->getFileSizeBytes($resource);
  1176.  
  1177. if ($fileSize <= $chunkSizeBytes) {
  1178. // If the resource fits in a single chunk, we'll just upload it in a single request
  1179. return $this->uploadStringToGoogleDrive(stream_get_contents($resource), $parentId, $fileName, $srcDriveFile, $mime);
  1180. }
  1181.  
  1182. $client = $this->service->getClient();
  1183. // Call the API with the media upload, defer so it doesn't immediately return.
  1184. $client->setDefer(true);
  1185. $request = $this->ensureDriveFileExists('', $parentId, $fileName, $srcDriveFile, $mime);
  1186. $client->setDefer(false);
  1187. $media = $this->getMediaFileUpload($client, $request, $mime, $chunkSizeBytes);
  1188. $media->setFileSize($fileSize);
  1189.  
  1190. // Upload chunks until we run out of file to upload; $status will be false until the process is complete.
  1191. $status = false;
  1192. while (! $status && ! feof($resource)) {
  1193. $chunk = $this->readFileChunk($resource, $chunkSizeBytes);
  1194. $status = $media->nextChunk($chunk);
  1195. }
  1196.  
  1197. // The final value of $status will be the data from the API for the object that has been uploaded.
  1198. return $status;
  1199. }
  1200.  
  1201. /**
  1202. * Upload a string to Google Drive
  1203. *
  1204. * @param string $contents
  1205. * @param string $parentId
  1206. * @param string $fileName
  1207. * @param string $mime
  1208. * @return Google_Service_Drive_DriveFile
  1209. */
  1210. protected function uploadStringToGoogleDrive($contents, $parentId, $fileName, $srcDriveFile, $mime)
  1211. {
  1212. return $this->ensureDriveFileExists($contents, $parentId, $fileName, $srcDriveFile, $mime);
  1213. }
  1214.  
  1215. /**
  1216. * Ensure that a file exists on Google Drive by creating it if it doesn't exist or updating it if it does
  1217. *
  1218. * @param string $contents
  1219. * @param string $parentId
  1220. * @param string $fileName
  1221. * @param string $mime
  1222. * @return Google_Service_Drive_DriveFile
  1223. */
  1224. protected function ensureDriveFileExists($contents, $parentId, $fileName, $srcDriveFile, $mime)
  1225. {
  1226. if (! $mime) {
  1227. $mime = Util::guessMimeType($fileName, $contents);
  1228. }
  1229.  
  1230. $driveFile = new Google_Service_Drive_DriveFile();
  1231.  
  1232. $mode = 'update';
  1233. if (! $srcDriveFile) {
  1234. $mode = 'insert';
  1235. $driveFile->setName($fileName);
  1236. $driveFile->setParents([$parentId]);
  1237. }
  1238.  
  1239. $driveFile->setMimeType($mime);
  1240.  
  1241. $params = ['fields' => $this->fetchfieldsGet];
  1242. if ($contents) {
  1243. $params['data'] = $contents;
  1244. $params['uploadType'] = 'media';
  1245. }
  1246. if ($mode === 'insert') {
  1247. $retrievedDriveFile = $this->service->files->create($driveFile, $this->applyDefaultParams($params, 'files.create'));
  1248. } else {
  1249. $retrievedDriveFile = $this->service->files->update(
  1250. $srcDriveFile->getId(),
  1251. $driveFile,
  1252. $this->applyDefaultParams($params, 'files.update')
  1253. );
  1254. }
  1255.  
  1256. return $retrievedDriveFile;
  1257. }
  1258.  
  1259. /**
  1260. * Read file chunk
  1261. *
  1262. * @param resource $handle
  1263. * @param int $chunkSize
  1264. *
  1265. * @return string
  1266. */
  1267. protected function readFileChunk($handle, $chunkSize)
  1268. {
  1269. $byteCount = 0;
  1270. $giantChunk = '';
  1271. while (! feof($handle)) {
  1272. // fread will never return more than 8192 bytes if the stream is read buffered and it does not represent a plain file
  1273. // An example of a read buffered file is when reading from a URL
  1274. $chunk = fread($handle, 8192);
  1275. $byteCount += strlen($chunk);
  1276. $giantChunk .= $chunk;
  1277. if ($byteCount >= $chunkSize) {
  1278. return $giantChunk;
  1279. }
  1280. }
  1281. return $giantChunk;
  1282. }
  1283.  
  1284. /**
  1285. * Return bytes from php.ini value
  1286. *
  1287. * @param string $iniName
  1288. * @param string $val
  1289. * @return number
  1290. */
  1291. protected function getIniBytes($iniName = '', $val = '')
  1292. {
  1293. if ($iniName !== '') {
  1294. $val = ini_get($iniName);
  1295. if ($val === false) {
  1296. return 0;
  1297. }
  1298. }
  1299. $val = trim($val, "bB \t\n\r\0\x0B");
  1300. $last = strtolower($val[strlen($val) - 1]);
  1301. $val = (int)$val;
  1302. switch ($last) {
  1303. case 't':
  1304. $val *= 1024;
  1305. case 'g':
  1306. $val *= 1024;
  1307. case 'm':
  1308. $val *= 1024;
  1309. case 'k':
  1310. $val *= 1024;
  1311. }
  1312. return $val;
  1313. }
  1314.  
  1315. /**
  1316. * Return the number of memory bytes allocated to PHP
  1317. *
  1318. * @return int
  1319. */
  1320. protected function getMemoryUsedBytes()
  1321. {
  1322. return memory_get_usage(true);
  1323. }
  1324.  
  1325. /**
  1326. * Get the size of a file resource
  1327. *
  1328. * @param $resource
  1329. *
  1330. * @return int
  1331. */
  1332. protected function getFileSizeBytes($resource)
  1333. {
  1334. return fstat($resource)['size'];
  1335. }
  1336.  
  1337. /**
  1338. * Get a MediaFileUpload
  1339. *
  1340. * @param $client
  1341. * @param $request
  1342. * @param $mime
  1343. * @param $chunkSizeBytes
  1344. *
  1345. * @return Google_Http_MediaFileUpload
  1346. */
  1347. protected function getMediaFileUpload($client, $request, $mime, $chunkSizeBytes)
  1348. {
  1349. return new Google_Http_MediaFileUpload($client, $request, $mime, null, true, $chunkSizeBytes);
  1350. }
  1351.  
  1352. /**
  1353. * Apply optional parameters for each command
  1354. *
  1355. * @param array $params The parameters
  1356. * @param string $cmdName The command name
  1357. *
  1358. * @return array
  1359. *
  1360. * @see https://developers.google.com/drive/v3/reference/files
  1361. * @see \Google_Service_Drive_Resource_Files
  1362. */
  1363. protected function applyDefaultParams($params, $cmdName)
  1364. {
  1365. if (isset($this->defaultParams[$cmdName]) && is_array($this->defaultParams[$cmdName])) {
  1366. return array_replace($this->defaultParams[$cmdName], $params);
  1367. } else {
  1368. return $params;
  1369. }
  1370. }
  1371.  
  1372. /**
  1373. * Enables Team Drive support by changing default parameters
  1374. *
  1375. * @return void
  1376. *
  1377. * @see https://developers.google.com/drive/v3/reference/files
  1378. * @see \Google_Service_Drive_Resource_Files
  1379. */
  1380. public function enableTeamDriveSupport()
  1381. {
  1382. $this->defaultParams = array_merge_recursive(
  1383. array_fill_keys([
  1384. 'files.copy', 'files.create', 'files.delete',
  1385. 'files.trash', 'files.get', 'files.list', 'files.update',
  1386. 'files.watch'
  1387. ], ['supportsTeamDrives' => true]),
  1388. $this->defaultParams
  1389. );
  1390. }
  1391.  
  1392. /**
  1393. * Selects Team Drive to operate by changing default parameters
  1394. *
  1395. * @return void
  1396. *
  1397. * @param string $teamDriveId Team Drive id
  1398. * @param string $corpora Corpora value for files.list
  1399. *
  1400. * @see https://developers.google.com/drive/v3/reference/files
  1401. * @see https://developers.google.com/drive/v3/reference/files/list
  1402. * @see \Google_Service_Drive_Resource_Files
  1403. */
  1404. public function setTeamDriveId($teamDriveId, $corpora = 'teamDrive')
  1405. {
  1406. $this->enableTeamDriveSupport();
  1407. $this->defaultParams = array_merge_recursive($this->defaultParams, [
  1408. 'files.list' => [
  1409. 'corpora' => $corpora,
  1410. 'includeTeamDriveItems' => true,
  1411. 'teamDriveId' => $teamDriveId
  1412. ]
  1413. ]);
  1414.  
  1415. if ($this->root === 'root') {
  1416. $this->setPathPrefix($teamDriveId);
  1417. $this->root = $teamDriveId;
  1418. }
  1419. }
  1420. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement