PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/libraries/joomla/filesystem/folder.php

https://bitbucket.org/pastor399/newcastleunifc
PHP | 676 lines | 420 code | 60 blank | 196 comment | 85 complexity | e704c964734e225bab16c2134d928b64 MD5 | raw file
  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage FileSystem
  5. *
  6. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. jimport('joomla.filesystem.path');
  11. /**
  12. * A Folder handling class
  13. *
  14. * @package Joomla.Platform
  15. * @subpackage FileSystem
  16. * @since 11.1
  17. */
  18. abstract class JFolder
  19. {
  20. /**
  21. * Copy a folder.
  22. *
  23. * @param string $src The path to the source folder.
  24. * @param string $dest The path to the destination folder.
  25. * @param string $path An optional base path to prefix to the file names.
  26. * @param string $force Force copy.
  27. * @param boolean $use_streams Optionally force folder/file overwrites.
  28. *
  29. * @return boolean True on success.
  30. *
  31. * @since 11.1
  32. * @throws RuntimeException
  33. */
  34. public static function copy($src, $dest, $path = '', $force = false, $use_streams = false)
  35. {
  36. @set_time_limit(ini_get('max_execution_time'));
  37. $FTPOptions = JClientHelper::getCredentials('ftp');
  38. if ($path)
  39. {
  40. $src = JPath::clean($path . '/' . $src);
  41. $dest = JPath::clean($path . '/' . $dest);
  42. }
  43. // Eliminate trailing directory separators, if any
  44. $src = rtrim($src, DIRECTORY_SEPARATOR);
  45. $dest = rtrim($dest, DIRECTORY_SEPARATOR);
  46. if (!self::exists($src))
  47. {
  48. throw new RuntimeException('Source folder not found', -1);
  49. }
  50. if (self::exists($dest) && !$force)
  51. {
  52. throw new RuntimeException('Destination folder not found', -1);
  53. }
  54. // Make sure the destination exists
  55. if (!self::create($dest))
  56. {
  57. throw new RuntimeException('Cannot create destination folder', -1);
  58. }
  59. // If we're using ftp and don't have streams enabled
  60. if ($FTPOptions['enabled'] == 1 && !$use_streams)
  61. {
  62. // Connect the FTP client
  63. $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
  64. if (!($dh = @opendir($src)))
  65. {
  66. throw new RuntimeException('Cannot open source folder', -1);
  67. }
  68. // Walk through the directory copying files and recursing into folders.
  69. while (($file = readdir($dh)) !== false)
  70. {
  71. $sfid = $src . '/' . $file;
  72. $dfid = $dest . '/' . $file;
  73. switch (filetype($sfid))
  74. {
  75. case 'dir':
  76. if ($file != '.' && $file != '..')
  77. {
  78. $ret = self::copy($sfid, $dfid, null, $force);
  79. if ($ret !== true)
  80. {
  81. return $ret;
  82. }
  83. }
  84. break;
  85. case 'file':
  86. // Translate path for the FTP account
  87. $dfid = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dfid), '/');
  88. if (!$ftp->store($sfid, $dfid))
  89. {
  90. throw new RuntimeException('Copy file failed', -1);
  91. }
  92. break;
  93. }
  94. }
  95. }
  96. else
  97. {
  98. if (!($dh = @opendir($src)))
  99. {
  100. throw new RuntimeException('Cannot open source folder', -1);
  101. }
  102. // Walk through the directory copying files and recursing into folders.
  103. while (($file = readdir($dh)) !== false)
  104. {
  105. $sfid = $src . '/' . $file;
  106. $dfid = $dest . '/' . $file;
  107. switch (filetype($sfid))
  108. {
  109. case 'dir':
  110. if ($file != '.' && $file != '..')
  111. {
  112. $ret = self::copy($sfid, $dfid, null, $force, $use_streams);
  113. if ($ret !== true)
  114. {
  115. return $ret;
  116. }
  117. }
  118. break;
  119. case 'file':
  120. if ($use_streams)
  121. {
  122. $stream = JFactory::getStream();
  123. if (!$stream->copy($sfid, $dfid))
  124. {
  125. throw new RuntimeException('Cannot copy file: ' . $stream->getError(), -1);
  126. }
  127. }
  128. else
  129. {
  130. if (!@copy($sfid, $dfid))
  131. {
  132. throw new RuntimeException('Copy file failed', -1);
  133. }
  134. }
  135. break;
  136. }
  137. }
  138. }
  139. return true;
  140. }
  141. /**
  142. * Create a folder -- and all necessary parent folders.
  143. *
  144. * @param string $path A path to create from the base path.
  145. * @param integer $mode Directory permissions to set for folders created. 0755 by default.
  146. *
  147. * @return boolean True if successful.
  148. *
  149. * @since 11.1
  150. */
  151. public static function create($path = '', $mode = 0755)
  152. {
  153. $FTPOptions = JClientHelper::getCredentials('ftp');
  154. static $nested = 0;
  155. // Check to make sure the path valid and clean
  156. $path = JPath::clean($path);
  157. // Check if parent dir exists
  158. $parent = dirname($path);
  159. if (!self::exists($parent))
  160. {
  161. // Prevent infinite loops!
  162. $nested++;
  163. if (($nested > 20) || ($parent == $path))
  164. {
  165. JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), JLog::WARNING, 'jerror');
  166. $nested--;
  167. return false;
  168. }
  169. // Create the parent directory
  170. if (self::create($parent, $mode) !== true)
  171. {
  172. // JFolder::create throws an error
  173. $nested--;
  174. return false;
  175. }
  176. // OK, parent directory has been created
  177. $nested--;
  178. }
  179. // Check if dir already exists
  180. if (self::exists($path))
  181. {
  182. return true;
  183. }
  184. // Check for safe mode
  185. if ($FTPOptions['enabled'] == 1)
  186. {
  187. // Connect the FTP client
  188. $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
  189. // Translate path to FTP path
  190. $path = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
  191. $ret = $ftp->mkdir($path);
  192. $ftp->chmod($path, $mode);
  193. }
  194. else
  195. {
  196. // We need to get and explode the open_basedir paths
  197. $obd = ini_get('open_basedir');
  198. // If open_basedir is set we need to get the open_basedir that the path is in
  199. if ($obd != null)
  200. {
  201. if (IS_WIN)
  202. {
  203. $obdSeparator = ";";
  204. }
  205. else
  206. {
  207. $obdSeparator = ":";
  208. }
  209. // Create the array of open_basedir paths
  210. $obdArray = explode($obdSeparator, $obd);
  211. $inBaseDir = false;
  212. // Iterate through open_basedir paths looking for a match
  213. foreach ($obdArray as $test)
  214. {
  215. $test = JPath::clean($test);
  216. if (strpos($path, $test) === 0)
  217. {
  218. $inBaseDir = true;
  219. break;
  220. }
  221. }
  222. if ($inBaseDir == false)
  223. {
  224. // Return false for JFolder::create because the path to be created is not in open_basedir
  225. JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), JLog::WARNING, 'jerror');
  226. return false;
  227. }
  228. }
  229. // First set umask
  230. $origmask = @umask(0);
  231. // Create the path
  232. if (!$ret = @mkdir($path, $mode))
  233. {
  234. @umask($origmask);
  235. JLog::add(
  236. __METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY'), 'Path: ' . $path, JLog::WARNING, 'jerror'
  237. );
  238. return false;
  239. }
  240. // Reset umask
  241. @umask($origmask);
  242. }
  243. return $ret;
  244. }
  245. /**
  246. * Delete a folder.
  247. *
  248. * @param string $path The path to the folder to delete.
  249. *
  250. * @return boolean True on success.
  251. *
  252. * @since 11.1
  253. */
  254. public static function delete($path)
  255. {
  256. @set_time_limit(ini_get('max_execution_time'));
  257. // Sanity check
  258. if (!$path)
  259. {
  260. // Bad programmer! Bad Bad programmer!
  261. JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), JLog::WARNING, 'jerror');
  262. return false;
  263. }
  264. $FTPOptions = JClientHelper::getCredentials('ftp');
  265. try
  266. {
  267. // Check to make sure the path valid and clean
  268. $path = JPath::clean($path);
  269. }
  270. catch (UnexpectedValueException $e)
  271. {
  272. throw new UnexpectedValueException($e);
  273. }
  274. // Is this really a folder?
  275. if (!is_dir($path))
  276. {
  277. JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');
  278. return false;
  279. }
  280. // Remove all the files in folder if they exist; disable all filtering
  281. $files = self::files($path, '.', false, true, array(), array());
  282. if (!empty($files))
  283. {
  284. jimport('joomla.filesystem.file');
  285. if (JFile::delete($files) !== true)
  286. {
  287. // JFile::delete throws an error
  288. return false;
  289. }
  290. }
  291. // Remove sub-folders of folder; disable all filtering
  292. $folders = self::folders($path, '.', false, true, array(), array());
  293. foreach ($folders as $folder)
  294. {
  295. if (is_link($folder))
  296. {
  297. // Don't descend into linked directories, just delete the link.
  298. jimport('joomla.filesystem.file');
  299. if (JFile::delete($folder) !== true)
  300. {
  301. // JFile::delete throws an error
  302. return false;
  303. }
  304. }
  305. elseif (self::delete($folder) !== true)
  306. {
  307. // JFolder::delete throws an error
  308. return false;
  309. }
  310. }
  311. if ($FTPOptions['enabled'] == 1)
  312. {
  313. // Connect the FTP client
  314. $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
  315. }
  316. // In case of restricted permissions we zap it one way or the other
  317. // as long as the owner is either the webserver or the ftp.
  318. if (@rmdir($path))
  319. {
  320. $ret = true;
  321. }
  322. elseif ($FTPOptions['enabled'] == 1)
  323. {
  324. // Translate path and delete
  325. $path = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
  326. // FTP connector throws an error
  327. $ret = $ftp->delete($path);
  328. }
  329. else
  330. {
  331. JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), JLog::WARNING, 'jerror');
  332. $ret = false;
  333. }
  334. return $ret;
  335. }
  336. /**
  337. * Moves a folder.
  338. *
  339. * @param string $src The path to the source folder.
  340. * @param string $dest The path to the destination folder.
  341. * @param string $path An optional base path to prefix to the file names.
  342. * @param boolean $use_streams Optionally use streams.
  343. *
  344. * @return mixed Error message on false or boolean true on success.
  345. *
  346. * @since 11.1
  347. */
  348. public static function move($src, $dest, $path = '', $use_streams = false)
  349. {
  350. $FTPOptions = JClientHelper::getCredentials('ftp');
  351. if ($path)
  352. {
  353. $src = JPath::clean($path . '/' . $src);
  354. $dest = JPath::clean($path . '/' . $dest);
  355. }
  356. if (!self::exists($src))
  357. {
  358. return JText::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER');
  359. }
  360. if (self::exists($dest))
  361. {
  362. return JText::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS');
  363. }
  364. if ($use_streams)
  365. {
  366. $stream = JFactory::getStream();
  367. if (!$stream->move($src, $dest))
  368. {
  369. return JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME', $stream->getError());
  370. }
  371. $ret = true;
  372. }
  373. else
  374. {
  375. if ($FTPOptions['enabled'] == 1)
  376. {
  377. // Connect the FTP client
  378. $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
  379. // Translate path for the FTP account
  380. $src = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
  381. $dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
  382. // Use FTP rename to simulate move
  383. if (!$ftp->rename($src, $dest))
  384. {
  385. return JText::_('Rename failed');
  386. }
  387. $ret = true;
  388. }
  389. else
  390. {
  391. if (!@rename($src, $dest))
  392. {
  393. return JText::_('Rename failed');
  394. }
  395. $ret = true;
  396. }
  397. }
  398. return $ret;
  399. }
  400. /**
  401. * Wrapper for the standard file_exists function
  402. *
  403. * @param string $path Folder name relative to installation dir
  404. *
  405. * @return boolean True if path is a folder
  406. *
  407. * @since 11.1
  408. */
  409. public static function exists($path)
  410. {
  411. return is_dir(JPath::clean($path));
  412. }
  413. /**
  414. * Utility function to read the files in a folder.
  415. *
  416. * @param string $path The path of the folder to read.
  417. * @param string $filter A filter for file names.
  418. * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
  419. * @param boolean $full True to return the full path to the file.
  420. * @param array $exclude Array with names of files which should not be shown in the result.
  421. * @param array $excludefilter Array of filter to exclude
  422. * @param boolean $naturalSort False for asort, true for natsort
  423. *
  424. * @return array Files in the given folder.
  425. *
  426. * @since 11.1
  427. */
  428. public static function files($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
  429. $excludefilter = array('^\..*', '.*~'), $naturalSort = false)
  430. {
  431. // Check to make sure the path valid and clean
  432. $path = JPath::clean($path);
  433. // Is the path a folder?
  434. if (!is_dir($path))
  435. {
  436. JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FILES', $path), JLog::WARNING, 'jerror');
  437. return false;
  438. }
  439. // Compute the excludefilter string
  440. if (count($excludefilter))
  441. {
  442. $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
  443. }
  444. else
  445. {
  446. $excludefilter_string = '';
  447. }
  448. // Get the files
  449. $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, true);
  450. // Sort the files based on either natural or alpha method
  451. if ($naturalSort)
  452. {
  453. natsort($arr);
  454. }
  455. else
  456. {
  457. asort($arr);
  458. }
  459. return array_values($arr);
  460. }
  461. /**
  462. * Utility function to read the folders in a folder.
  463. *
  464. * @param string $path The path of the folder to read.
  465. * @param string $filter A filter for folder names.
  466. * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
  467. * @param boolean $full True to return the full path to the folders.
  468. * @param array $exclude Array with names of folders which should not be shown in the result.
  469. * @param array $excludefilter Array with regular expressions matching folders which should not be shown in the result.
  470. *
  471. * @return array Folders in the given folder.
  472. *
  473. * @since 11.1
  474. */
  475. public static function folders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
  476. $excludefilter = array('^\..*'))
  477. {
  478. // Check to make sure the path valid and clean
  479. $path = JPath::clean($path);
  480. // Is the path a folder?
  481. if (!is_dir($path))
  482. {
  483. JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FOLDER', $path), JLog::WARNING, 'jerror');
  484. return false;
  485. }
  486. // Compute the excludefilter string
  487. if (count($excludefilter))
  488. {
  489. $excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
  490. }
  491. else
  492. {
  493. $excludefilter_string = '';
  494. }
  495. // Get the folders
  496. $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, false);
  497. // Sort the folders
  498. asort($arr);
  499. return array_values($arr);
  500. }
  501. /**
  502. * Function to read the files/folders in a folder.
  503. *
  504. * @param string $path The path of the folder to read.
  505. * @param string $filter A filter for file names.
  506. * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
  507. * @param boolean $full True to return the full path to the file.
  508. * @param array $exclude Array with names of files which should not be shown in the result.
  509. * @param string $excludefilter_string Regexp of files to exclude
  510. * @param boolean $findfiles True to read the files, false to read the folders
  511. *
  512. * @return array Files.
  513. *
  514. * @since 11.1
  515. */
  516. protected static function _items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles)
  517. {
  518. @set_time_limit(ini_get('max_execution_time'));
  519. $arr = array();
  520. // Read the source directory
  521. if (!($handle = @opendir($path)))
  522. {
  523. return $arr;
  524. }
  525. while (($file = readdir($handle)) !== false)
  526. {
  527. if ($file != '.' && $file != '..' && !in_array($file, $exclude)
  528. && (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)))
  529. {
  530. // Compute the fullpath
  531. $fullpath = $path . DIRECTORY_SEPARATOR . $file;
  532. // Compute the isDir flag
  533. $isDir = is_dir($fullpath);
  534. if (($isDir xor $findfiles) && preg_match("/$filter/", $file))
  535. {
  536. // (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
  537. if ($full)
  538. {
  539. // Full path is requested
  540. $arr[] = $fullpath;
  541. }
  542. else
  543. {
  544. // Filename is requested
  545. $arr[] = $file;
  546. }
  547. }
  548. if ($isDir && $recurse)
  549. {
  550. // Search recursively
  551. if (is_int($recurse))
  552. {
  553. // Until depth 0 is reached
  554. $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludefilter_string, $findfiles));
  555. }
  556. else
  557. {
  558. $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles));
  559. }
  560. }
  561. }
  562. }
  563. closedir($handle);
  564. return $arr;
  565. }
  566. /**
  567. * Lists folder in format suitable for tree display.
  568. *
  569. * @param string $path The path of the folder to read.
  570. * @param string $filter A filter for folder names.
  571. * @param integer $maxLevel The maximum number of levels to recursively read, defaults to three.
  572. * @param integer $level The current level, optional.
  573. * @param integer $parent Unique identifier of the parent folder, if any.
  574. *
  575. * @return array Folders in the given folder.
  576. *
  577. * @since 11.1
  578. */
  579. public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
  580. {
  581. $dirs = array();
  582. if ($level == 0)
  583. {
  584. $GLOBALS['_JFolder_folder_tree_index'] = 0;
  585. }
  586. if ($level < $maxLevel)
  587. {
  588. $folders = self::folders($path, $filter);
  589. // First path, index foldernames
  590. foreach ($folders as $name)
  591. {
  592. $id = ++$GLOBALS['_JFolder_folder_tree_index'];
  593. $fullName = JPath::clean($path . '/' . $name);
  594. $dirs[] = array('id' => $id, 'parent' => $parent, 'name' => $name, 'fullname' => $fullName,
  595. 'relname' => str_replace(JPATH_ROOT, '', $fullName));
  596. $dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
  597. $dirs = array_merge($dirs, $dirs2);
  598. }
  599. }
  600. return $dirs;
  601. }
  602. /**
  603. * Makes path name safe to use.
  604. *
  605. * @param string $path The full path to sanitise.
  606. *
  607. * @return string The sanitised string.
  608. *
  609. * @since 11.1
  610. */
  611. public static function makeSafe($path)
  612. {
  613. $regex = array('#[^A-Za-z0-9:_\\\/-]#');
  614. return preg_replace($regex, '', $path);
  615. }
  616. }