filesystem.inc

  1. 8.0.x includes/filesystem.inc
  2. 6.x includes/filesystem.inc
  3. 7.x includes/filesystem.inc
  4. 5.x includes/filesystem.inc
  5. master includes/filesystem.inc

Functions

Namesort descending Description
drush_copy_dir Copy $src to $dest.
drush_correct_absolute_path_for_exec If we are going to pass a path to exec or proc_open, then we need to fix it up under CYGWIN or MINGW. In both of these environments, PHP works with absolute paths such as "C:\path". CYGWIN expects these to be converted to…
drush_delete_dir Deletes the provided file or folder and everything inside it.
drush_delete_tmp_dir Deletes the provided file or folder and everything inside it. This function explicitely tries to delete read-only files / folders.
drush_dir_md5 Calculates a single md5 hash for all files a directory (incuding subdirectories)
drush_file_append_data Simple helper function to append data to a given file.
drush_file_not_empty Test to see if a file exists and is not empty
drush_find_tmp Returns the path to a temporary directory.
drush_is_absolute_path Determines whether the provided path is absolute or not on the specified O.S. -- starts with "/" on *nix, or starts with "[A-Z]:\" or "[A-Z]:/" on Windows.
drush_mkdir Cross-platform compatible helper function to recursively create a directory tree.
drush_move_dir Move $src to $dest.
drush_normalize_path Makes sure the path has only path separators native for the current operating system
drush_preflight_backup_dir Decide where our backup directory should go
drush_prepare_backup_dir Prepare a backup directory
drush_register_file_for_deletion Any file passed in to this function will be deleted when drush exits.
drush_save_data_to_temp_file Save a string to a temporary file. Does not depend on Drupal's API. The temporary file will be automatically deleted when drush exits.
drush_scan_directory Finds all files that match a given mask in a given directory. Directories and files beginning with a period are excluded; this prevents hidden files and directories (such as SVN working directories and GIT repositories) from being scanned.
drush_tempdir Creates a temporary directory and return its path.
drush_tempnam Creates a temporary file, and registers it so that it will be deleted when drush exits. Whenever possible, drush_save_data_to_temp_file() should be used instead of this function.
drush_trim_path Remove the trailing DIRECTORY_SEPARATOR from a path. Will actually remove either / or \ on Windows.
_drush_delete_registered_files Delete all of the registered temporary files.
_drush_recursive_copy Internal function called by drush_copy_dir; do not use directly.

Constants

Namesort descending Description
FILE_EXISTS_ABORT Behavior for drush_copy_dir() and drush_move_dir() when destinations exist.
FILE_EXISTS_MERGE
FILE_EXISTS_OVERWRITE

File

