elFinderVolumeDropbox2.class.php 43 KB

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