3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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-2013
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 $path the path name
138 static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
139 static $exceptions = array('.', '..');
140 if ($target == '' ||
$target == '/') {
141 throw new Exception("Overly broad deletion");
144 if ($sourcedir = @opendir
($target)) {
145 while (FALSE !== ($sibling = readdir($sourcedir))) {
146 if (!in_array($sibling, $exceptions)) {
147 $object = $target . DIRECTORY_SEPARATOR
. $sibling;
149 if (is_dir($object)) {
150 CRM_Utils_File
::cleanDir($object, $rmdir, $verbose);
152 elseif (is_file($object)) {
153 if (!unlink($object)) {
154 CRM_Core_Session
::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
159 closedir($sourcedir);
162 if (rmdir($target)) {
164 CRM_Core_Session
::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
169 CRM_Core_Session
::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
175 static function copyDir($source, $destination) {
176 $dir = opendir($source);
177 @mkdir
($destination);
178 while (FALSE !== ($file = readdir($dir))) {
179 if (($file != '.') && ($file != '..')) {
180 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
181 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
184 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
192 * Given a file name, recode it (in place!) to UTF-8
194 * @param string $name name of file
196 * @return boolean whether the file was recoded properly
199 static function toUtf8($name) {
200 static $config = NULL;
201 static $legacyEncoding = NULL;
202 if ($config == NULL) {
203 $config = CRM_Core_Config
::singleton();
204 $legacyEncoding = $config->legacyEncoding
;
207 if (!function_exists('iconv')) {
213 $contents = file_get_contents($name);
214 if ($contents === FALSE) {
218 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
219 if ($contents === FALSE) {
223 $file = fopen($name, 'w');
224 if ($file === FALSE) {
228 $written = fwrite($file, $contents);
229 $closed = fclose($file);
230 if ($written === FALSE or !$closed) {
238 * Appends trailing slashed to paths
244 static function addTrailingSlash($name, $separator = NULL) {
246 $separator = DIRECTORY_SEPARATOR
;
249 if (substr($name, -1, 1) != $separator) {
255 static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
256 require_once 'DB.php';
258 $db = DB
::connect($dsn);
259 if (PEAR
::isError($db)) {
260 die("Cannot open $dsn: " . $db->getMessage());
262 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
263 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
266 if (!$isQueryString) {
267 $string = $prefix . file_get_contents($fileName);
270 // use filename as query string
271 $string = $prefix . $fileName;
274 //get rid of comments starting with # and --
276 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
277 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
279 $queries = preg_split('/;\s*$/m', $string);
280 foreach ($queries as $query) {
281 $query = trim($query);
282 if (!empty($query)) {
283 CRM_Core_Error
::debug_query($query);
284 $res = &$db->query($query);
285 if (PEAR
::isError($res)) {
287 die("Cannot execute $query: " . $res->getMessage());
290 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
297 static function isExtensionSafe($ext) {
298 static $extensions = NULL;
300 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
302 //make extensions to lowercase
303 $extensions = array_change_key_case($extensions, CASE_LOWER
);
304 // allow html/htm extension ONLY if the user is admin
305 // and/or has access CiviMail
306 if (!(CRM_Core_Permission
::check('access CiviMail') ||
307 CRM_Core_Permission
::check('administer CiviCRM') ||
308 (CRM_Mailing_Info
::workflowEnabled() &&
309 CRM_Core_Permission
::check('create mailings')
312 unset($extensions['html']);
313 unset($extensions['htm']);
316 //support lower and uppercase file extensions
317 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
321 * Determine whether a given file is listed in the PHP include path
323 * @param string $name name of file
325 * @return boolean whether the file can be include()d or require()d
327 static function isIncludable($name) {
328 $x = @fopen
($name, 'r', TRUE);
339 * remove the 32 bit md5 we add to the fileName
340 * also remove the unknown tag if we added it
342 static function cleanFileName($name) {
343 // replace the last 33 character before the '.' with null
344 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
348 static function makeFileName($name) {
349 $uniqID = md5(uniqid(rand(), TRUE));
350 $info = pathinfo($name);
351 $basename = substr($info['basename'],
352 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
354 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
355 // munge extension so it cannot have an embbeded dot in it
356 // The maximum length of a filename for most filesystems is 255 chars.
357 // We'll truncate at 240 to give some room for the extension.
358 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
361 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
365 static function getFilesByExtension($path, $ext) {
366 $path = self
::addTrailingSlash($path);
367 $dh = opendir($path);
369 while (FALSE !== ($elem = readdir($dh))) {
370 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
371 $files[] .= $path . $elem;
379 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
381 * @param string $dir the directory to be secured
383 static function restrictAccess($dir) {
384 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
385 // of site, causing site to stop functioning.
386 // FIXME: we should do more checks here -
388 $htaccess = <<<HTACCESS
395 $file = $dir . '.htaccess';
396 if (file_put_contents($file, $htaccess) === FALSE) {
397 CRM_Core_Error
::movedSiteError($file);
403 * Create the base file path from which all our internal directories are
404 * offset. This is derived from the template compile directory set
406 static function baseFilePath($templateCompileDir = NULL) {
407 static $_path = NULL;
409 if ($templateCompileDir == NULL) {
410 $config = CRM_Core_Config
::singleton();
411 $templateCompileDir = $config->templateCompileDir
;
414 $path = dirname($templateCompileDir);
416 //this fix is to avoid creation of upload dirs inside templates_c directory
417 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
419 $cnt = count($checkPath) - 1;
420 if ($checkPath[$cnt] == 'templates_c') {
421 unset($checkPath[$cnt]);
422 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
425 $_path = CRM_Utils_File
::addTrailingSlash($path);
430 static function relativeDirectory($directory) {
431 // Do nothing on windows
432 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
436 // check if directory is relative, if so return immediately
437 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR
) {
441 // make everything relative from the baseFilePath
442 $basePath = self
::baseFilePath();
443 // check if basePath is a substr of $directory, if so
444 // return rest of string
445 if (substr($directory, 0, strlen($basePath)) == $basePath) {
446 return substr($directory, strlen($basePath));
449 // return the original value
453 static function absoluteDirectory($directory) {
454 // Do nothing on windows - config will need to specify absolute path
455 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
459 // check if directory is already absolute, if so return immediately
460 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
464 // make everything absolute from the baseFilePath
465 $basePath = self
::baseFilePath();
467 return $basePath . $directory;
471 * Make a file path relative to some base dir
475 static function relativize($directory, $basePath) {
476 if (substr($directory, 0, strlen($basePath)) == $basePath) {
477 return substr($directory, strlen($basePath));
484 * Create a path to a temporary file which can endure for multiple requests
486 * TODO: Automatic file cleanup using, eg, TTL policy
488 * @param $prefix string
490 * @return string, path to an openable/writable file
493 static function tempnam($prefix = 'tmp-') {
494 //$config = CRM_Core_Config::singleton();
495 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
496 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
497 $fileName = tempnam(sys_get_temp_dir(), $prefix);
502 * Create a path to a temporary directory which can endure for multiple requests
504 * TODO: Automatic file cleanup using, eg, TTL policy
506 * @param $prefix string
508 * @return string, path to an openable/writable directory; ends with '/'
511 static function tempdir($prefix = 'tmp-') {
512 $fileName = self
::tempnam($prefix);
514 mkdir($fileName, 0700);
515 return $fileName . '/';
519 * Search directory tree for files which match a glob pattern.
521 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
523 * @param $dir string, base dir
524 * @param $pattern string, glob pattern, eg "*.txt"
525 * @return array(string)
527 static function findFiles($dir, $pattern) {
528 $todos = array($dir);
530 while (!empty($todos)) {
531 $subdir = array_shift($todos);
532 foreach (glob("$subdir/$pattern") as $match) {
533 if (!is_dir($match)) {
537 $dh = opendir($subdir);
539 while (FALSE !== ($entry = readdir($dh))) {
540 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
541 if ($entry{0} == '.') {
543 } elseif (is_dir($path)) {
554 * Determine if $child is a sub-directory of $parent
556 * @param string $parent
557 * @param string $child
560 static function isChildPath($parent, $child, $checkRealPath = TRUE) {
561 if ($checkRealPath) {
562 $parent = realpath($parent);
563 $child = realpath($child);
565 $parentParts = explode('/', rtrim($parent, '/'));
566 $childParts = explode('/', rtrim($child, '/'));
567 while (($parentPart = array_shift($parentParts)) !== NULL) {
568 $childPart = array_shift($childParts);
569 if ($parentPart != $childPart) {
573 if (empty($childParts)) {
574 return FALSE; // same directory
581 * Move $fromDir to $toDir, replacing/deleting any
582 * pre-existing content.
584 * @param string $fromDir the directory which should be moved
585 * @param string $toDir the new location of the directory
586 * @return bool TRUE on success
588 static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
589 if (is_dir($toDir)) {
590 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
595 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
597 CRM_Utils_File
::copyDir($fromDir, $toDir);
598 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
599 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');