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 * NULL: Folder already exists or was not specified.
107 * TRUE: Creation succeeded.
108 * FALSE: Creation failed.
110 public static function createDir($path, $abort = TRUE) {
111 if (is_dir($path) ||
empty($path)) {
115 CRM_Utils_File
::createDir(dirname($path), $abort);
116 if (@mkdir
($path, 0777) == FALSE) {
118 $docLink = CRM_Utils_System
::docURL2('Moving an Existing Installation to a New Server or Location', NULL, NULL, NULL, NULL, "wiki");
119 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>";
121 CRM_Utils_System
::civiExit();
131 * Delete a directory given a path name, delete children directories
132 * and files if needed
134 * @param string $target
137 * @param bool $verbose
141 public static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
142 static $exceptions = array('.', '..');
143 if ($target == '' ||
$target == '/') {
144 throw new Exception("Overly broad deletion");
147 if ($dh = @opendir
($target)) {
148 while (FALSE !== ($sibling = readdir($dh))) {
149 if (!in_array($sibling, $exceptions)) {
150 $object = $target . DIRECTORY_SEPARATOR
. $sibling;
152 if (is_dir($object)) {
153 CRM_Utils_File
::cleanDir($object, $rmdir, $verbose);
155 elseif (is_file($object)) {
156 if (!unlink($object)) {
157 CRM_Core_Session
::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
165 if (rmdir($target)) {
167 CRM_Core_Session
::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
172 CRM_Core_Session
::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
179 * Concatenate several files.
181 * @param array $files
182 * List of file names.
183 * @param string $delim
184 * An optional delimiter to put between files.
187 public static function concat($files, $delim = '') {
190 foreach ($files as $file) {
194 $buf .= file_get_contents($file);
201 * @param string $source
202 * @param string $destination
204 public static function copyDir($source, $destination) {
205 if ($dh = opendir($source)) {
206 @mkdir
($destination);
207 while (FALSE !== ($file = readdir($dh))) {
208 if (($file != '.') && ($file != '..')) {
209 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
210 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
213 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
222 * Given a file name, recode it (in place!) to UTF-8
224 * @param string $name
228 * whether the file was recoded properly
230 public static function toUtf8($name) {
231 static $config = NULL;
232 static $legacyEncoding = NULL;
233 if ($config == NULL) {
234 $config = CRM_Core_Config
::singleton();
235 $legacyEncoding = $config->legacyEncoding
;
238 if (!function_exists('iconv')) {
244 $contents = file_get_contents($name);
245 if ($contents === FALSE) {
249 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
250 if ($contents === FALSE) {
254 $file = fopen($name, 'w');
255 if ($file === FALSE) {
259 $written = fwrite($file, $contents);
260 $closed = fclose($file);
261 if ($written === FALSE or !$closed) {
269 * Appends a slash to the end of a string if it doesn't already end with one
271 * @param string $path
272 * @param string $slash
276 public static function addTrailingSlash($path, $slash = NULL) {
278 // FIXME: Defaulting to backslash on windows systems can produce
279 // unexpected results, esp for URL strings which should always use forward-slashes.
280 // I think this fn should default to forward-slash instead.
281 $slash = DIRECTORY_SEPARATOR
;
283 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
291 * @param string $fileName
292 * @param null $prefix
293 * @param bool $isQueryString
294 * @param bool $dieOnErrors
296 public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
297 require_once 'DB.php';
299 $db = DB
::connect($dsn);
300 if (PEAR
::isError($db)) {
301 die("Cannot open $dsn: " . $db->getMessage());
303 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
304 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
307 if (!$isQueryString) {
308 $string = $prefix . file_get_contents($fileName);
311 // use filename as query string
312 $string = $prefix . $fileName;
315 // get rid of comments starting with # and --
317 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
318 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
320 $queries = preg_split('/;\s*$/m', $string);
321 foreach ($queries as $query) {
322 $query = trim($query);
323 if (!empty($query)) {
324 CRM_Core_Error
::debug_query($query);
325 $res = &$db->query($query);
326 if (PEAR
::isError($res)) {
328 die("Cannot execute $query: " . $res->getMessage());
331 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
343 public static function isExtensionSafe($ext) {
344 static $extensions = NULL;
346 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
348 // make extensions to lowercase
349 $extensions = array_change_key_case($extensions, CASE_LOWER
);
350 // allow html/htm extension ONLY if the user is admin
351 // and/or has access CiviMail
352 if (!(CRM_Core_Permission
::check('access CiviMail') ||
353 CRM_Core_Permission
::check('administer CiviCRM') ||
354 (CRM_Mailing_Info
::workflowEnabled() &&
355 CRM_Core_Permission
::check('create mailings')
359 unset($extensions['html']);
360 unset($extensions['htm']);
363 // support lower and uppercase file extensions
364 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
368 * Determine whether a given file is listed in the PHP include path.
370 * @param string $name
374 * whether the file can be include()d or require()d
376 public static function isIncludable($name) {
377 $x = @fopen
($name, 'r', TRUE);
388 * Remove the 32 bit md5 we add to the fileName also remove the unknown tag if we added it.
394 public static function cleanFileName($name) {
395 // replace the last 33 character before the '.' with null
396 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
401 * @param string $name
405 public static function makeFileName($name) {
406 $uniqID = md5(uniqid(rand(), TRUE));
407 $info = pathinfo($name);
408 $basename = substr($info['basename'],
409 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
411 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
412 // munge extension so it cannot have an embbeded dot in it
413 // The maximum length of a filename for most filesystems is 255 chars.
414 // We'll truncate at 240 to give some room for the extension.
415 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
418 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
428 public static function getFilesByExtension($path, $ext) {
429 $path = self
::addTrailingSlash($path);
431 if ($dh = opendir($path)) {
432 while (FALSE !== ($elem = readdir($dh))) {
433 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
434 $files[] .= $path . $elem;
443 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
446 * The directory to be secured.
447 * @param bool $overwrite
449 public static function restrictAccess($dir, $overwrite = FALSE) {
450 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
451 // of site, causing site to stop functioning.
452 // FIXME: we should do more checks here -
453 if (!empty($dir) && is_dir($dir)) {
454 $htaccess = <<<HTACCESS
461 $file = $dir . '.htaccess';
462 if ($overwrite ||
!file_exists($file)) {
463 if (file_put_contents($file, $htaccess) === FALSE) {
464 CRM_Core_Error
::movedSiteError($file);
471 * Restrict remote users from browsing the given directory.
475 public static function restrictBrowsing($publicDir) {
476 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
481 $nobrowse = realpath($publicDir) . '/index.html';
482 if (!file_exists($nobrowse)) {
483 @file_put_contents
($nobrowse, '');
487 $dir = new RecursiveDirectoryIterator($publicDir);
488 foreach ($dir as $name => $object) {
489 if (is_dir($name) && $name != '..') {
490 $nobrowse = realpath($name) . '/index.html';
491 if (!file_exists($nobrowse)) {
492 @file_put_contents
($nobrowse, '');
499 * Create the base file path from which all our internal directories are
500 * offset. This is derived from the template compile directory set
502 public static function baseFilePath() {
503 static $_path = NULL;
505 // Note: Don't rely on $config; that creates a dependency loop.
506 if (!defined('CIVICRM_TEMPLATE_COMPILEDIR')) {
507 throw new RuntimeException("Undefined constant: CIVICRM_TEMPLATE_COMPILEDIR");
509 $templateCompileDir = CIVICRM_TEMPLATE_COMPILEDIR
;
511 $path = dirname($templateCompileDir);
513 //this fix is to avoid creation of upload dirs inside templates_c directory
514 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
516 $cnt = count($checkPath) - 1;
517 if ($checkPath[$cnt] == 'templates_c') {
518 unset($checkPath[$cnt]);
519 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
522 $_path = CRM_Utils_File
::addTrailingSlash($path);
528 * Determine if a path is absolute.
530 * @param string $path
533 * TRUE if absolute. FALSE if relative.
535 public static function isAbsolute($path) {
536 if (substr($path, 0, 1) === DIRECTORY_SEPARATOR
) {
539 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
540 if (preg_match('!^[a-zA-Z]:[/\\\\]!', $path)) {
552 public static function relativeDirectory($directory) {
553 // Do nothing on windows
554 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
558 // check if directory is relative, if so return immediately
559 if (!self
::isAbsolute($directory)) {
563 // make everything relative from the baseFilePath
564 $basePath = self
::baseFilePath();
565 // check if basePath is a substr of $directory, if so
566 // return rest of string
567 if (substr($directory, 0, strlen($basePath)) == $basePath) {
568 return substr($directory, strlen($basePath));
571 // return the original value
577 * @param string|NULL $basePath
578 * The base path when evaluating relative paths. Should include trailing slash.
582 public static function absoluteDirectory($directory, $basePath = NULL) {
583 // check if directory is already absolute, if so return immediately
584 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
585 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
589 // check if directory is already absolute, if so return immediately
590 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
594 // make everything absolute from the baseFilePath
595 $basePath = ($basePath === NULL) ? self
::baseFilePath() : $basePath;
597 return $basePath . $directory;
601 * Make a file path relative to some base dir.
608 public static function relativize($directory, $basePath) {
609 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
610 $directory = strtr($directory, '\\', '/');
611 $basePath = strtr($basePath, '\\', '/');
613 if (substr($directory, 0, strlen($basePath)) == $basePath) {
614 return substr($directory, strlen($basePath));
622 * Create a path to a temporary file which can endure for multiple requests.
624 * @todo Automatic file cleanup using, eg, TTL policy
626 * @param string $prefix
628 * @return string, path to an openable/writable file
631 public static function tempnam($prefix = 'tmp-') {
632 // $config = CRM_Core_Config::singleton();
633 // $nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
634 // $fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
635 $fileName = tempnam(sys_get_temp_dir(), $prefix);
640 * Create a path to a temporary directory which can endure for multiple requests.
642 * @todo Automatic file cleanup using, eg, TTL policy
644 * @param string $prefix
646 * @return string, path to an openable/writable directory; ends with '/'
649 public static function tempdir($prefix = 'tmp-') {
650 $fileName = self
::tempnam($prefix);
652 mkdir($fileName, 0700);
653 return $fileName . '/';
657 * Search directory tree for files which match a glob pattern.
659 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
663 * @param string $pattern
664 * glob pattern, eg "*.txt".
665 * @param bool $relative
666 * TRUE if paths should be made relative to $dir
667 * @return array(string)
669 public static function findFiles($dir, $pattern, $relative = FALSE) {
673 $dir = rtrim($dir, '/');
674 $todos = array($dir);
676 while (!empty($todos)) {
677 $subdir = array_shift($todos);
678 $matches = glob("$subdir/$pattern");
679 if (is_array($matches)) {
680 foreach ($matches as $match) {
681 if (!is_dir($match)) {
682 $result[] = $relative ? CRM_Utils_File
::relativize($match, "$dir/") : $match;
686 if ($dh = opendir($subdir)) {
687 while (FALSE !== ($entry = readdir($dh))) {
688 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
689 if ($entry{0} == '.') {
692 elseif (is_dir($path)) {
703 * Determine if $child is a sub-directory of $parent
705 * @param string $parent
706 * @param string $child
707 * @param bool $checkRealPath
711 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
712 if ($checkRealPath) {
713 $parent = realpath($parent);
714 $child = realpath($child);
716 $parentParts = explode('/', rtrim($parent, '/'));
717 $childParts = explode('/', rtrim($child, '/'));
718 while (($parentPart = array_shift($parentParts)) !== NULL) {
719 $childPart = array_shift($childParts);
720 if ($parentPart != $childPart) {
724 if (empty($childParts)) {
725 return FALSE; // same directory
733 * Move $fromDir to $toDir, replacing/deleting any
734 * pre-existing content.
736 * @param string $fromDir
737 * The directory which should be moved.
738 * @param string $toDir
739 * The new location of the directory.
740 * @param bool $verbose
745 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
746 if (is_dir($toDir)) {
747 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
752 // return rename($fromDir, $toDir); CRM-11987, https://bugs.php.net/bug.php?id=54097
754 CRM_Utils_File
::copyDir($fromDir, $toDir);
755 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
756 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');