3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
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
49 static function isAscii($name) {
50 $fd = fopen($name, "r");
57 $line = fgets($fd, 8192);
58 if (!CRM_Utils_String
::isAscii($line)) {
69 * Given a file name, determine if the file contents make it an html file
71 * @param string $name name of file
73 * @return boolean true if file is html
76 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 the path name
102 * @param boolean $abort should we abort or just return an invalid code
108 static function createDir($path, $abort = TRUE) {
109 if (is_dir($path) ||
empty($path)) {
113 CRM_Utils_File
::createDir(dirname($path), $abort);
114 if (@mkdir
($path, 0777) == FALSE) {
116 $docLink = CRM_Utils_System
::docURL2('Moving an Existing Installation to a New Server or Location', NULL, NULL, NULL, NULL, "wiki");
117 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>";
119 CRM_Utils_System
::civiExit();
129 * Delete a directory given a path name, delete children directories
130 * and files if needed
132 * @param string $target the path name
134 * @param bool $verbose
141 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 ($sourcedir = @opendir
($target)) {
148 while (FALSE !== ($sibling = readdir($sourcedir))) {
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');
162 closedir($sourcedir);
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');
180 * @param $destination
182 static function copyDir($source, $destination) {
183 $dir = opendir($source);
184 @mkdir
($destination);
185 while (FALSE !== ($file = readdir($dir))) {
186 if (($file != '.') && ($file != '..')) {
187 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
188 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
191 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
199 * Given a file name, recode it (in place!) to UTF-8
201 * @param string $name name of file
203 * @return boolean whether the file was recoded properly
206 static function toUtf8($name) {
207 static $config = NULL;
208 static $legacyEncoding = NULL;
209 if ($config == NULL) {
210 $config = CRM_Core_Config
::singleton();
211 $legacyEncoding = $config->legacyEncoding
;
214 if (!function_exists('iconv')) {
220 $contents = file_get_contents($name);
221 if ($contents === FALSE) {
225 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
226 if ($contents === FALSE) {
230 $file = fopen($name, 'w');
231 if ($file === FALSE) {
235 $written = fwrite($file, $contents);
236 $closed = fclose($file);
237 if ($written === FALSE or !$closed) {
245 * Appends a slash to the end of a string if it doesn't already end with one
247 * @param string $path
248 * @param string $slash
254 static function addTrailingSlash($path, $slash = NULL) {
256 // FIXME: Defaulting to backslash on windows systems can produce unexpected results, esp for URL strings which should always use forward-slashes.
257 // I think this fn should default to forward-slash instead.
258 $slash = DIRECTORY_SEPARATOR
;
260 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
268 * @param string $fileName
269 * @param null $prefix
270 * @param bool $isQueryString
271 * @param bool $dieOnErrors
273 static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
274 require_once 'DB.php';
276 $db = DB
::connect($dsn);
277 if (PEAR
::isError($db)) {
278 die("Cannot open $dsn: " . $db->getMessage());
280 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
281 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
284 if (!$isQueryString) {
285 $string = $prefix . file_get_contents($fileName);
288 // use filename as query string
289 $string = $prefix . $fileName;
292 //get rid of comments starting with # and --
294 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
295 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
297 $queries = preg_split('/;\s*$/m', $string);
298 foreach ($queries as $query) {
299 $query = trim($query);
300 if (!empty($query)) {
301 CRM_Core_Error
::debug_query($query);
302 $res = &$db->query($query);
303 if (PEAR
::isError($res)) {
305 die("Cannot execute $query: " . $res->getMessage());
308 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
320 static function isExtensionSafe($ext) {
321 static $extensions = NULL;
323 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
325 //make extensions to lowercase
326 $extensions = array_change_key_case($extensions, CASE_LOWER
);
327 // allow html/htm extension ONLY if the user is admin
328 // and/or has access CiviMail
329 if (!(CRM_Core_Permission
::check('access CiviMail') ||
330 CRM_Core_Permission
::check('administer CiviCRM') ||
331 (CRM_Mailing_Info
::workflowEnabled() &&
332 CRM_Core_Permission
::check('create mailings')
335 unset($extensions['html']);
336 unset($extensions['htm']);
339 //support lower and uppercase file extensions
340 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
344 * Determine whether a given file is listed in the PHP include path
346 * @param string $name name of file
348 * @return boolean whether the file can be include()d or require()d
350 static function isIncludable($name) {
351 $x = @fopen
($name, 'r', TRUE);
362 * Remove the 32 bit md5 we add to the fileName
363 * also remove the unknown tag if we added it
365 static function cleanFileName($name) {
366 // replace the last 33 character before the '.' with null
367 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
372 * @param string $name
376 static function makeFileName($name) {
377 $uniqID = md5(uniqid(rand(), TRUE));
378 $info = pathinfo($name);
379 $basename = substr($info['basename'],
380 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
382 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
383 // munge extension so it cannot have an embbeded dot in it
384 // The maximum length of a filename for most filesystems is 255 chars.
385 // We'll truncate at 240 to give some room for the extension.
386 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
389 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
399 static function getFilesByExtension($path, $ext) {
400 $path = self
::addTrailingSlash($path);
401 $dh = opendir($path);
403 while (FALSE !== ($elem = readdir($dh))) {
404 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
405 $files[] .= $path . $elem;
413 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
415 * @param string $dir the directory to be secured
416 * @param bool $overwrite
418 static function restrictAccess($dir, $overwrite = FALSE) {
419 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
420 // of site, causing site to stop functioning.
421 // FIXME: we should do more checks here -
422 if (!empty($dir) && is_dir($dir)) {
423 $htaccess = <<<HTACCESS
430 $file = $dir . '.htaccess';
431 if ($overwrite ||
!file_exists($file)) {
432 if (file_put_contents($file, $htaccess) === FALSE) {
433 CRM_Core_Error
::movedSiteError($file);
440 * Restrict remote users from browsing the given directory.
444 static function restrictBrowsing($publicDir) {
445 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
450 $nobrowse = realpath($publicDir) . '/index.html';
451 if (!file_exists($nobrowse)) {
452 @file_put_contents
($nobrowse, '');
456 $dir = new RecursiveDirectoryIterator($publicDir);
457 foreach ($dir as $name => $object) {
458 if (is_dir($name) && $name != '..') {
459 $nobrowse = realpath($name) . '/index.html';
460 if (!file_exists($nobrowse)) {
461 @file_put_contents
($nobrowse, '');
468 * Create the base file path from which all our internal directories are
469 * offset. This is derived from the template compile directory set
471 static function baseFilePath($templateCompileDir = NULL) {
472 static $_path = NULL;
474 if ($templateCompileDir == NULL) {
475 $config = CRM_Core_Config
::singleton();
476 $templateCompileDir = $config->templateCompileDir
;
479 $path = dirname($templateCompileDir);
481 //this fix is to avoid creation of upload dirs inside templates_c directory
482 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
484 $cnt = count($checkPath) - 1;
485 if ($checkPath[$cnt] == 'templates_c') {
486 unset($checkPath[$cnt]);
487 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
490 $_path = CRM_Utils_File
::addTrailingSlash($path);
500 static function relativeDirectory($directory) {
501 // Do nothing on windows
502 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
506 // check if directory is relative, if so return immediately
507 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR
) {
511 // make everything relative from the baseFilePath
512 $basePath = self
::baseFilePath();
513 // check if basePath is a substr of $directory, if so
514 // return rest of string
515 if (substr($directory, 0, strlen($basePath)) == $basePath) {
516 return substr($directory, strlen($basePath));
519 // return the original value
528 static function absoluteDirectory($directory) {
529 // check if directory is already absolute, if so return immediately
530 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
531 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
535 // check if directory is already absolute, if so return immediately
536 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
540 // make everything absolute from the baseFilePath
541 $basePath = self
::baseFilePath();
543 return $basePath . $directory;
547 * Make a file path relative to some base dir
554 static function relativize($directory, $basePath) {
555 if (substr($directory, 0, strlen($basePath)) == $basePath) {
556 return substr($directory, strlen($basePath));
563 * Create a path to a temporary file which can endure for multiple requests
565 * TODO: Automatic file cleanup using, eg, TTL policy
567 * @param $prefix string
569 * @return string, path to an openable/writable file
572 static function tempnam($prefix = 'tmp-') {
573 //$config = CRM_Core_Config::singleton();
574 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
575 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
576 $fileName = tempnam(sys_get_temp_dir(), $prefix);
581 * Create a path to a temporary directory which can endure for multiple requests
583 * TODO: Automatic file cleanup using, eg, TTL policy
585 * @param $prefix string
587 * @return string, path to an openable/writable directory; ends with '/'
590 static function tempdir($prefix = 'tmp-') {
591 $fileName = self
::tempnam($prefix);
593 mkdir($fileName, 0700);
594 return $fileName . '/';
598 * Search directory tree for files which match a glob pattern.
600 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
602 * @param $dir string, base dir
603 * @param $pattern string, glob pattern, eg "*.txt"
604 * @return array(string)
606 static function findFiles($dir, $pattern) {
607 $todos = array($dir);
609 while (!empty($todos)) {
610 $subdir = array_shift($todos);
611 $matches = glob("$subdir/$pattern");
612 if (is_array($matches)) {
613 foreach ($matches as $match) {
614 if (!is_dir($match)) {
619 $dh = opendir($subdir);
621 while (FALSE !== ($entry = readdir($dh))) {
622 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
623 if ($entry{0} == '.') {
625 } elseif (is_dir($path)) {
636 * Determine if $child is a sub-directory of $parent
638 * @param string $parent
639 * @param string $child
640 * @param bool $checkRealPath
644 static function isChildPath($parent, $child, $checkRealPath = TRUE) {
645 if ($checkRealPath) {
646 $parent = realpath($parent);
647 $child = realpath($child);
649 $parentParts = explode('/', rtrim($parent, '/'));
650 $childParts = explode('/', rtrim($child, '/'));
651 while (($parentPart = array_shift($parentParts)) !== NULL) {
652 $childPart = array_shift($childParts);
653 if ($parentPart != $childPart) {
657 if (empty($childParts)) {
658 return FALSE; // same directory
665 * Move $fromDir to $toDir, replacing/deleting any
666 * pre-existing content.
668 * @param string $fromDir the directory which should be moved
669 * @param string $toDir the new location of the directory
670 * @param bool $verbose
672 * @return bool TRUE on success
674 static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
675 if (is_dir($toDir)) {
676 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
681 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
683 CRM_Utils_File
::copyDir($fromDir, $toDir);
684 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
685 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');