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
134 * @param bool $verbose
137 * @internal param string $path the path name
143 static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
144 static $exceptions = array('.', '..');
145 if ($target == '' ||
$target == '/') {
146 throw new Exception("Overly broad deletion");
149 if ($sourcedir = @opendir
($target)) {
150 while (FALSE !== ($sibling = readdir($sourcedir))) {
151 if (!in_array($sibling, $exceptions)) {
152 $object = $target . DIRECTORY_SEPARATOR
. $sibling;
154 if (is_dir($object)) {
155 CRM_Utils_File
::cleanDir($object, $rmdir, $verbose);
157 elseif (is_file($object)) {
158 if (!unlink($object)) {
159 CRM_Core_Session
::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
164 closedir($sourcedir);
167 if (rmdir($target)) {
169 CRM_Core_Session
::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
174 CRM_Core_Session
::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
180 static function copyDir($source, $destination) {
181 $dir = opendir($source);
182 @mkdir
($destination);
183 while (FALSE !== ($file = readdir($dir))) {
184 if (($file != '.') && ($file != '..')) {
185 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
186 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
189 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
197 * Given a file name, recode it (in place!) to UTF-8
199 * @param string $name name of file
201 * @return boolean whether the file was recoded properly
204 static function toUtf8($name) {
205 static $config = NULL;
206 static $legacyEncoding = NULL;
207 if ($config == NULL) {
208 $config = CRM_Core_Config
::singleton();
209 $legacyEncoding = $config->legacyEncoding
;
212 if (!function_exists('iconv')) {
218 $contents = file_get_contents($name);
219 if ($contents === FALSE) {
223 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
224 if ($contents === FALSE) {
228 $file = fopen($name, 'w');
229 if ($file === FALSE) {
233 $written = fwrite($file, $contents);
234 $closed = fclose($file);
235 if ($written === FALSE or !$closed) {
243 * Appends trailing slashed to paths
246 * @param null $separator
252 static function addTrailingSlash($name, $separator = NULL) {
254 $separator = DIRECTORY_SEPARATOR
;
257 if (substr($name, -1, 1) != $separator) {
263 static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
264 require_once 'DB.php';
266 $db = DB
::connect($dsn);
267 if (PEAR
::isError($db)) {
268 die("Cannot open $dsn: " . $db->getMessage());
270 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
271 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
274 if (!$isQueryString) {
275 $string = $prefix . file_get_contents($fileName);
278 // use filename as query string
279 $string = $prefix . $fileName;
282 //get rid of comments starting with # and --
284 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
285 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
287 $queries = preg_split('/;\s*$/m', $string);
288 foreach ($queries as $query) {
289 $query = trim($query);
290 if (!empty($query)) {
291 CRM_Core_Error
::debug_query($query);
292 $res = &$db->query($query);
293 if (PEAR
::isError($res)) {
295 die("Cannot execute $query: " . $res->getMessage());
298 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
305 static function isExtensionSafe($ext) {
306 static $extensions = NULL;
308 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
310 //make extensions to lowercase
311 $extensions = array_change_key_case($extensions, CASE_LOWER
);
312 // allow html/htm extension ONLY if the user is admin
313 // and/or has access CiviMail
314 if (!(CRM_Core_Permission
::check('access CiviMail') ||
315 CRM_Core_Permission
::check('administer CiviCRM') ||
316 (CRM_Mailing_Info
::workflowEnabled() &&
317 CRM_Core_Permission
::check('create mailings')
320 unset($extensions['html']);
321 unset($extensions['htm']);
324 //support lower and uppercase file extensions
325 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
329 * Determine whether a given file is listed in the PHP include path
331 * @param string $name name of file
333 * @return boolean whether the file can be include()d or require()d
335 static function isIncludable($name) {
336 $x = @fopen
($name, 'r', TRUE);
347 * remove the 32 bit md5 we add to the fileName
348 * also remove the unknown tag if we added it
350 static function cleanFileName($name) {
351 // replace the last 33 character before the '.' with null
352 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
356 static function makeFileName($name) {
357 $uniqID = md5(uniqid(rand(), TRUE));
358 $info = pathinfo($name);
359 $basename = substr($info['basename'],
360 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
362 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
363 // munge extension so it cannot have an embbeded dot in it
364 // The maximum length of a filename for most filesystems is 255 chars.
365 // We'll truncate at 240 to give some room for the extension.
366 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
369 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
373 static function getFilesByExtension($path, $ext) {
374 $path = self
::addTrailingSlash($path);
375 $dh = opendir($path);
377 while (FALSE !== ($elem = readdir($dh))) {
378 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
379 $files[] .= $path . $elem;
387 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
389 * @param string $dir the directory to be secured
390 * @param bool $overwrite
392 static function restrictAccess($dir, $overwrite = FALSE) {
393 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
394 // of site, causing site to stop functioning.
395 // FIXME: we should do more checks here -
396 if (!empty($dir) && is_dir($dir)) {
397 $htaccess = <<<HTACCESS
404 $file = $dir . '.htaccess';
405 if ($overwrite ||
!file_exists($file)) {
406 if (file_put_contents($file, $htaccess) === FALSE) {
407 CRM_Core_Error
::movedSiteError($file);
414 * Restrict remote users from browsing the given directory.
418 static function restrictBrowsing($publicDir) {
419 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
424 $nobrowse = realpath($publicDir) . '/index.html';
425 if (!file_exists($nobrowse)) {
426 @file_put_contents
($nobrowse, '');
430 $dir = new RecursiveDirectoryIterator($publicDir);
431 foreach ($dir as $name => $object) {
432 if (is_dir($name) && $name != '..') {
433 $nobrowse = realpath($name) . '/index.html';
434 if (!file_exists($nobrowse)) {
435 @file_put_contents
($nobrowse, '');
442 * Create the base file path from which all our internal directories are
443 * offset. This is derived from the template compile directory set
445 static function baseFilePath($templateCompileDir = NULL) {
446 static $_path = NULL;
448 if ($templateCompileDir == NULL) {
449 $config = CRM_Core_Config
::singleton();
450 $templateCompileDir = $config->templateCompileDir
;
453 $path = dirname($templateCompileDir);
455 //this fix is to avoid creation of upload dirs inside templates_c directory
456 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
458 $cnt = count($checkPath) - 1;
459 if ($checkPath[$cnt] == 'templates_c') {
460 unset($checkPath[$cnt]);
461 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
464 $_path = CRM_Utils_File
::addTrailingSlash($path);
469 static function relativeDirectory($directory) {
470 // Do nothing on windows
471 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
475 // check if directory is relative, if so return immediately
476 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR
) {
480 // make everything relative from the baseFilePath
481 $basePath = self
::baseFilePath();
482 // check if basePath is a substr of $directory, if so
483 // return rest of string
484 if (substr($directory, 0, strlen($basePath)) == $basePath) {
485 return substr($directory, strlen($basePath));
488 // return the original value
492 static function absoluteDirectory($directory) {
493 // Do nothing on windows - config will need to specify absolute path
494 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
498 // check if directory is already absolute, if so return immediately
499 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
503 // make everything absolute from the baseFilePath
504 $basePath = self
::baseFilePath();
506 return $basePath . $directory;
510 * Make a file path relative to some base dir
517 static function relativize($directory, $basePath) {
518 if (substr($directory, 0, strlen($basePath)) == $basePath) {
519 return substr($directory, strlen($basePath));
526 * Create a path to a temporary file which can endure for multiple requests
528 * TODO: Automatic file cleanup using, eg, TTL policy
530 * @param $prefix string
532 * @return string, path to an openable/writable file
535 static function tempnam($prefix = 'tmp-') {
536 //$config = CRM_Core_Config::singleton();
537 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
538 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
539 $fileName = tempnam(sys_get_temp_dir(), $prefix);
544 * Create a path to a temporary directory which can endure for multiple requests
546 * TODO: Automatic file cleanup using, eg, TTL policy
548 * @param $prefix string
550 * @return string, path to an openable/writable directory; ends with '/'
553 static function tempdir($prefix = 'tmp-') {
554 $fileName = self
::tempnam($prefix);
556 mkdir($fileName, 0700);
557 return $fileName . '/';
561 * Search directory tree for files which match a glob pattern.
563 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
565 * @param $dir string, base dir
566 * @param $pattern string, glob pattern, eg "*.txt"
567 * @return array(string)
569 static function findFiles($dir, $pattern) {
570 $todos = array($dir);
572 while (!empty($todos)) {
573 $subdir = array_shift($todos);
574 $matches = glob("$subdir/$pattern");
575 if (is_array($matches)) {
576 foreach ($matches as $match) {
577 if (!is_dir($match)) {
582 $dh = opendir($subdir);
584 while (FALSE !== ($entry = readdir($dh))) {
585 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
586 if ($entry{0} == '.') {
588 } elseif (is_dir($path)) {
599 * Determine if $child is a sub-directory of $parent
601 * @param string $parent
602 * @param string $child
603 * @param bool $checkRealPath
607 static function isChildPath($parent, $child, $checkRealPath = TRUE) {
608 if ($checkRealPath) {
609 $parent = realpath($parent);
610 $child = realpath($child);
612 $parentParts = explode('/', rtrim($parent, '/'));
613 $childParts = explode('/', rtrim($child, '/'));
614 while (($parentPart = array_shift($parentParts)) !== NULL) {
615 $childPart = array_shift($childParts);
616 if ($parentPart != $childPart) {
620 if (empty($childParts)) {
621 return FALSE; // same directory
628 * Move $fromDir to $toDir, replacing/deleting any
629 * pre-existing content.
631 * @param string $fromDir the directory which should be moved
632 * @param string $toDir the new location of the directory
633 * @param bool $verbose
635 * @return bool TRUE on success
637 static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
638 if (is_dir($toDir)) {
639 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
644 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
646 CRM_Utils_File
::copyDir($fromDir, $toDir);
647 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
648 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');