3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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-2014
37 * class to provide simple static functions for file objects
39 class CRM_Utils_File
{
42 * Given a file name, determine if the file contents make it an ascii file
44 * @param string $name name of file
46 * @return boolean 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
70 * @param string $name name of file
72 * @return boolean true if file is html
74 public static function isHtml($name) {
75 $fd = fopen($name, "r");
82 while (!feof($fd) & $lineCount <= 5) {
84 $line = fgets($fd, 8192);
85 if (!CRM_Utils_String
::isHtml($line)) {
96 * Create a directory given a path name, creates parent directories
99 * @param string $path the path name
100 * @param boolean $abort should we abort or just return an invalid code
105 public static function createDir($path, $abort = TRUE) {
106 if (is_dir($path) ||
empty($path)) {
110 CRM_Utils_File
::createDir(dirname($path), $abort);
111 if (@mkdir
($path, 0777) == FALSE) {
113 $docLink = CRM_Utils_System
::docURL2('Moving an Existing Installation to a New Server or Location', NULL, NULL, NULL, NULL, "wiki");
114 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>";
116 CRM_Utils_System
::civiExit();
126 * Delete a directory given a path name, delete children directories
127 * and files if needed
129 * @param string $target the path name
131 * @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 ($sourcedir = @opendir
($target)) {
144 while (FALSE !== ($sibling = readdir($sourcedir))) {
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');
158 closedir($sourcedir);
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');
176 * @param $destination
178 public static function copyDir($source, $destination) {
179 $dir = opendir($source);
180 @mkdir
($destination);
181 while (FALSE !== ($file = readdir($dir))) {
182 if (($file != '.') && ($file != '..')) {
183 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
184 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
187 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
195 * Given a file name, recode it (in place!) to UTF-8
197 * @param string $name name of file
199 * @return boolean whether the file was recoded properly
201 public static function toUtf8($name) {
202 static $config = NULL;
203 static $legacyEncoding = NULL;
204 if ($config == NULL) {
205 $config = CRM_Core_Config
::singleton();
206 $legacyEncoding = $config->legacyEncoding
;
209 if (!function_exists('iconv')) {
215 $contents = file_get_contents($name);
216 if ($contents === FALSE) {
220 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
221 if ($contents === FALSE) {
225 $file = fopen($name, 'w');
226 if ($file === FALSE) {
230 $written = fwrite($file, $contents);
231 $closed = fclose($file);
232 if ($written === FALSE or !$closed) {
240 * Appends a slash to the end of a string if it doesn't already end with one
242 * @param string $path
243 * @param string $slash
248 public static function addTrailingSlash($path, $slash = NULL) {
250 // FIXME: Defaulting to backslash on windows systems can produce unexpected results, esp for URL strings which should always use forward-slashes.
251 // I think this fn should default to forward-slash instead.
252 $slash = DIRECTORY_SEPARATOR
;
254 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
262 * @param string $fileName
263 * @param null $prefix
264 * @param bool $isQueryString
265 * @param bool $dieOnErrors
267 public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
268 require_once 'DB.php';
270 $db = DB
::connect($dsn);
271 if (PEAR
::isError($db)) {
272 die("Cannot open $dsn: " . $db->getMessage());
274 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
275 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
278 if (!$isQueryString) {
279 $string = $prefix . file_get_contents($fileName);
282 // use filename as query string
283 $string = $prefix . $fileName;
286 //get rid of comments starting with # and --
288 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
289 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
291 $queries = preg_split('/;\s*$/m', $string);
292 foreach ($queries as $query) {
293 $query = trim($query);
294 if (!empty($query)) {
295 CRM_Core_Error
::debug_query($query);
296 $res = &$db->query($query);
297 if (PEAR
::isError($res)) {
299 die("Cannot execute $query: " . $res->getMessage());
302 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
314 public static function isExtensionSafe($ext) {
315 static $extensions = NULL;
317 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
319 //make extensions to lowercase
320 $extensions = array_change_key_case($extensions, CASE_LOWER
);
321 // allow html/htm extension ONLY if the user is admin
322 // and/or has access CiviMail
323 if (!(CRM_Core_Permission
::check('access CiviMail') ||
324 CRM_Core_Permission
::check('administer CiviCRM') ||
325 (CRM_Mailing_Info
::workflowEnabled() &&
326 CRM_Core_Permission
::check('create mailings')
329 unset($extensions['html']);
330 unset($extensions['htm']);
333 //support lower and uppercase file extensions
334 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
338 * Determine whether a given file is listed in the PHP include path
340 * @param string $name name of file
342 * @return boolean whether the file can be include()d or require()d
344 public static function isIncludable($name) {
345 $x = @fopen
($name, 'r', TRUE);
356 * Remove the 32 bit md5 we add to the fileName
357 * also remove the unknown tag if we added it
359 public static function cleanFileName($name) {
360 // replace the last 33 character before the '.' with null
361 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
366 * @param string $name
370 public static function makeFileName($name) {
371 $uniqID = md5(uniqid(rand(), TRUE));
372 $info = pathinfo($name);
373 $basename = substr($info['basename'],
374 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
376 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
377 // munge extension so it cannot have an embbeded dot in it
378 // The maximum length of a filename for most filesystems is 255 chars.
379 // We'll truncate at 240 to give some room for the extension.
380 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
383 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
393 public static function getFilesByExtension($path, $ext) {
394 $path = self
::addTrailingSlash($path);
395 $dh = opendir($path);
397 while (FALSE !== ($elem = readdir($dh))) {
398 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
399 $files[] .= $path . $elem;
407 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
409 * @param string $dir the directory to be secured
410 * @param bool $overwrite
412 public static function restrictAccess($dir, $overwrite = FALSE) {
413 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
414 // of site, causing site to stop functioning.
415 // FIXME: we should do more checks here -
416 if (!empty($dir) && is_dir($dir)) {
417 $htaccess = <<<HTACCESS
424 $file = $dir . '.htaccess';
425 if ($overwrite ||
!file_exists($file)) {
426 if (file_put_contents($file, $htaccess) === FALSE) {
427 CRM_Core_Error
::movedSiteError($file);
434 * Restrict remote users from browsing the given directory.
438 public static function restrictBrowsing($publicDir) {
439 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
444 $nobrowse = realpath($publicDir) . '/index.html';
445 if (!file_exists($nobrowse)) {
446 @file_put_contents
($nobrowse, '');
450 $dir = new RecursiveDirectoryIterator($publicDir);
451 foreach ($dir as $name => $object) {
452 if (is_dir($name) && $name != '..') {
453 $nobrowse = realpath($name) . '/index.html';
454 if (!file_exists($nobrowse)) {
455 @file_put_contents
($nobrowse, '');
462 * Create the base file path from which all our internal directories are
463 * offset. This is derived from the template compile directory set
465 public static function baseFilePath($templateCompileDir = NULL) {
466 static $_path = NULL;
468 if ($templateCompileDir == NULL) {
469 $config = CRM_Core_Config
::singleton();
470 $templateCompileDir = $config->templateCompileDir
;
473 $path = dirname($templateCompileDir);
475 //this fix is to avoid creation of upload dirs inside templates_c directory
476 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
478 $cnt = count($checkPath) - 1;
479 if ($checkPath[$cnt] == 'templates_c') {
480 unset($checkPath[$cnt]);
481 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
484 $_path = CRM_Utils_File
::addTrailingSlash($path);
494 public static function relativeDirectory($directory) {
495 // Do nothing on windows
496 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
500 // check if directory is relative, if so return immediately
501 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR
) {
505 // make everything relative from the baseFilePath
506 $basePath = self
::baseFilePath();
507 // check if basePath is a substr of $directory, if so
508 // return rest of string
509 if (substr($directory, 0, strlen($basePath)) == $basePath) {
510 return substr($directory, strlen($basePath));
513 // return the original value
522 public static function absoluteDirectory($directory) {
523 // check if directory is already absolute, if so return immediately
524 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
525 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
529 // check if directory is already absolute, if so return immediately
530 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
534 // make everything absolute from the baseFilePath
535 $basePath = self
::baseFilePath();
537 return $basePath . $directory;
541 * Make a file path relative to some base dir
548 public static function relativize($directory, $basePath) {
549 if (substr($directory, 0, strlen($basePath)) == $basePath) {
550 return substr($directory, strlen($basePath));
557 * Create a path to a temporary file which can endure for multiple requests
559 * TODO: Automatic file cleanup using, eg, TTL policy
561 * @param $prefix string
563 * @return string, path to an openable/writable file
566 public static function tempnam($prefix = 'tmp-') {
567 //$config = CRM_Core_Config::singleton();
568 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
569 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
570 $fileName = tempnam(sys_get_temp_dir(), $prefix);
575 * Create a path to a temporary directory which can endure for multiple requests
577 * TODO: Automatic file cleanup using, eg, TTL policy
579 * @param $prefix string
581 * @return string, path to an openable/writable directory; ends with '/'
584 public static function tempdir($prefix = 'tmp-') {
585 $fileName = self
::tempnam($prefix);
587 mkdir($fileName, 0700);
588 return $fileName . '/';
592 * Search directory tree for files which match a glob pattern.
594 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
596 * @param $dir string, base dir
597 * @param $pattern string, glob pattern, eg "*.txt"
598 * @return array(string)
600 public static function findFiles($dir, $pattern) {
601 $todos = array($dir);
603 while (!empty($todos)) {
604 $subdir = array_shift($todos);
605 $matches = glob("$subdir/$pattern");
606 if (is_array($matches)) {
607 foreach ($matches as $match) {
608 if (!is_dir($match)) {
613 $dh = opendir($subdir);
615 while (FALSE !== ($entry = readdir($dh))) {
616 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
617 if ($entry{0} == '.') {
619 } elseif (is_dir($path)) {
630 * Determine if $child is a sub-directory of $parent
632 * @param string $parent
633 * @param string $child
634 * @param bool $checkRealPath
638 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
639 if ($checkRealPath) {
640 $parent = realpath($parent);
641 $child = realpath($child);
643 $parentParts = explode('/', rtrim($parent, '/'));
644 $childParts = explode('/', rtrim($child, '/'));
645 while (($parentPart = array_shift($parentParts)) !== NULL) {
646 $childPart = array_shift($childParts);
647 if ($parentPart != $childPart) {
651 if (empty($childParts)) {
652 return FALSE; // same directory
659 * Move $fromDir to $toDir, replacing/deleting any
660 * pre-existing content.
662 * @param string $fromDir the directory which should be moved
663 * @param string $toDir the new location of the directory
664 * @param bool $verbose
666 * @return bool TRUE on success
668 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
669 if (is_dir($toDir)) {
670 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
675 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
677 CRM_Utils_File
::copyDir($fromDir, $toDir);
678 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
679 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');