includes/filesystem.inc
View source
  1. <?php
  2. /**
  3. * @defgroup filesystemfunctions Filesystem convenience functions.
  4. * @{
  5. */
  6. /**
  7. * Behavior for drush_copy_dir() and drush_move_dir() when destinations exist.
  8. */
  9. define('FILE_EXISTS_ABORT', 0);
  10. define('FILE_EXISTS_OVERWRITE', 1);
  11. define('FILE_EXISTS_MERGE', 2);
  12. /**
  13. * Determines whether the provided path is absolute or not
  14. * on the specified O.S. -- starts with "/" on *nix, or starts
  15. * with "[A-Z]:\" or "[A-Z]:/" on Windows.
  16. */
  17. function drush_is_absolute_path($path, $os = NULL) {
  18. // Relative paths will never start with a '/', even on Windows,
  19. // so it is safe to just call all paths that start with a '/'
  20. // absolute. This simplifies things for Windows with CYGWIN / MINGW / CWRSYNC,
  21. // where absolute paths sometimes start c:\path and sometimes
  22. // start /cygdrive/c/path.
  23. if ($path[0] == '/') {
  24. return TRUE;
  25. }
  26. if (drush_is_windows($os)) {
  27. return preg_match('@^[a-zA-Z]:[\\\/]@', $path);
  28. }
  29. return FALSE;
  30. }
  31. /**
  32. * If we are going to pass a path to exec or proc_open,
  33. * then we need to fix it up under CYGWIN or MINGW. In
  34. * both of these environments, PHP works with absolute paths
  35. * such as "C:\path". CYGWIN expects these to be converted
  36. * to "/cygdrive/c/path" and MINGW expects these to be converted
  37. * to "/c/path"; otherwise, the exec will not work.
  38. *
  39. * This call does nothing if the parameter is not an absolute
  40. * path, or we are not running under CYGWIN / MINGW.
  41. *
  42. * UPDATE: It seems I was mistaken; this is only necessary if we
  43. * are using cwRsync. We do not need to correct every path to
  44. * exec or proc_open (thank god).
  45. */
  46. function drush_correct_absolute_path_for_exec($path, $os = NULL) {
  47. if (drush_is_windows() && drush_is_absolute_path($path, "WINNT")) {
  48. if (drush_is_mingw($os)) {
  49. $path = preg_replace('/(\w):/', '/${1}', str_replace('\\', '/', $path));
  50. }
  51. elseif (drush_is_cygwin($os)) {
  52. $path = preg_replace('/(\w):/', '/cygdrive/${1}', str_replace('\\', '/', $path));
  53. }
  54. }
  55. return $path;
  56. }
  57. /**
  58. * Remove the trailing DIRECTORY_SEPARATOR from a path.
  59. * Will actually remove either / or \ on Windows.
  60. */
  61. function drush_trim_path($path, $os = NULL) {
  62. if (drush_is_windows($os)) {
  63. return rtrim($path, '/\\');
  64. }
  65. else {
  66. return rtrim($path, '/');
  67. }
  68. }
  69. /**
  70. * Makes sure the path has only path separators native for the current operating system
  71. */
  72. function drush_normalize_path($path) {
  73. if (drush_is_windows()) {
  74. $path = str_replace('/', '\\', strtolower($path));
  75. }
  76. else {
  77. $path = str_replace('\\', '/', $path);
  78. }
  79. return drush_trim_path($path);
  80. }
  81. /**
  82. * Calculates a single md5 hash for all files a directory (incuding subdirectories)
  83. */
  84. function drush_dir_md5($dir) {
  85. $flist = drush_scan_directory($dir, '/./', array('.', '..'), 0, TRUE, 'filename', 0, TRUE);
  86. $hashes = array();
  87. foreach ($flist as $f) {
  88. $sum = array();
  89. exec('cksum ' . escapeshellarg($f->filename), $sum);
  90. $hashes[] = trim(str_replace(array($dir), array(''), $sum[0]));
  91. }
  92. sort($hashes);
  93. return md5(implode("\n", $hashes));
  94. }
  95. /**
  96. * Deletes the provided file or folder and everything inside it.
  97. *
  98. * Usually respects read-only files and folders. To do a forced delete use
  99. * drush_delete_tmp_dir() or set the parameter $forced.
  100. *
  101. * @param $dir
  102. * The file or directory to delete.
  103. * @param $force
  104. * Try whatever possible to delete the directory - also read-only ones.
  105. * @param $follow_symlinks
  106. * Do not delete symlinked files. Simply unlink symbolic links.
  107. * @return
  108. * FALSE on failure, TRUE if everything was deleted.
  109. */
  110. function drush_delete_dir($dir, $force = FALSE, $follow_symlinks = FALSE) {
  111. // Do not delete symlinked files, only unlink symbolic links
  112. if (is_link($dir) && !$follow_symlinks) {
  113. return unlink($dir);
  114. }
  115. // Allow to delete symlinks even if the target doesn't exist.
  116. if (!is_link($dir) && !file_exists($dir)) {
  117. return TRUE;
  118. }
  119. if (!is_dir($dir)) {
  120. if ($force) {
  121. // Force deletion of items with readonly flag.
  122. @chmod($dir, 0777);
  123. }
  124. return unlink($dir);
  125. }
  126. foreach (scandir($dir) as $item) {
  127. if ($item == '.' || $item == '..') {
  128. continue;
  129. }
  130. if ($force) {
  131. @chmod($dir, 0777);
  132. }
  133. if (!drush_delete_dir($dir . '/' . $item, $force)) {
  134. return FALSE;
  135. }
  136. }
  137. if ($force) {
  138. // Force deletion of items with readonly flag.
  139. @chmod($dir, 0777);
  140. }
  141. return rmdir($dir);
  142. }
  143. /**
  144. * Deletes the provided file or folder and everything inside it.
  145. * This function explicitely tries to delete read-only files / folders.
  146. *
  147. * @param $dir
  148. * The directory to delete
  149. * @return
  150. * FALSE on failure, TRUE if everything was deleted
  151. */
  152. function drush_delete_tmp_dir($dir) {
  153. return drush_delete_dir($dir, TRUE);
  154. }
  155. /**
  156. * Copy $src to $dest.
  157. *
  158. * @param $src
  159. * The directory to copy.
  160. * @param $dest
  161. * The destination to copy the source to, including the new name of
  162. * the directory. To copy directory "a" from "/b" to "/c", then
  163. * $src = "/b/a" and $dest = "/c/a". To copy "a" to "/c" and rename
  164. * it to "d", then $dest = "/c/d".
  165. * @param $overwrite
  166. * Action to take if destination already exists.
  167. * - FILE_EXISTS_OVERWRITE - completely removes existing directory.
  168. * - FILE_EXISTS_ABORT - aborts the operation.
  169. * - FILE_EXISTS_MERGE - Leaves existing files and directories in place.
  170. * @return
  171. * TRUE on success, FALSE on failure.
  172. */
  173. function drush_copy_dir($src, $dest, $overwrite = FILE_EXISTS_ABORT) {
  174. // Preflight based on $overwrite if $dest exists.
  175. if (file_exists($dest)) {
  176. if ($overwrite === FILE_EXISTS_OVERWRITE) {
  177. drush_op('drush_delete_dir', $dest, TRUE);
  178. }
  179. elseif ($overwrite === FILE_EXISTS_ABORT) {
  180. return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
  181. }
  182. elseif ($overwrite === FILE_EXISTS_MERGE) {
  183. // $overwrite flag may indicate we should merge instead.
  184. drush_log(dt('Merging existing !dest directory', array('!dest' => $dest)));
  185. }
  186. }
  187. // $src readable?
  188. if (!is_readable($src)) {
  189. return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
  190. }
  191. // $dest writable?
  192. if (!is_writable(dirname($dest))) {
  193. return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
  194. }
  195. // Try to do a recursive copy.
  196. if (@drush_op('_drush_recursive_copy', $src, $dest)) {
  197. return TRUE;
  198. }
  199. return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Unable to copy !src to !dest.', array('!src' => $src, '!dest' => $dest)));
  200. }
  201. /**
  202. * Internal function called by drush_copy_dir; do not use directly.
  203. */
  204. function _drush_recursive_copy($src, $dest) {
  205. // all subdirectories and contents:
  206. if(is_dir($src)) {
  207. if (!drush_mkdir($dest, TRUE)) {
  208. return FALSE;
  209. }
  210. $dir_handle = opendir($src);
  211. while($file = readdir($dir_handle)) {
  212. if ($file != "." && $file != "..") {
  213. if (_drush_recursive_copy("$src/$file", "$dest/$file") !== TRUE) {
  214. return FALSE;
  215. }
  216. }
  217. }
  218. closedir($dir_handle);
  219. }
  220. elseif (is_link($src)) {
  221. symlink(readlink($src), $dest);
  222. }
  223. elseif (!(copy($src, $dest) && touch($dest, filemtime($src)))) {
  224. return FALSE;
  225. }
  226. // Preserve execute permission.
  227. if (!is_link($src) && !drush_is_windows()) {
  228. // Get execute bits of $src.
  229. $execperms = fileperms($src) & 0111;
  230. // Apply execute permissions if any.
  231. if ($execperms > 0) {
  232. $perms = fileperms($dest) | $execperms;
  233. chmod($dest, $perms);
  234. }
  235. }
  236. return TRUE;
  237. }
  238. /**
  239. * Move $src to $dest.
  240. *
  241. * If the php 'rename' function doesn't work, then we'll do copy & delete.
  242. *
  243. * @param $src
  244. * The directory to move.
  245. * @param $dest
  246. * The destination to move the source to, including the new name of
  247. * the directory. To move directory "a" from "/b" to "/c", then
  248. * $src = "/b/a" and $dest = "/c/a". To move "a" to "/c" and rename
  249. * it to "d", then $dest = "/c/d" (just like php rename function).
  250. * @param $overwrite
  251. * If TRUE, the destination will be deleted if it exists.
  252. * @return
  253. * TRUE on success, FALSE on failure.
  254. */
  255. function drush_move_dir($src, $dest, $overwrite = FALSE) {
  256. // Preflight based on $overwrite if $dest exists.
  257. if (file_exists($dest)) {
  258. if ($overwrite) {
  259. drush_op('drush_delete_dir', $dest, TRUE);
  260. }
  261. else {
  262. return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
  263. }
  264. }
  265. // $src readable?
  266. if (!drush_op('is_readable', $src)) {
  267. return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
  268. }
  269. // $dest writable?
  270. if (!drush_op('is_writable', dirname($dest))) {
  271. return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
  272. }
  273. // Try rename. It will fail if $src and $dest are not in the same partition.
  274. if (@drush_op('rename', $src, $dest)) {
  275. return TRUE;
  276. }
  277. // Eventually it will create an empty file in $dest. See
  278. // http://www.php.net/manual/es/function.rename.php#90025
  279. elseif (is_file($dest)) {
  280. drush_op('unlink', $dest);
  281. }
  282. // If 'rename' fails, then we will use copy followed
  283. // by a delete of the source.
  284. if (drush_copy_dir($src, $dest)) {
  285. drush_op('drush_delete_dir', $src, TRUE);
  286. return TRUE;
  287. }
  288. return drush_set_error('DRUSH_MOVE_DIR_FAILURE', dt('Unable to move !src to !dest.', array('!src' => $src, '!dest' => $dest)));
  289. }
  290. /**
  291. * Cross-platform compatible helper function to recursively create a directory tree.
  292. *
  293. * @param path
  294. * Path to directory to create.
  295. * @param required
  296. * If TRUE, then drush_mkdir will call drush_set_error on failure.
  297. *
  298. * Callers should *always* do their own error handling after calling drush_mkdir.
  299. * If $required is FALSE, then a different location should be selected, and a final
  300. * error message should be displayed if no usable locations can be found.
  301. * @see drush_directory_cache().
  302. * If $required is TRUE, then the execution of the current command should be
  303. * halted if the required directory cannot be created.
  304. */
  305. function drush_mkdir($path, $required = TRUE) {
  306. if (!is_dir($path)) {
  307. if (drush_mkdir(dirname($path))) {
  308. if (@mkdir($path)) {
  309. return TRUE;
  310. }
  311. elseif (is_dir($path) && is_writable($path)) {
  312. // The directory was created by a concurrent process.
  313. return TRUE;
  314. }
  315. else {
  316. if (!$required) {
  317. return FALSE;
  318. }
  319. if (is_writable(dirname($path))) {
  320. return drush_set_error('DRUSH_CREATE_DIR_FAILURE', dt('Unable to create !dir.', array('!dir' => preg_replace('/\w+\/\.\.\//', '', $path))));
  321. }
  322. else {
  323. return drush_set_error('DRUSH_PARENT_NOT_WRITABLE', dt('Unable to create !newdir in !dir. Please check directory permissions.', array('!newdir' => basename($path), '!dir' => realpath(dirname($path)))));
  324. }
  325. }
  326. }
  327. return FALSE;
  328. }
  329. else {
  330. if (!is_writable($path)) {
  331. if (!$required) {
  332. return FALSE;
  333. }
  334. return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Directory !dir exists, but is not writable. Please check directory permissions.', array('!dir' => realpath($path))));
  335. }
  336. return TRUE;
  337. }
  338. }
  339. /**
  340. * Save a string to a temporary file. Does not depend on Drupal's API.
  341. * The temporary file will be automatically deleted when drush exits.
  342. *
  343. * @param string $data
  344. * @param string $suffix
  345. * Append string to filename. use of this parameter if is discouraged. @see
  346. * drush_tempnam().
  347. * @return string
  348. * A path to the file.
  349. */
  350. function drush_save_data_to_temp_file($data, $suffix = NULL) {
  351. static $fp;
  352. $file = drush_tempnam('drush_', NULL, $suffix);
  353. $fp = fopen($file, "w");
  354. fwrite($fp, $data);
  355. $meta_data = stream_get_meta_data($fp);
  356. $file = $meta_data['uri'];
  357. fclose($fp);
  358. return $file;
  359. }
  360. /**
  361. * Returns the path to a temporary directory.
  362. *
  363. * This is a custom version of Drupal's file_directory_path().
  364. * We can't directly rely on sys_get_temp_dir() as this
  365. * path is not valid in some setups for Mac, and we want to honor
  366. * an environment variable (used by tests).
  367. */
  368. function drush_find_tmp() {
  369. static $temporary_directory;
  370. if (!isset($temporary_directory)) {
  371. $directories = array();
  372. // Operating system specific dirs.
  373. if (drush_is_windows()) {
  374. // get user specific and operating system temp folders from system environment variables
  375. // see http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx?mfr=true
  376. $tempdir = getenv('TEMP');
  377. if (isset($tempdir)) {
  378. $directories[] = $tempdir;
  379. }
  380. $tmpdir = getenv('TMP');
  381. if (isset($tmpdir)) {
  382. $directories[] = $tmpdir;
  383. }
  384. $windir = getenv('WINDIR');
  385. if (isset($windir)) {
  386. // WINDIR itself is not writable, but it always contains a /Temp dir,
  387. // which is the system-wide temporary directory on older versions. Newer
  388. // versions only allow system processes to use it.
  389. $directories[] = $windir . '/Temp';
  390. }
  391. }
  392. else {
  393. $directories[] = '/tmp';
  394. }
  395. // This function exists in PHP 5 >= 5.2.1, but drush
  396. // requires PHP 5 >= 5.2.0, so we check for it.
  397. if (function_exists('sys_get_temp_dir')) {
  398. $directories[] = sys_get_temp_dir();
  399. }
  400. foreach ($directories as $directory) {
  401. if (is_dir($directory) && is_writable($directory)) {
  402. $temporary_directory = $directory;
  403. break;
  404. }
  405. }
  406. if (empty($temporary_directory)) {
  407. // If no directory has been found, create one in cwd.
  408. $temporary_directory = drush_cwd() . '/tmp';
  409. drush_mkdir($temporary_directory, TRUE);
  410. if (!is_dir($temporary_directory)) {
  411. return drush_set_error('DRUSH_UNABLE_TO_CREATE_TMP_DIR', dt("Unable to create a temporary directory."));
  412. }
  413. drush_register_file_for_deletion($temporary_directory);
  414. }
  415. }
  416. return $temporary_directory;
  417. }
  418. /**
  419. * Creates a temporary file, and registers it so that
  420. * it will be deleted when drush exits. Whenever possible,
  421. * drush_save_data_to_temp_file() should be used instead
  422. * of this function.
  423. *
  424. * @param string suffix
  425. * Append this suffix to the filename. Use of this parameter is discouraged as
  426. * it can break the guarantee of tempname(). See http://www.php.net/manual/en/function.tempnam.php#42052.
  427. * Originally added to support Oracle driver.
  428. */
  429. function drush_tempnam($pattern, $tmp_dir = NULL, $suffix = '') {
  430. if ($tmp_dir == NULL) {
  431. $tmp_dir = drush_find_tmp();
  432. }
  433. $tmp_file = tempnam($tmp_dir, $pattern);
  434. drush_register_file_for_deletion($tmp_file);
  435. $tmp_file_with_suffix = $tmp_file . $suffix;
  436. drush_register_file_for_deletion($tmp_file_with_suffix);
  437. return $tmp_file_with_suffix;
  438. }
  439. /**
  440. * Creates a temporary directory and return its path.
  441. */
  442. function drush_tempdir() {
  443. $tmp_dir = drush_trim_path(drush_find_tmp());
  444. $tmp_dir .= '/' . 'drush_tmp_' . uniqid(time() . '_');
  445. drush_mkdir($tmp_dir);
  446. drush_register_file_for_deletion($tmp_dir);
  447. return $tmp_dir;
  448. }
  449. /**
  450. * Any file passed in to this function will be deleted
  451. * when drush exits.
  452. */
  453. function drush_register_file_for_deletion($file = NULL) {
  454. static $registered_files = array();
  455. if (isset($file)) {
  456. if (empty($registered_files)) {
  457. register_shutdown_function('_drush_delete_registered_files');
  458. }
  459. $registered_files[] = $file;
  460. }
  461. return $registered_files;
  462. }
  463. /**
  464. * Delete all of the registered temporary files.
  465. */
  466. function _drush_delete_registered_files() {
  467. $files_to_delete = drush_register_file_for_deletion();
  468. foreach ($files_to_delete as $file) {
  469. // We'll make sure that the file still exists, just
  470. // in case someone came along and deleted it, even
  471. // though they did not need to.
  472. if (file_exists($file)) {
  473. if (is_dir($file)) {
  474. drush_delete_dir($file, TRUE);
  475. }
  476. else {
  477. @chmod($dir, 0777); // Make file writeable
  478. unlink($file);
  479. }
  480. }
  481. }
  482. }
  483. /**
  484. * Decide where our backup directory should go
  485. *
  486. * @param string $subdir
  487. * The name of the desired subdirectory(s) under drush-backups.
  488. * Usually a database name.
  489. */
  490. function drush_preflight_backup_dir($subdir = NULL) {
  491. $backup_dir = drush_get_context('DRUSH_BACKUP_DIR', drush_get_option('backup-location'));
  492. if (empty($backup_dir)) {
  493. // Try to use db name as subdir if none was provided.
  494. if (empty($subdir)) {
  495. $subdir = 'unknown';
  496. if ($creds = drush_get_context('DRUSH_DB_CREDENTIALS')) {
  497. $subdir = $creds['name'];
  498. }
  499. }
  500. // Save the date to be used in the backup directory's path name.
  501. $date = gmdate('YmdHis', $_SERVER['REQUEST_TIME']);
  502. $backup_dir = drush_get_option('backup-dir', drush_server_home() . '/' . 'drush-backups');
  503. $backup_dir = drush_trim_path($backup_dir) . '/' . $subdir . '/' . $date;
  504. drush_set_context('DRUSH_BACKUP_DIR', $backup_dir);
  505. }
  506. return $backup_dir;
  507. }
  508. /**
  509. * Prepare a backup directory
  510. */
  511. function drush_prepare_backup_dir($subdir = NULL) {
  512. $backup_dir = drush_preflight_backup_dir($subdir);
  513. $backup_parent = dirname($backup_dir);
  514. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  515. $drupal_root .= '/';
  516. if ((!empty($drupal_root)) && (strpos($backup_parent, $drupal_root) === 0)) {
  517. return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('It\'s not allowed to store backups inside the Drupal root directory.'));
  518. }
  519. if (!file_exists($backup_parent)) {
  520. if (!drush_mkdir($backup_parent, TRUE)) {
  521. return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Unable to create backup directory !dir.', array('!dir' => $backup_parent)));
  522. }
  523. }
  524. if (!is_writable($backup_parent)) {
  525. return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Backup directory !dir is not writable.', array('!dir' => $backup_parent)));
  526. }
  527. if (!drush_mkdir($backup_dir, TRUE)) {
  528. return FALSE;
  529. }
  530. return $backup_dir;
  531. }
  532. /**
  533. * Test to see if a file exists and is not empty
  534. */
  535. function drush_file_not_empty($file_to_test) {
  536. if (file_exists($file_to_test)) {
  537. clearstatcache();
  538. $stat = stat($file_to_test);
  539. if ($stat['size'] > 0) {
  540. return TRUE;
  541. }
  542. }
  543. return FALSE;
  544. }
  545. /**
  546. * Finds all files that match a given mask in a given directory.
  547. * Directories and files beginning with a period are excluded; this
  548. * prevents hidden files and directories (such as SVN working directories
  549. * and GIT repositories) from being scanned.
  550. *
  551. * @param $dir
  552. * The base directory for the scan, without trailing slash.
  553. * @param $mask
  554. * The regular expression of the files to find.
  555. * @param $nomask
  556. * An array of files/directories to ignore.
  557. * @param $callback
  558. * The callback function to call for each match.
  559. * @param $recurse_max_depth
  560. * When TRUE, the directory scan will recurse the entire tree
  561. * starting at the provided directory. When FALSE, only files
  562. * in the provided directory are returned. Integer values
  563. * limit the depth of the traversal, with zero being treated
  564. * identically to FALSE, and 1 limiting the traversal to the
  565. * provided directory and its immediate children only, and so on.
  566. * @param $key
  567. * The key to be used for the returned array of files. Possible
  568. * values are "filename", for the path starting with $dir,
  569. * "basename", for the basename of the file, and "name" for the name
  570. * of the file without an extension.
  571. * @param $min_depth
  572. * Minimum depth of directories to return files from.
  573. * @param $include_dot_files
  574. * If TRUE, files that begin with a '.' will be returned if they
  575. * match the provided mask. If FALSE, files that begin with a '.'
  576. * will not be returned, even if they match the provided mask.
  577. * @param $depth
  578. * Current depth of recursion. This parameter is only used internally and should not be passed.
  579. *
  580. * @return
  581. * An associative array (keyed on the provided key) of objects with
  582. * "path", "basename", and "name" members corresponding to the
  583. * matching files.
  584. */
  585. function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse_max_depth = TRUE, $key = 'filename', $min_depth = 0, $include_dot_files = FALSE, $depth = 0) {
  586. $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
  587. $files = array();
  588. if (is_string($dir) && is_dir($dir) && $handle = opendir($dir)) {
  589. while (FALSE !== ($file = readdir($handle))) {
  590. if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) {
  591. if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) {
  592. // Give priority to files in this folder by merging them in after any subdirectory files.
  593. $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files);
  594. }
  595. elseif ($depth >= $min_depth && preg_match($mask, $file)) {
  596. // Always use this match over anything already set in $files with the same $$key.
  597. $filename = "$dir/$file";
  598. $basename = basename($file);
  599. $name = substr($basename, 0, strrpos($basename, '.'));
  600. $files[$$key] = new stdClass();
  601. $files[$$key]->filename = $filename;
  602. $files[$$key]->basename = $basename;
  603. $files[$$key]->name = $name;
  604. if ($callback) {
  605. drush_op($callback, $filename);
  606. }
  607. }
  608. }
  609. }
  610. closedir($handle);
  611. }
  612. return $files;
  613. }
  614. /**
  615. * Simple helper function to append data to a given file.
  616. *
  617. * @param string $file
  618. * The full path to the file to append the data to.
  619. * @param string $data
  620. * The data to append.
  621. *
  622. * @return boolean
  623. * TRUE on success, FALSE in case of failure to open or write to the file.
  624. */
  625. function drush_file_append_data($file, $data) {
  626. if (!$fd = fopen($file, 'a+')) {
  627. drush_set_error(dt("ERROR: fopen(@file, 'ab') failed", array('@file' => $file)));
  628. return FALSE;
  629. }
  630. if (!fwrite($fd, $data)) {
  631. drush_set_error(dt("ERROR: fwrite(@file) failed", array('@file' => $file)) . '<pre>' . $data);
  632. return FALSE;
  633. }
  634. return TRUE;
  635. }
  636. /**
  637. * @} End of "defgroup filesystemfunctions".
  638. */