3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2015
35 * class to provide simple static functions for file objects
37 class CRM_Utils_File
{
40 * Given a file name, determine if the file contents make it an ascii file
46 * true if file is ascii
48 public static function isAscii($name) {
49 $fd = fopen($name, "r");
56 $line = fgets($fd, 8192);
57 if (!CRM_Utils_String
::isAscii($line)) {
68 * Given a file name, determine if the file contents make it an html file
74 * true if file is html
76 public static function isHtml($name) {
77 $fd = fopen($name, "r");
84 while (!feof($fd) & $lineCount <= 5) {
86 $line = fgets($fd, 8192);
87 if (!CRM_Utils_String
::isHtml($line)) {
98 * Create a directory given a path name, creates parent directories
101 * @param string $path
104 * Should we abort or just return an invalid code.
106 public static function createDir($path, $abort = TRUE) {
107 if (is_dir($path) ||
empty($path)) {
111 CRM_Utils_File
::createDir(dirname($path), $abort);
112 if (@mkdir
($path, 0777) == FALSE) {
114 $docLink = CRM_Utils_System
::docURL2('Moving an Existing Installation to a New Server or Location', NULL, NULL, NULL, NULL, "wiki");
115 echo "Error: Could not create directory: $path.<p>If you have moved an existing CiviCRM installation from one location or server to another there are several steps you will need to follow. They are detailed on this CiviCRM wiki page - {$docLink}. A fix for the specific problem that caused this error message to be displayed is to set the value of the config_backend column in the civicrm_domain table to NULL. However we strongly recommend that you review and follow all the steps in that document.</p>";
117 CRM_Utils_System
::civiExit();
127 * Delete a directory given a path name, delete children directories
128 * and files if needed
130 * @param string $target
133 * @param bool $verbose
137 public static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
138 static $exceptions = array('.', '..');
139 if ($target == '' ||
$target == '/') {
140 throw new Exception("Overly broad deletion");
143 if ($dh = @opendir
($target)) {
144 while (FALSE !== ($sibling = readdir($dh))) {
145 if (!in_array($sibling, $exceptions)) {
146 $object = $target . DIRECTORY_SEPARATOR
. $sibling;
148 if (is_dir($object)) {
149 CRM_Utils_File
::cleanDir($object, $rmdir, $verbose);
151 elseif (is_file($object)) {
152 if (!unlink($object)) {
153 CRM_Core_Session
::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
161 if (rmdir($target)) {
163 CRM_Core_Session
::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
168 CRM_Core_Session
::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
175 * Concatenate several files.
177 * @param array $files
178 * List of file names.
179 * @param string $delim
180 * An optional delimiter to put between files.
183 public static function concat($files, $delim = '') {
186 foreach ($files as $file) {
190 $buf .= file_get_contents($file);
197 * @param string $source
198 * @param string $destination
200 public static function copyDir($source, $destination) {
201 if ($dh = opendir($source)) {
202 @mkdir
($destination);
203 while (FALSE !== ($file = readdir($dh))) {
204 if (($file != '.') && ($file != '..')) {
205 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
206 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
209 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
218 * Given a file name, recode it (in place!) to UTF-8
220 * @param string $name
224 * whether the file was recoded properly
226 public static function toUtf8($name) {
227 static $config = NULL;
228 static $legacyEncoding = NULL;
229 if ($config == NULL) {
230 $config = CRM_Core_Config
::singleton();
231 $legacyEncoding = $config->legacyEncoding
;
234 if (!function_exists('iconv')) {
240 $contents = file_get_contents($name);
241 if ($contents === FALSE) {
245 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
246 if ($contents === FALSE) {
250 $file = fopen($name, 'w');
251 if ($file === FALSE) {
255 $written = fwrite($file, $contents);
256 $closed = fclose($file);
257 if ($written === FALSE or !$closed) {
265 * Appends a slash to the end of a string if it doesn't already end with one
267 * @param string $path
268 * @param string $slash
272 public static function addTrailingSlash($path, $slash = NULL) {
274 // FIXME: Defaulting to backslash on windows systems can produce
275 // unexpected results, esp for URL strings which should always use forward-slashes.
276 // I think this fn should default to forward-slash instead.
277 $slash = DIRECTORY_SEPARATOR
;
279 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
287 * @param string $fileName
288 * @param null $prefix
289 * @param bool $isQueryString
290 * @param bool $dieOnErrors
292 public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
293 require_once 'DB.php';
295 $db = DB
::connect($dsn);
296 if (PEAR
::isError($db)) {
297 die("Cannot open $dsn: " . $db->getMessage());
299 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
300 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
303 if (!$isQueryString) {
304 $string = $prefix . file_get_contents($fileName);
307 // use filename as query string
308 $string = $prefix . $fileName;
311 // get rid of comments starting with # and --
313 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
314 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
316 $queries = preg_split('/;\s*$/m', $string);
317 foreach ($queries as $query) {
318 $query = trim($query);
319 if (!empty($query)) {
320 CRM_Core_Error
::debug_query($query);
321 $res = &$db->query($query);
322 if (PEAR
::isError($res)) {
324 die("Cannot execute $query: " . $res->getMessage());
327 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
339 public static function isExtensionSafe($ext) {
340 static $extensions = NULL;
342 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
344 // make extensions to lowercase
345 $extensions = array_change_key_case($extensions, CASE_LOWER
);
346 // allow html/htm extension ONLY if the user is admin
347 // and/or has access CiviMail
348 if (!(CRM_Core_Permission
::check('access CiviMail') ||
349 CRM_Core_Permission
::check('administer CiviCRM') ||
350 (CRM_Mailing_Info
::workflowEnabled() &&
351 CRM_Core_Permission
::check('create mailings')
355 unset($extensions['html']);
356 unset($extensions['htm']);
359 // support lower and uppercase file extensions
360 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
364 * Determine whether a given file is listed in the PHP include path.
366 * @param string $name
370 * whether the file can be include()d or require()d
372 public static function isIncludable($name) {
373 $x = @fopen
($name, 'r', TRUE);
384 * Remove the 32 bit md5 we add to the fileName
385 * also remove the unknown tag if we added it
387 public static function cleanFileName($name) {
388 // replace the last 33 character before the '.' with null
389 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
394 * @param string $name
398 public static function makeFileName($name) {
399 $uniqID = md5(uniqid(rand(), TRUE));
400 $info = pathinfo($name);
401 $basename = substr($info['basename'],
402 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
404 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
405 // munge extension so it cannot have an embbeded dot in it
406 // The maximum length of a filename for most filesystems is 255 chars.
407 // We'll truncate at 240 to give some room for the extension.
408 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
411 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
421 public static function getFilesByExtension($path, $ext) {
422 $path = self
::addTrailingSlash($path);
424 if ($dh = opendir($path)) {
425 while (FALSE !== ($elem = readdir($dh))) {
426 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
427 $files[] .= $path . $elem;
436 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
439 * The directory to be secured.
440 * @param bool $overwrite
442 public static function restrictAccess($dir, $overwrite = FALSE) {
443 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
444 // of site, causing site to stop functioning.
445 // FIXME: we should do more checks here -
446 if (!empty($dir) && is_dir($dir)) {
447 $htaccess = <<<HTACCESS
454 $file = $dir . '.htaccess';
455 if ($overwrite ||
!file_exists($file)) {
456 if (file_put_contents($file, $htaccess) === FALSE) {
457 CRM_Core_Error
::movedSiteError($file);
464 * Restrict remote users from browsing the given directory.
468 public static function restrictBrowsing($publicDir) {
469 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
474 $nobrowse = realpath($publicDir) . '/index.html';
475 if (!file_exists($nobrowse)) {
476 @file_put_contents
($nobrowse, '');
480 $dir = new RecursiveDirectoryIterator($publicDir);
481 foreach ($dir as $name => $object) {
482 if (is_dir($name) && $name != '..') {
483 $nobrowse = realpath($name) . '/index.html';
484 if (!file_exists($nobrowse)) {
485 @file_put_contents
($nobrowse, '');
492 * Create the base file path from which all our internal directories are
493 * offset. This is derived from the template compile directory set
495 public static function baseFilePath() {
496 static $_path = NULL;
498 // Note: Don't rely on $config; that creates a dependency loop.
499 if (!defined('CIVICRM_TEMPLATE_COMPILEDIR')) {
500 throw new RuntimeException("Undefined constant: CIVICRM_TEMPLATE_COMPILEDIR");
502 $templateCompileDir = CIVICRM_TEMPLATE_COMPILEDIR
;
504 $path = dirname($templateCompileDir);
506 //this fix is to avoid creation of upload dirs inside templates_c directory
507 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
509 $cnt = count($checkPath) - 1;
510 if ($checkPath[$cnt] == 'templates_c') {
511 unset($checkPath[$cnt]);
512 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
515 $_path = CRM_Utils_File
::addTrailingSlash($path);
521 * Determine if a path is absolute.
524 * TRUE if absolute. FALSE if relative.
526 public static function isAbsolute($path) {
527 if (substr($path, 0, 1) === DIRECTORY_SEPARATOR
) {
530 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
531 if (preg_match('!^[a-zA-Z]:[/\\\\]!', $path)) {
543 public static function relativeDirectory($directory) {
544 // Do nothing on windows
545 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
549 // check if directory is relative, if so return immediately
550 if (!self
::isAbsolute($directory)) {
554 // make everything relative from the baseFilePath
555 $basePath = self
::baseFilePath();
556 // check if basePath is a substr of $directory, if so
557 // return rest of string
558 if (substr($directory, 0, strlen($basePath)) == $basePath) {
559 return substr($directory, strlen($basePath));
562 // return the original value
568 * @param string|NULL $basePath
569 * The base path when evaluating relative paths. Should include trailing slash.
573 public static function absoluteDirectory($directory, $basePath = NULL) {
574 // check if directory is already absolute, if so return immediately
575 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
576 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
580 // check if directory is already absolute, if so return immediately
581 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
585 // make everything absolute from the baseFilePath
586 $basePath = ($basePath === NULL) ? self
::baseFilePath() : $basePath;
588 return $basePath . $directory;
592 * Make a file path relative to some base dir.
599 public static function relativize($directory, $basePath) {
600 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
601 $directory = strtr($directory, '\\', '/');
602 $basePath = strtr($basePath, '\\', '/');
604 if (substr($directory, 0, strlen($basePath)) == $basePath) {
605 return substr($directory, strlen($basePath));
613 * Create a path to a temporary file which can endure for multiple requests.
615 * @todo Automatic file cleanup using, eg, TTL policy
617 * @param string $prefix
619 * @return string, path to an openable/writable file
622 public static function tempnam($prefix = 'tmp-') {
623 // $config = CRM_Core_Config::singleton();
624 // $nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
625 // $fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
626 $fileName = tempnam(sys_get_temp_dir(), $prefix);
631 * Create a path to a temporary directory which can endure for multiple requests.
633 * @todo Automatic file cleanup using, eg, TTL policy
635 * @param string $prefix
637 * @return string, path to an openable/writable directory; ends with '/'
640 public static function tempdir($prefix = 'tmp-') {
641 $fileName = self
::tempnam($prefix);
643 mkdir($fileName, 0700);
644 return $fileName . '/';
648 * Search directory tree for files which match a glob pattern.
650 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
654 * @param string $pattern
655 * glob pattern, eg "*.txt".
656 * @param bool $relative
657 * TRUE if paths should be made relative to $dir
658 * @return array(string)
660 public static function findFiles($dir, $pattern, $relative = FALSE) {
661 $dir = rtrim($dir, '/');
662 $todos = array($dir);
664 while (!empty($todos)) {
665 $subdir = array_shift($todos);
666 $matches = glob("$subdir/$pattern");
667 if (is_array($matches)) {
668 foreach ($matches as $match) {
669 if (!is_dir($match)) {
670 $result[] = $relative ? CRM_Utils_File
::relativize($match, "$dir/") : $match;
674 if ($dh = opendir($subdir)) {
675 while (FALSE !== ($entry = readdir($dh))) {
676 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
677 if ($entry{0} == '.') {
680 elseif (is_dir($path)) {
691 * Determine if $child is a sub-directory of $parent
693 * @param string $parent
694 * @param string $child
695 * @param bool $checkRealPath
699 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
700 if ($checkRealPath) {
701 $parent = realpath($parent);
702 $child = realpath($child);
704 $parentParts = explode('/', rtrim($parent, '/'));
705 $childParts = explode('/', rtrim($child, '/'));
706 while (($parentPart = array_shift($parentParts)) !== NULL) {
707 $childPart = array_shift($childParts);
708 if ($parentPart != $childPart) {
712 if (empty($childParts)) {
713 return FALSE; // same directory
721 * Move $fromDir to $toDir, replacing/deleting any
722 * pre-existing content.
724 * @param string $fromDir
725 * The directory which should be moved.
726 * @param string $toDir
727 * The new location of the directory.
728 * @param bool $verbose
733 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
734 if (is_dir($toDir)) {
735 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
740 // return rename($fromDir, $toDir); CRM-11987, https://bugs.php.net/bug.php?id=54097
742 CRM_Utils_File
::copyDir($fromDir, $toDir);
743 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
744 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');