elFinderVolumeDropbox2.class.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
  1. <?php
  2. use Kunnu\Dropbox\Dropbox;
  3. use Kunnu\Dropbox\DropboxApp;
  4. use Kunnu\Dropbox\DropboxFile;
  5. use Kunnu\Dropbox\Exceptions\DropboxClientException;
  6. use Kunnu\Dropbox\Models\FileMetadata;
  7. use Kunnu\Dropbox\Models\FolderMetadata;
  8. /**
  9. * Simple elFinder driver for Dropbox
  10. * kunalvarma05/dropbox-php-sdk:0.1.5 or above.
  11. *
  12. * @author Naoki Sawada
  13. **/
  14. class elFinderVolumeDropbox2 extends elFinderVolumeDriver
  15. {
  16. /**
  17. * Driver id
  18. * Must be started from letter and contains [a-z0-9]
  19. * Used as part of volume id.
  20. *
  21. * @var string
  22. **/
  23. protected $driverId = 'db';
  24. /**
  25. * Dropbox service object.
  26. *
  27. * @var object
  28. **/
  29. protected $service = null;
  30. /**
  31. * Fetch options.
  32. *
  33. * @var string
  34. */
  35. private $FETCH_OPTIONS = [];
  36. /**
  37. * Directory for tmp files
  38. * If not set driver will try to use tmbDir as tmpDir.
  39. *
  40. * @var string
  41. **/
  42. protected $tmp = '';
  43. /**
  44. * Net mount key.
  45. *
  46. * @var string
  47. **/
  48. public $netMountKey = '';
  49. /**
  50. * Constructor
  51. * Extend options with required fields.
  52. *
  53. * @author Naoki Sawada
  54. **/
  55. public function __construct()
  56. {
  57. $opts = [
  58. 'app_key' => '',
  59. 'app_secret' => '',
  60. 'access_token' => '',
  61. 'aliasFormat' => '%s@Dropbox',
  62. 'path' => '/',
  63. 'separator' => '/',
  64. 'acceptedName' => '#^[^/\\?*:|"<>]*[^./\\?*:|"<>]$#',
  65. 'rootCssClass' => 'elfinder-navbar-root-dropbox',
  66. 'publishPermission' => [
  67. 'requested_visibility' => 'public',
  68. //'link_password' => '',
  69. //'expires' => '',
  70. ],
  71. 'getThumbSize' => 'medium', // Available sizes: 'thumb', 'small', 'medium', 'large', 'huge'
  72. ];
  73. $this->options = array_merge($this->options, $opts);
  74. $this->options['mimeDetect'] = 'internal';
  75. }
  76. /*********************************************************************/
  77. /* ORIGINAL FUNCTIONS */
  78. /*********************************************************************/
  79. /**
  80. * Get Parent ID, Item ID, Parent Path as an array from path.
  81. *
  82. * @param string $path
  83. *
  84. * @return array
  85. */
  86. protected function _db_splitPath($path)
  87. {
  88. $path = trim($path, '/');
  89. if ($path === '') {
  90. $dirname = '/';
  91. $basename = '';
  92. } else {
  93. $pos = strrpos($path, '/');
  94. if ($pos === false) {
  95. $dirname = '/';
  96. $basename = $path;
  97. } else {
  98. $dirname = '/' . substr($path, 0, $pos);
  99. $basename = substr($path, $pos + 1);
  100. }
  101. }
  102. return [$dirname, $basename];
  103. }
  104. /**
  105. * Get dat(Dropbox metadata) from Dropbox.
  106. *
  107. * @param string $path
  108. *
  109. * @return boolean|object Dropbox metadata
  110. */
  111. private function _db_getFile($path)
  112. {
  113. if ($path === '/') {
  114. return true;
  115. }
  116. $res = false;
  117. try {
  118. $file = $this->service->getMetadata($path, $this->FETCH_OPTIONS);
  119. if ($file instanceof FolderMetadata || $file instanceof FileMetadata) {
  120. $res = $file;
  121. }
  122. return $res;
  123. } catch (DropboxClientException $e) {
  124. return false;
  125. }
  126. }
  127. /**
  128. * Parse line from Dropbox metadata output and return file stat (array).
  129. *
  130. * @param object $raw line from ftp_rawlist() output
  131. *
  132. * @return array
  133. * @author Naoki Sawada
  134. **/
  135. protected function _db_parseRaw($raw)
  136. {
  137. $stat = [];
  138. $isFolder = false;
  139. if ($raw === true) {
  140. // root folder
  141. $isFolder = true;
  142. $stat['name'] = '';
  143. $stat['iid'] = '0';
  144. }
  145. $data = [];
  146. if (is_object($raw)) {
  147. $isFolder = $raw instanceof FolderMetadata;
  148. $data = $raw->getData();
  149. } elseif (is_array($raw)) {
  150. $isFolder = $raw['.tag'] === 'folder';
  151. $data = $raw;
  152. }
  153. if (isset($data['path_lower'])) {
  154. $stat['path'] = $data['path_lower'];
  155. }
  156. if (isset($data['name'])) {
  157. $stat['name'] = $data['name'];
  158. }
  159. if (isset($data['id'])) {
  160. $stat['iid'] = substr($data['id'], 3);
  161. }
  162. if ($isFolder) {
  163. $stat['mime'] = 'directory';
  164. $stat['size'] = 0;
  165. $stat['ts'] = 0;
  166. $stat['dirs'] = -1;
  167. } else {
  168. $stat['size'] = isset($data['size']) ? (int)$data['size'] : 0;
  169. if (isset($data['server_modified'])) {
  170. $stat['ts'] = strtotime($data['server_modified']);
  171. } elseif (isset($data['client_modified'])) {
  172. $stat['ts'] = strtotime($data['client_modified']);
  173. } else {
  174. $stat['ts'] = 0;
  175. }
  176. $stat['url'] = '1';
  177. }
  178. return $stat;
  179. }
  180. /**
  181. * Get thumbnail from Dropbox.
  182. *
  183. * @param string $path
  184. * @param string $size
  185. *
  186. * @return string | boolean
  187. */
  188. protected function _db_getThumbnail($path)
  189. {
  190. try {
  191. return $this->service->getThumbnail($path, $this->options['getThumbSize'])->getContents();
  192. } catch (DropboxClientException $e) {
  193. return false;
  194. }
  195. }
  196. /**
  197. * Join dir name and file name(display name) and retur full path.
  198. *
  199. * @param string $dir
  200. * @param string $displayName
  201. *
  202. * @return string
  203. */
  204. protected function _db_joinName($dir, $displayName)
  205. {
  206. return rtrim($dir, '/') . '/' . $displayName;
  207. }
  208. /**
  209. * Get OAuth2 access token form OAuth1 tokens.
  210. *
  211. * @param string $app_key
  212. * @param string $app_secret
  213. * @param string $oauth1_token
  214. * @param string $oauth1_secret
  215. *
  216. * @return string|false
  217. */
  218. public static function getTokenFromOauth1($app_key, $app_secret, $oauth1_token, $oauth1_secret)
  219. {
  220. $data = [
  221. 'oauth1_token' => $oauth1_token,
  222. 'oauth1_token_secret' => $oauth1_secret,
  223. ];
  224. $auth = base64_encode($app_key . ':' . $app_secret);
  225. $ch = curl_init('https://api.dropboxapi.com/2/auth/token/from_oauth1');
  226. curl_setopt($ch, CURLOPT_POST, true);
  227. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  228. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  229. 'Content-Type: application/json',
  230. 'Authorization: Basic ' . $auth,
  231. ]);
  232. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  233. $result = curl_exec($ch);
  234. curl_close($ch);
  235. $res = $result ? json_decode($result, true) : [];
  236. return isset($res['oauth2_token']) ? $res['oauth2_token'] : false;
  237. }
  238. /*********************************************************************/
  239. /* EXTENDED FUNCTIONS */
  240. /*********************************************************************/
  241. /**
  242. * Prepare
  243. * Call from elFinder::netmout() before volume->mount().
  244. *
  245. * @return array
  246. * @author Naoki Sawada
  247. **/
  248. public function netmountPrepare($options)
  249. {
  250. if (empty($options['app_key']) && defined('ELFINDER_DROPBOX_APPKEY')) {
  251. $options['app_key'] = ELFINDER_DROPBOX_APPKEY;
  252. }
  253. if (empty($options['app_secret']) && defined('ELFINDER_DROPBOX_APPSECRET')) {
  254. $options['app_secret'] = ELFINDER_DROPBOX_APPSECRET;
  255. }
  256. if (!isset($options['pass'])) {
  257. $options['pass'] = '';
  258. }
  259. try {
  260. $this->session->start();
  261. $app = new DropboxApp($options['app_key'], $options['app_secret']);
  262. $dropbox = new Dropbox($app);
  263. $authHelper = $dropbox->getAuthHelper();
  264. if ($options['pass'] === 'reauth') {
  265. $options['pass'] = '';
  266. $this->session->set('Dropbox2AuthParams', [])->set('Dropbox2Tokens', []);
  267. } elseif ($options['pass'] === 'dropbox2') {
  268. $options['pass'] = '';
  269. }
  270. $options = array_merge($this->session->get('Dropbox2AuthParams', []), $options);
  271. if (!isset($options['tokens'])) {
  272. $options['tokens'] = $this->session->get('Dropbox2Tokens', []);
  273. $this->session->remove('Dropbox2Tokens');
  274. }
  275. $aToken = $options['tokens'];
  276. if (!is_array($aToken) || !isset($aToken['access_token'])) {
  277. $aToken = [];
  278. }
  279. $service = null;
  280. if ($aToken) {
  281. try {
  282. $dropbox->setAccessToken($aToken['access_token']);
  283. $this->session->set('Dropbox2AuthParams', $options);
  284. } catch (DropboxClientException $e) {
  285. $aToken = [];
  286. $options['tokens'] = [];
  287. if ($options['user'] !== 'init') {
  288. $this->session->set('Dropbox2AuthParams', $options);
  289. return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
  290. }
  291. }
  292. }
  293. if ($options['user'] === 'init') {
  294. if (empty($options['url'])) {
  295. $options['url'] = elFinder::getConnectorUrl();
  296. }
  297. $callback = $options['url']
  298. . '?cmd=netmount&protocol=dropbox2&host=1';
  299. if (!$aToken && empty($_GET['code'])) {
  300. $url = $authHelper->getAuthUrl($callback);
  301. $html = '<input id="elf-volumedriver-dropbox2-host-btn" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" value="{msg:btnApprove}" type="button" onclick="window.open(\'' . $url . '\')">';
  302. $html .= '<script>
  303. $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", {protocol: "dropbox2", mode: "makebtn"});
  304. </script>';
  305. if (empty($options['pass']) && $options['host'] !== '1') {
  306. $options['pass'] = 'return';
  307. $this->session->set('Dropbox2AuthParams', $options);
  308. return ['exit' => true, 'body' => $html];
  309. } else {
  310. $out = [
  311. 'node' => $options['id'],
  312. 'json' => '{"protocol": "dropbox2", "mode": "makebtn", "body" : "' . str_replace($html, '"', '\\"') . '", "error" : "' . elFinder::ERROR_ACCESS_DENIED . '"}',
  313. 'bind' => 'netmount',
  314. ];
  315. return ['exit' => 'callback', 'out' => $out];
  316. }
  317. } else {
  318. if (!empty($_GET['code']) && isset($_GET['state'])) {
  319. $tokenObj = $authHelper->getAccessToken($_GET['code'], $_GET['state'], $callback);
  320. $options['tokens'] = [
  321. 'access_token' => $tokenObj->getToken(),
  322. 'uid' => $tokenObj->getUid(),
  323. ];
  324. $this->session->set('Dropbox2Tokens', $options['tokens'])->set('Dropbox2AuthParams', $options);
  325. $out = [
  326. 'node' => $options['id'],
  327. 'json' => '{"protocol": "dropbox2", "mode": "done", "reset": 1}',
  328. 'bind' => 'netmount',
  329. ];
  330. return ['exit' => 'callback', 'out' => $out];
  331. }
  332. $path = $options['path'];
  333. $folders = [];
  334. $listFolderContents = $dropbox->listFolder($path);
  335. $items = $listFolderContents->getItems();
  336. foreach ($items as $item) {
  337. $data = $item->getData();
  338. if ($data['.tag'] === 'folder') {
  339. $folders[$data['path_lower']] = $data['name'];
  340. }
  341. }
  342. natcasesort($folders);
  343. if ($options['pass'] === 'folders') {
  344. return ['exit' => true, 'folders' => $folders];
  345. }
  346. $folders = ['/' => '/'] + $folders;
  347. $folders = json_encode($folders);
  348. $json = '{"protocol": "dropbox2", "mode": "done", "folders": ' . $folders . '}';
  349. $options['pass'] = 'return';
  350. $html = 'Dropbox.com';
  351. $html .= '<script>
  352. $("#' . $options['id'] . '").elfinder("instance").trigger("netmount", ' . $json . ');
  353. </script>';
  354. $this->session->set('Dropbox2AuthParams', $options);
  355. return ['exit' => true, 'body' => $html];
  356. }
  357. }
  358. } catch (DropboxClientException $e) {
  359. $this->session->remove('Dropbox2AuthParams')->remove('Dropbox2Tokens');
  360. if (empty($options['pass'])) {
  361. return ['exit' => true, 'body' => '{msg:' . elFinder::ERROR_ACCESS_DENIED . '}' . ' ' . $e->getMessage()];
  362. } else {
  363. return ['exit' => true, 'error' => [elFinder::ERROR_ACCESS_DENIED, $e->getMessage()]];
  364. }
  365. }
  366. if (!$aToken) {
  367. return ['exit' => true, 'error' => elFinder::ERROR_REAUTH_REQUIRE];
  368. }
  369. if ($options['path'] === 'root') {
  370. $options['path'] = '/';
  371. }
  372. try {
  373. if ($options['path'] !== '/') {
  374. $file = $dropbox->getMetadata($options['path']);
  375. $name = $file->getName();
  376. } else {
  377. $name = 'root';
  378. }
  379. $options['alias'] = sprintf($this->options['aliasFormat'], $name);
  380. } catch (DropboxClientException $e) {
  381. return ['exit' => true, 'error' => $e->getMessage()];
  382. }
  383. foreach (['host', 'user', 'pass', 'id', 'offline'] as $key) {
  384. unset($options[$key]);
  385. }
  386. return $options;
  387. }
  388. /**
  389. * process of on netunmount
  390. * Drop `Dropbox` & rm thumbs.
  391. *
  392. * @param array $options
  393. *
  394. * @return bool
  395. */
  396. public function netunmount($netVolumes, $key)
  397. {
  398. if ($tmbs = glob(rtrim($this->options['tmbPath'], '\\/') . DIRECTORY_SEPARATOR . $this->driverId . '_' . $this->options['tokens']['uid'] . '*.png')) {
  399. foreach ($tmbs as $file) {
  400. unlink($file);
  401. }
  402. }
  403. return true;
  404. }
  405. /*********************************************************************/
  406. /* INIT AND CONFIGURE */
  407. /*********************************************************************/
  408. /**
  409. * Prepare Dropbox connection
  410. * Connect to remote server and check if credentials are correct, if so, store the connection id in $this->service.
  411. *
  412. * @return bool
  413. * @author Naoki Sawada
  414. **/
  415. protected function init()
  416. {
  417. if (empty($this->options['app_key'])) {
  418. if (defined('ELFINDER_DROPBOX_APPKEY') && ELFINDER_DROPBOX_APPKEY) {
  419. $this->options['app_key'] = ELFINDER_DROPBOX_APPKEY;
  420. } else {
  421. return $this->setError('Required option "app_key" is undefined.');
  422. }
  423. }
  424. if (empty($this->options['app_secret'])) {
  425. if (defined('ELFINDER_DROPBOX_APPSECRET') && ELFINDER_DROPBOX_APPSECRET) {
  426. $this->options['app_secret'] = ELFINDER_DROPBOX_APPSECRET;
  427. } else {
  428. return $this->setError('Required option "app_secret" is undefined.');
  429. }
  430. }
  431. if (isset($this->options['tokens']) && is_array($this->options['tokens']) && !empty($this->options['tokens']['access_token'])) {
  432. $this->options['access_token'] = $this->options['tokens']['access_token'];
  433. }
  434. if (!$this->options['access_token']) {
  435. return $this->setError('Required option "access_token" or "refresh_token" is undefined.');
  436. }
  437. try {
  438. // make net mount key for network mount
  439. $aToken = $this->options['access_token'];
  440. $this->netMountKey = md5($aToken . '-' . $this->options['path']);
  441. $errors = [];
  442. if (!$this->service) {
  443. $app = new DropboxApp($this->options['app_key'], $this->options['app_secret'], $aToken);
  444. $this->service = new Dropbox($app);
  445. }
  446. } catch (DropboxClientException $e) {
  447. $errors[] = 'Dropbox error: ' . $e->getMessage();
  448. } catch (Exception $e) {
  449. $errors[] = $e->getMessage();
  450. }
  451. if (!$this->service) {
  452. $errors[] = 'Dropbox Service could not be loaded.';
  453. return $this->setError($errors);
  454. }
  455. // normalize root path
  456. $this->options['path'] = strtolower($this->options['path']);
  457. if ($this->options['path'] == 'root') {
  458. $this->options['path'] = '/';
  459. }
  460. $this->root = $this->options['path'] = $this->_normpath($this->options['path']);
  461. if (empty($this->options['alias'])) {
  462. $this->options['alias'] = sprintf($this->options['aliasFormat'], ($this->options['path'] === '/') ? 'root' : $this->_basename($this->options['path']));
  463. }
  464. $this->rootName = $this->options['alias'];
  465. if (!empty($this->options['tmpPath'])) {
  466. if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'])) && is_writable($this->options['tmpPath'])) {
  467. $this->tmp = $this->options['tmpPath'];
  468. }
  469. }
  470. if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
  471. $this->tmp = $tmp;
  472. }
  473. // This driver dose not support `syncChkAsTs`
  474. $this->options['syncChkAsTs'] = false;
  475. // 'lsPlSleep' minmum 10 sec
  476. $this->options['lsPlSleep'] = max(10, $this->options['lsPlSleep']);
  477. // enable command archive
  478. $this->options['useRemoteArchive'] = true;
  479. return true;
  480. }
  481. /**
  482. * Configure after successfull mount.
  483. *
  484. * @author Naoki Sawada
  485. * @throws elFinderAbortException
  486. */
  487. protected function configure()
  488. {
  489. parent::configure();
  490. // fallback of $this->tmp
  491. if (!$this->tmp && $this->tmbPathWritable) {
  492. $this->tmp = $this->tmbPath;
  493. }
  494. if ($this->isMyReload()) {
  495. //$this->_db_getDirectoryData(false);
  496. }
  497. }
  498. /*********************************************************************/
  499. /* FS API */
  500. /*********************************************************************/
  501. /**
  502. * Close opened connection.
  503. **/
  504. public function umount()
  505. {
  506. }
  507. /**
  508. * Cache dir contents.
  509. *
  510. * @param string $path dir path
  511. *
  512. * @return
  513. * @author Naoki Sawada
  514. */
  515. protected function cacheDir($path)
  516. {
  517. $this->dirsCache[$path] = [];
  518. $hasDir = false;
  519. $res = $this->service->listFolder($path, $this->FETCH_OPTIONS);
  520. if ($res) {
  521. $items = $res->getItems()->all();
  522. foreach ($items as $raw) {
  523. if ($stat = $this->_db_parseRaw($raw)) {
  524. $mountPath = $this->_joinPath($path, $stat['name']);
  525. $stat = $this->updateCache($mountPath, $stat);
  526. if (empty($stat['hidden']) && $path !== $mountPath) {
  527. if (!$hasDir && $stat['mime'] === 'directory') {
  528. $hasDir = true;
  529. }
  530. $this->dirsCache[$path][] = $mountPath;
  531. }
  532. }
  533. }
  534. }
  535. if (isset($this->sessionCache['subdirs'])) {
  536. $this->sessionCache['subdirs'][$path] = $hasDir;
  537. }
  538. return $this->dirsCache[$path];
  539. }
  540. /**
  541. * Recursive files search.
  542. *
  543. * @param string $path dir path
  544. * @param string $q search string
  545. * @param array $mimes
  546. *
  547. * @return array
  548. * @throws elFinderAbortException
  549. * @author Naoki Sawada
  550. */
  551. protected function doSearch($path, $q, $mimes)
  552. {
  553. if (!empty($this->doSearchCurrentQuery['matchMethod']) || $mimes) {
  554. // has custom match method or mimes, use elFinderVolumeDriver::doSearch()
  555. return parent::doSearch($path, $q, $mimes);
  556. }
  557. $timeout = $this->options['searchTimeout'] ? $this->searchStart + $this->options['searchTimeout'] : 0;
  558. $searchRes = $this->service->search($path, $q, ['start' => 0, 'max_results' => 1000]);
  559. $items = $searchRes->getItems();
  560. $more = $searchRes->hasMoreItems();
  561. while ($more) {
  562. if ($timeout && $timeout < time()) {
  563. $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->_path($path));
  564. break;
  565. }
  566. $searchRes = $this->service->search($path, $q, ['start' => $searchRes->getCursor(), 'max_results' => 1000]);
  567. $more = $searchRes->hasMoreItems();
  568. $items = $items->merge($searchRes->getItems());
  569. }
  570. $result = [];
  571. foreach ($items as $raw) {
  572. if ($stat = $this->_db_parseRaw($raw->getMetadata())) {
  573. $stat = $this->updateCache($stat['path'], $stat);
  574. if (empty($stat['hidden'])) {
  575. $result[] = $stat;
  576. }
  577. }
  578. }
  579. return $result;
  580. }
  581. /**
  582. * Copy file/recursive copy dir only in current volume.
  583. * Return new file path or false.
  584. *
  585. * @param string $src source path
  586. * @param string $dst destination dir path
  587. * @param string $name new file name (optionaly)
  588. *
  589. * @return string|false
  590. * @throws elFinderAbortException
  591. * @author Naoki Sawada
  592. */
  593. protected function copy($src, $dst, $name)
  594. {
  595. $srcStat = $this->stat($src);
  596. $target = $this->_joinPath($dst, $name);
  597. $tgtStat = $this->stat($target);
  598. if ($tgtStat) {
  599. if ($srcStat['mime'] === 'directory') {
  600. return parent::copy($src, $dst, $name);
  601. } else {
  602. $this->_unlink($target);
  603. }
  604. }
  605. $this->clearcache();
  606. if ($res = $this->_copy($src, $dst, $name)) {
  607. $this->added[] = $this->stat($target);
  608. $res = $target;
  609. }
  610. return $res;
  611. }
  612. /**
  613. * Remove file/ recursive remove dir.
  614. *
  615. * @param string $path file path
  616. * @param bool $force try to remove even if file locked
  617. * @param bool $recursive
  618. *
  619. * @return bool
  620. * @throws elFinderAbortException
  621. * @author Naoki Sawada
  622. */
  623. protected function remove($path, $force = false, $recursive = false)
  624. {
  625. $stat = $this->stat($path);
  626. $stat['realpath'] = $path;
  627. $this->rmTmb($stat);
  628. $this->clearcache();
  629. if (empty($stat)) {
  630. return $this->setError(elFinder::ERROR_RM, $this->_path($path), elFinder::ERROR_FILE_NOT_FOUND);
  631. }
  632. if (!$force && !empty($stat['locked'])) {
  633. return $this->setError(elFinder::ERROR_LOCKED, $this->_path($path));
  634. }
  635. if ($stat['mime'] == 'directory') {
  636. if (!$recursive && !$this->_rmdir($path)) {
  637. return $this->setError(elFinder::ERROR_RM, $this->_path($path));
  638. }
  639. } else {
  640. if (!$recursive && !$this->_unlink($path)) {
  641. return $this->setError(elFinder::ERROR_RM, $this->_path($path));
  642. }
  643. }
  644. $this->removed[] = $stat;
  645. return true;
  646. }
  647. /**
  648. * Create thumnbnail and return it's URL on success.
  649. *
  650. * @param string $path file path
  651. * @param $stat
  652. *
  653. * @return string|false
  654. * @throws ImagickException
  655. * @throws elFinderAbortException
  656. * @author Naoki Sawada
  657. */
  658. protected function createTmb($path, $stat)
  659. {
  660. if (!$stat || !$this->canCreateTmb($path, $stat)) {
  661. return false;
  662. }
  663. $name = $this->tmbname($stat);
  664. $tmb = $this->tmbPath . DIRECTORY_SEPARATOR . $name;
  665. // copy image into tmbPath so some drivers does not store files on local fs
  666. if (!$data = $this->_db_getThumbnail($path)) {
  667. return false;
  668. }
  669. if (!file_put_contents($tmb, $data)) {
  670. return false;
  671. }
  672. $tmbSize = $this->tmbSize;
  673. if (($s = getimagesize($tmb)) == false) {
  674. return false;
  675. }
  676. $result = true;
  677. /* If image smaller or equal thumbnail size - just fitting to thumbnail square */
  678. if ($s[0] <= $tmbSize && $s[1] <= $tmbSize) {
  679. $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png');
  680. } else {
  681. if ($this->options['tmbCrop']) {
  682. /* Resize and crop if image bigger than thumbnail */
  683. if (!(($s[0] > $tmbSize && $s[1] <= $tmbSize) || ($s[0] <= $tmbSize && $s[1] > $tmbSize)) || ($s[0] > $tmbSize && $s[1] > $tmbSize)) {
  684. $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, false, 'png');
  685. }
  686. if ($result && ($s = getimagesize($tmb)) != false) {
  687. $x = $s[0] > $tmbSize ? intval(($s[0] - $tmbSize) / 2) : 0;
  688. $y = $s[1] > $tmbSize ? intval(($s[1] - $tmbSize) / 2) : 0;
  689. $result = $this->imgCrop($tmb, $tmbSize, $tmbSize, $x, $y, 'png');
  690. }
  691. } else {
  692. $result = $this->imgResize($tmb, $tmbSize, $tmbSize, true, true, 'png');
  693. }
  694. if ($result) {
  695. $result = $this->imgSquareFit($tmb, $tmbSize, $tmbSize, 'center', 'middle', $this->options['tmbBgColor'], 'png');
  696. }
  697. }
  698. if (!$result) {
  699. unlink($tmb);
  700. return false;
  701. }
  702. return $name;
  703. }
  704. /**
  705. * Return thumbnail file name for required file.
  706. *
  707. * @param array $stat file stat
  708. *
  709. * @return string
  710. * @author Naoki Sawada
  711. **/
  712. protected function tmbname($stat)
  713. {
  714. $name = $this->driverId . '_';
  715. if (isset($this->options['tokens']) && is_array($this->options['tokens'])) {
  716. $name .= $this->options['tokens']['uid'];
  717. }
  718. return $name . md5($stat['iid']) . $stat['ts'] . '.png';
  719. }
  720. /**
  721. * Return content URL (for netmout volume driver)
  722. * If file.url == 1 requests from JavaScript client with XHR.
  723. *
  724. * @param string $hash file hash
  725. * @param array $options options array
  726. *
  727. * @return bool|string
  728. * @author Naoki Sawada
  729. */
  730. public function getContentUrl($hash, $options = [])
  731. {
  732. if (!empty($options['onetime']) && $this->options['onetimeUrl']) {
  733. return parent::getContentUrl($hash, $options);
  734. }
  735. if (!empty($options['temporary'])) {
  736. // try make temporary file
  737. $url = parent::getContentUrl($hash, $options);
  738. if ($url) {
  739. return $url;
  740. }
  741. }
  742. $file = $this->file($hash);
  743. if (($file = $this->file($hash)) !== false && (!$file['url'] || $file['url'] == 1)) {
  744. $path = $this->decode($hash);
  745. $url = '';
  746. try {
  747. $res = $this->service->postToAPI('/sharing/list_shared_links', ['path' => $path, 'direct_only' => true])->getDecodedBody();
  748. if ($res && !empty($res['links'])) {
  749. foreach ($res['links'] as $link) {
  750. if (isset($link['link_permissions'])
  751. && isset($link['link_permissions']['requested_visibility'])
  752. && $link['link_permissions']['requested_visibility']['.tag'] === $this->options['publishPermission']['requested_visibility']) {
  753. $url = $link['url'];
  754. break;
  755. }
  756. }
  757. }
  758. if (!$url) {
  759. $res = $this->service->postToAPI('/sharing/create_shared_link_with_settings', ['path' => $path, 'settings' => $this->options['publishPermission']])->getDecodedBody();
  760. if (isset($res['url'])) {
  761. $url = $res['url'];
  762. }
  763. }
  764. if ($url) {
  765. $url = str_replace('www.dropbox.com', 'dl.dropboxusercontent.com', $url);
  766. $url = str_replace('?dl=0', '', $url);
  767. return $url;
  768. }
  769. } catch (DropboxClientException $e) {
  770. return $this->setError('Dropbox error: ' . $e->getMessage());
  771. }
  772. }
  773. return false;
  774. }
  775. /**
  776. * Return debug info for client.
  777. *
  778. * @return array
  779. **/
  780. public function debug()
  781. {
  782. $res = parent::debug();
  783. if (isset($this->options['tokens']) && !empty($this->options['tokens']['uid'])) {
  784. $res['Dropbox uid'] = $this->options['tokens']['uid'];
  785. $res['access_token'] = $this->options['tokens']['access_token'];
  786. }
  787. return $res;
  788. }
  789. /*********************** paths/urls *************************/
  790. /**
  791. * Return parent directory path.
  792. *
  793. * @param string $path file path
  794. *
  795. * @return string
  796. * @author Naoki Sawada
  797. **/
  798. protected function _dirname($path)
  799. {
  800. list($dirname) = $this->_db_splitPath($path);
  801. return $dirname;
  802. }
  803. /**
  804. * Return file name.
  805. *
  806. * @param string $path file path
  807. *
  808. * @return string
  809. * @author Naoki Sawada
  810. **/
  811. protected function _basename($path)
  812. {
  813. list(, $basename) = $this->_db_splitPath($path);
  814. return $basename;
  815. }
  816. /**
  817. * Join dir name and file name and retur full path.
  818. *
  819. * @param string $dir
  820. * @param string $name
  821. *
  822. * @return string
  823. * @author Dmitry (dio) Levashov
  824. **/
  825. protected function _joinPath($dir, $name)
  826. {
  827. return rtrim($dir, '/') . '/' . strtolower($name);
  828. }
  829. /**
  830. * Return normalized path, this works the same as os.path.normpath() in Python.
  831. *
  832. * @param string $path path
  833. *
  834. * @return string
  835. * @author Naoki Sawada
  836. **/
  837. protected function _normpath($path)
  838. {
  839. return '/' . ltrim($path, '/');
  840. }
  841. /**
  842. * Return file path related to root dir.
  843. *
  844. * @param string $path file path
  845. *
  846. * @return string
  847. * @author Dmitry (dio) Levashov
  848. **/
  849. protected function _relpath($path)
  850. {
  851. if ($path === $this->root) {
  852. return '';
  853. } else {
  854. return ltrim(substr($path, strlen($this->root)), '/');
  855. }
  856. }
  857. /**
  858. * Convert path related to root dir into real path.
  859. *
  860. * @param string $path file path
  861. *
  862. * @return string
  863. * @author Naoki Sawada
  864. **/
  865. protected function _abspath($path)
  866. {
  867. if ($path === '/') {
  868. return $this->root;
  869. } else {
  870. return $this->_joinPath($this->root, $path);
  871. }
  872. }
  873. /**
  874. * Return fake path started from root dir.
  875. *
  876. * @param string $path file path
  877. *
  878. * @return string
  879. * @author Naoki Sawada
  880. **/
  881. protected function _path($path)
  882. {
  883. $path = $this->_normpath(substr($path, strlen($this->root)));
  884. return $path;
  885. }
  886. /**
  887. * Return true if $path is children of $parent.
  888. *
  889. * @param string $path path to check
  890. * @param string $parent parent path
  891. *
  892. * @return bool
  893. * @author Naoki Sawada
  894. **/
  895. protected function _inpath($path, $parent)
  896. {
  897. return $path == $parent || strpos($path, $parent . '/') === 0;
  898. }
  899. /***************** file stat ********************/
  900. /**
  901. * Return stat for given path.
  902. * Stat contains following fields:
  903. * - (int) size file size in b. required
  904. * - (int) ts file modification time in unix time. required
  905. * - (string) mime mimetype. required for folders, others - optionally
  906. * - (bool) read read permissions. required
  907. * - (bool) write write permissions. required
  908. * - (bool) locked is object locked. optionally
  909. * - (bool) hidden is object hidden. optionally
  910. * - (string) alias for symlinks - link target path relative to root path. optionally
  911. * - (string) target for symlinks - link target path. optionally.
  912. * If file does not exists - returns empty array or false.
  913. *
  914. * @param string $path file path
  915. *
  916. * @return array|false
  917. * @author Dmitry (dio) Levashov
  918. **/
  919. protected function _stat($path)
  920. {
  921. if ($raw = $this->_db_getFile($path)) {
  922. return $this->_db_parseRaw($raw);
  923. }
  924. return false;
  925. }
  926. /**
  927. * Return true if path is dir and has at least one childs directory.
  928. *
  929. * @param string $path dir path
  930. *
  931. * @return bool
  932. * @author Naoki Sawada
  933. **/
  934. protected function _subdirs($path)
  935. {
  936. $hasdir = false;
  937. try {
  938. $res = $this->service->listFolder($path);
  939. if ($res) {
  940. $items = $res->getItems();
  941. foreach ($items as $raw) {
  942. if ($raw instanceof FolderMetadata) {
  943. $hasdir = true;
  944. break;
  945. }
  946. }
  947. }
  948. } catch (DropboxClientException $e) {
  949. $this->setError('Dropbox error: ' . $e->getMessage());
  950. }
  951. return $hasdir;
  952. }
  953. /**
  954. * Return object width and height
  955. * Ususaly used for images, but can be realize for video etc...
  956. *
  957. * @param string $path file path
  958. * @param string $mime file mime type
  959. *
  960. * @return string
  961. * @throws ImagickException
  962. * @throws elFinderAbortException
  963. * @author Naoki Sawada
  964. */
  965. protected function _dimensions($path, $mime)
  966. {
  967. if (strpos($mime, 'image') !== 0) {
  968. return '';
  969. }
  970. $ret = '';
  971. if ($data = $this->_getContents($path)) {
  972. $tmp = $this->getTempFile();
  973. file_put_contents($tmp, $data);
  974. $size = getimagesize($tmp);
  975. if ($size) {
  976. $ret = array('dim' => $size[0] . 'x' . $size[1]);
  977. $srcfp = fopen($tmp, 'rb');
  978. if ($subImgLink = $this->getSubstituteImgLink(elFinder::$currentArgs['target'], $size, $srcfp)) {
  979. $ret['url'] = $subImgLink;
  980. }
  981. }
  982. }
  983. return $ret;
  984. }
  985. /******************** file/dir content *********************/
  986. /**
  987. * Return files list in directory.
  988. *
  989. * @param string $path dir path
  990. *
  991. * @return array
  992. * @author Naoki Sawada
  993. **/
  994. protected function _scandir($path)
  995. {
  996. return isset($this->dirsCache[$path])
  997. ? $this->dirsCache[$path]
  998. : $this->cacheDir($path);
  999. }
  1000. /**
  1001. * Open file and return file pointer.
  1002. *
  1003. * @param string $path file path
  1004. * @param bool $write open file for writing
  1005. *
  1006. * @return resource|false
  1007. * @author Naoki Sawada
  1008. **/
  1009. protected function _fopen($path, $mode = 'rb')
  1010. {
  1011. if ($mode === 'rb' || $mode === 'r') {
  1012. if ($link = $this->service->getTemporaryLink($path)) {
  1013. $access_token = $this->service->getAccessToken();
  1014. if ($access_token) {
  1015. $data = array(
  1016. 'target' => $link->getLink(),
  1017. 'headers' => array('Authorization: Bearer ' . $access_token),
  1018. );
  1019. return elFinder::getStreamByUrl($data);
  1020. }
  1021. }
  1022. }
  1023. return false;
  1024. }
  1025. /**
  1026. * Close opened file.
  1027. *
  1028. * @param resource $fp file pointer
  1029. *
  1030. * @return bool
  1031. * @author Naoki Sawada
  1032. **/
  1033. protected function _fclose($fp, $path = '')
  1034. {
  1035. fclose($fp);
  1036. }
  1037. /******************** file/dir manipulations *************************/
  1038. /**
  1039. * Create dir and return created dir path or false on failed.
  1040. *
  1041. * @param string $path parent dir path
  1042. * @param string $name new directory name
  1043. *
  1044. * @return string|bool
  1045. * @author Naoki Sawada
  1046. **/
  1047. protected function _mkdir($path, $name)
  1048. {
  1049. try {
  1050. return $this->service->createFolder($this->_db_joinName($path, $name))->getPathLower();
  1051. } catch (DropboxClientException $e) {
  1052. return $this->setError('Dropbox error: ' . $e->getMessage());
  1053. }
  1054. }
  1055. /**
  1056. * Create file and return it's path or false on failed.
  1057. *
  1058. * @param string $path parent dir path
  1059. * @param string $name new file name
  1060. *
  1061. * @return string|bool
  1062. * @author Naoki Sawada
  1063. **/
  1064. protected function _mkfile($path, $name)
  1065. {
  1066. return $this->_save($this->tmpfile(), $path, $name, []);
  1067. }
  1068. /**
  1069. * Create symlink. FTP driver does not support symlinks.
  1070. *
  1071. * @param string $target link target
  1072. * @param string $path symlink path
  1073. *
  1074. * @return bool
  1075. * @author Naoki Sawada
  1076. **/
  1077. protected function _symlink($target, $path, $name)
  1078. {
  1079. return false;
  1080. }
  1081. /**
  1082. * Copy file into another file.
  1083. *
  1084. * @param string $source source file path
  1085. * @param string $targetDir target directory path
  1086. * @param string $name new file name
  1087. *
  1088. * @return bool
  1089. * @author Naoki Sawada
  1090. **/
  1091. protected function _copy($source, $targetDir, $name)
  1092. {
  1093. try {
  1094. $this->service->copy($source, $this->_db_joinName($targetDir, $name))->getPathLower();
  1095. } catch (DropboxClientException $e) {
  1096. return $this->setError('Dropbox error: ' . $e->getMessage());
  1097. }
  1098. return true;
  1099. }
  1100. /**
  1101. * Move file into another parent dir.
  1102. * Return new file path or false.
  1103. *
  1104. * @param string $source source file path
  1105. * @param string $target target dir path
  1106. * @param string $name file name
  1107. *
  1108. * @return string|bool
  1109. * @author Naoki Sawada
  1110. **/
  1111. protected function _move($source, $targetDir, $name)
  1112. {
  1113. try {
  1114. return $this->service->move($source, $this->_db_joinName($targetDir, $name))->getPathLower();
  1115. } catch (DropboxClientException $e) {
  1116. return $this->setError('Dropbox error: ' . $e->getMessage());
  1117. }
  1118. }
  1119. /**
  1120. * Remove file.
  1121. *
  1122. * @param string $path file path
  1123. *
  1124. * @return bool
  1125. * @author Naoki Sawada
  1126. **/
  1127. protected function _unlink($path)
  1128. {
  1129. try {
  1130. $this->service->delete($path);
  1131. return true;
  1132. } catch (DropboxClientException $e) {
  1133. return $this->setError('Dropbox error: ' . $e->getMessage());
  1134. }
  1135. return true;
  1136. }
  1137. /**
  1138. * Remove dir.
  1139. *
  1140. * @param string $path dir path
  1141. *
  1142. * @return bool
  1143. * @author Naoki Sawada
  1144. **/
  1145. protected function _rmdir($path)
  1146. {
  1147. return $this->_unlink($path);
  1148. }
  1149. /**
  1150. * Create new file and write into it from file pointer.
  1151. * Return new file path or false on error.
  1152. *
  1153. * @param resource $fp file pointer
  1154. * @param string $dir target dir path
  1155. * @param string $name file name
  1156. * @param array $stat file stat (required by some virtual fs)
  1157. *
  1158. * @return bool|string
  1159. * @author Naoki Sawada
  1160. **/
  1161. protected function _save($fp, $path, $name, $stat)
  1162. {
  1163. try {
  1164. $info = stream_get_meta_data($fp);
  1165. if (empty($info['uri']) || preg_match('#^[a-z0-9.-]+://#', $info['uri'])) {
  1166. if ($filepath = $this->getTempFile()) {
  1167. $_fp = fopen($filepath, 'wb');
  1168. stream_copy_to_stream($fp, $_fp);
  1169. fclose($_fp);
  1170. }
  1171. } else {
  1172. $filepath = $info['uri'];
  1173. }
  1174. $dropboxFile = new DropboxFile($filepath);
  1175. if ($name === '') {
  1176. $fullpath = $path;
  1177. } else {
  1178. $fullpath = $this->_db_joinName($path, $name);
  1179. }
  1180. return $this->service->upload($dropboxFile, $fullpath, ['mode' => 'overwrite'])->getPathLower();
  1181. } catch (DropboxClientException $e) {
  1182. return $this->setError('Dropbox error: ' . $e->getMessage());
  1183. }
  1184. }
  1185. /**
  1186. * Get file contents.
  1187. *
  1188. * @param string $path file path
  1189. *
  1190. * @return string|false
  1191. * @author Naoki Sawada
  1192. **/
  1193. protected function _getContents($path)
  1194. {
  1195. $contents = '';
  1196. try {
  1197. $file = $this->service->download($path);
  1198. $contents = $file->getContents();
  1199. } catch (Exception $e) {
  1200. return $this->setError('Dropbox error: ' . $e->getMessage());
  1201. }
  1202. return $contents;
  1203. }
  1204. /**
  1205. * Write a string to a file.
  1206. *
  1207. * @param string $path file path
  1208. * @param string $content new file content
  1209. *
  1210. * @return bool
  1211. * @author Naoki Sawada
  1212. **/
  1213. protected function _filePutContents($path, $content)
  1214. {
  1215. $res = false;
  1216. if ($local = $this->getTempFile($path)) {
  1217. if (file_put_contents($local, $content, LOCK_EX) !== false
  1218. && ($fp = fopen($local, 'rb'))) {
  1219. clearstatcache();
  1220. $name = '';
  1221. $stat = $this->stat($path);
  1222. if ($stat) {
  1223. // keep real name
  1224. $path = $this->_dirname($path);
  1225. $name = $stat['name'];
  1226. }
  1227. $res = $this->_save($fp, $path, $name, []);
  1228. fclose($fp);
  1229. }
  1230. file_exists($local) && unlink($local);
  1231. }
  1232. return $res;
  1233. }
  1234. /**
  1235. * Detect available archivers.
  1236. **/
  1237. protected function _checkArchivers()
  1238. {
  1239. // die('Not yet implemented. (_checkArchivers)');
  1240. return [];
  1241. }
  1242. /**
  1243. * chmod implementation.
  1244. *
  1245. * @return bool
  1246. **/
  1247. protected function _chmod($path, $mode)
  1248. {
  1249. return false;
  1250. }
  1251. /**
  1252. * Unpack archive.
  1253. *
  1254. * @param string $path archive path
  1255. * @param array $arc archiver command and arguments (same as in $this->archivers)
  1256. *
  1257. * @return true
  1258. * @author Dmitry (dio) Levashov
  1259. * @author Alexey Sukhotin
  1260. **/
  1261. protected function _unpack($path, $arc)
  1262. {
  1263. die('Not yet implemented. (_unpack)');
  1264. //return false;
  1265. }
  1266. /**
  1267. * Recursive symlinks search.
  1268. *
  1269. * @param string $path file/dir path
  1270. *
  1271. * @return bool
  1272. * @author Dmitry (dio) Levashov
  1273. **/
  1274. protected function _findSymlinks($path)
  1275. {
  1276. die('Not yet implemented. (_findSymlinks)');
  1277. }
  1278. /**
  1279. * Extract files from archive.
  1280. *
  1281. * @param string $path archive path
  1282. * @param array $arc archiver command and arguments (same as in $this->archivers)
  1283. *
  1284. * @return true
  1285. * @author Dmitry (dio) Levashov,
  1286. * @author Alexey Sukhotin
  1287. **/
  1288. protected function _extract($path, $arc)
  1289. {
  1290. die('Not yet implemented. (_extract)');
  1291. }
  1292. /**
  1293. * Create archive and return its path.
  1294. *
  1295. * @param string $dir target dir
  1296. * @param array $files files names list
  1297. * @param string $name archive name
  1298. * @param array $arc archiver options
  1299. *
  1300. * @return string|bool
  1301. * @author Dmitry (dio) Levashov,
  1302. * @author Alexey Sukhotin
  1303. **/
  1304. protected function _archive($dir, $files, $name, $arc)
  1305. {
  1306. die('Not yet implemented. (_archive)');
  1307. }
  1308. } // END class