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 ($dh = @opendir
($target)) {
150 while (FALSE !== ($sibling = readdir($dh))) {
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');
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');
182 * @param $destination
184 static function copyDir($source, $destination) {
185 if ($dh = opendir($source)) {
186 @mkdir
($destination);
187 while (FALSE !== ($file = readdir($dh))) {
188 if (($file != '.') && ($file != '..')) {
189 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
190 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
193 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
202 * Given a file name, recode it (in place!) to UTF-8
204 * @param string $name name of file
206 * @return boolean whether the file was recoded properly
209 static function toUtf8($name) {
210 static $config = NULL;
211 static $legacyEncoding = NULL;
212 if ($config == NULL) {
213 $config = CRM_Core_Config
::singleton();
214 $legacyEncoding = $config->legacyEncoding
;
217 if (!function_exists('iconv')) {
223 $contents = file_get_contents($name);
224 if ($contents === FALSE) {
228 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
229 if ($contents === FALSE) {
233 $file = fopen($name, 'w');
234 if ($file === FALSE) {
238 $written = fwrite($file, $contents);
239 $closed = fclose($file);
240 if ($written === FALSE or !$closed) {
248 * Appends a slash to the end of a string if it doesn't already end with one
250 * @param string $path
251 * @param string $slash
257 static function addTrailingSlash($path, $slash = NULL) {
259 // FIXME: Defaulting to backslash on windows systems can produce unexpected results, esp for URL strings which should always use forward-slashes.
260 // I think this fn should default to forward-slash instead.
261 $slash = DIRECTORY_SEPARATOR
;
263 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
272 * @param null $prefix
273 * @param bool $isQueryString
274 * @param bool $dieOnErrors
276 static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
277 require_once 'DB.php';
279 $db = DB
::connect($dsn);
280 if (PEAR
::isError($db)) {
281 die("Cannot open $dsn: " . $db->getMessage());
283 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
284 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
287 if (!$isQueryString) {
288 $string = $prefix . file_get_contents($fileName);
291 // use filename as query string
292 $string = $prefix . $fileName;
295 //get rid of comments starting with # and --
297 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
298 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
300 $queries = preg_split('/;\s*$/m', $string);
301 foreach ($queries as $query) {
302 $query = trim($query);
303 if (!empty($query)) {
304 CRM_Core_Error
::debug_query($query);
305 $res = &$db->query($query);
306 if (PEAR
::isError($res)) {
308 die("Cannot execute $query: " . $res->getMessage());
311 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
323 static function isExtensionSafe($ext) {
324 static $extensions = NULL;
326 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
328 //make extensions to lowercase
329 $extensions = array_change_key_case($extensions, CASE_LOWER
);
330 // allow html/htm extension ONLY if the user is admin
331 // and/or has access CiviMail
332 if (!(CRM_Core_Permission
::check('access CiviMail') ||
333 CRM_Core_Permission
::check('administer CiviCRM') ||
334 (CRM_Mailing_Info
::workflowEnabled() &&
335 CRM_Core_Permission
::check('create mailings')
338 unset($extensions['html']);
339 unset($extensions['htm']);
342 //support lower and uppercase file extensions
343 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
347 * Determine whether a given file is listed in the PHP include path
349 * @param string $name name of file
351 * @return boolean whether the file can be include()d or require()d
353 static function isIncludable($name) {
354 $x = @fopen
($name, 'r', TRUE);
365 * remove the 32 bit md5 we add to the fileName
366 * also remove the unknown tag if we added it
368 static function cleanFileName($name) {
369 // replace the last 33 character before the '.' with null
370 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
379 static function makeFileName($name) {
380 $uniqID = md5(uniqid(rand(), TRUE));
381 $info = pathinfo($name);
382 $basename = substr($info['basename'],
383 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
385 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
386 // munge extension so it cannot have an embbeded dot in it
387 // The maximum length of a filename for most filesystems is 255 chars.
388 // We'll truncate at 240 to give some room for the extension.
389 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
392 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
402 static function getFilesByExtension($path, $ext) {
403 $path = self
::addTrailingSlash($path);
405 if ($dh = opendir($path)) {
406 while (FALSE !== ($elem = readdir($dh))) {
407 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
408 $files[] .= $path . $elem;
417 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
419 * @param string $dir the directory to be secured
420 * @param bool $overwrite
422 static function restrictAccess($dir, $overwrite = FALSE) {
423 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
424 // of site, causing site to stop functioning.
425 // FIXME: we should do more checks here -
426 if (!empty($dir) && is_dir($dir)) {
427 $htaccess = <<<HTACCESS
434 $file = $dir . '.htaccess';
435 if ($overwrite ||
!file_exists($file)) {
436 if (file_put_contents($file, $htaccess) === FALSE) {
437 CRM_Core_Error
::movedSiteError($file);
444 * Restrict remote users from browsing the given directory.
448 static function restrictBrowsing($publicDir) {
449 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
454 $nobrowse = realpath($publicDir) . '/index.html';
455 if (!file_exists($nobrowse)) {
456 @file_put_contents
($nobrowse, '');
460 $dir = new RecursiveDirectoryIterator($publicDir);
461 foreach ($dir as $name => $object) {
462 if (is_dir($name) && $name != '..') {
463 $nobrowse = realpath($name) . '/index.html';
464 if (!file_exists($nobrowse)) {
465 @file_put_contents
($nobrowse, '');
472 * Create the base file path from which all our internal directories are
473 * offset. This is derived from the template compile directory set
475 static function baseFilePath($templateCompileDir = NULL) {
476 static $_path = NULL;
478 if ($templateCompileDir == NULL) {
479 $config = CRM_Core_Config
::singleton();
480 $templateCompileDir = $config->templateCompileDir
;
483 $path = dirname($templateCompileDir);
485 //this fix is to avoid creation of upload dirs inside templates_c directory
486 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
488 $cnt = count($checkPath) - 1;
489 if ($checkPath[$cnt] == 'templates_c') {
490 unset($checkPath[$cnt]);
491 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
494 $_path = CRM_Utils_File
::addTrailingSlash($path);
504 static function relativeDirectory($directory) {
505 // Do nothing on windows
506 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
510 // check if directory is relative, if so return immediately
511 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR
) {
515 // make everything relative from the baseFilePath
516 $basePath = self
::baseFilePath();
517 // check if basePath is a substr of $directory, if so
518 // return rest of string
519 if (substr($directory, 0, strlen($basePath)) == $basePath) {
520 return substr($directory, strlen($basePath));
523 // return the original value
532 static function absoluteDirectory($directory) {
533 // check if directory is already absolute, if so return immediately
534 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
535 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
539 // check if directory is already absolute, if so return immediately
540 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
544 // make everything absolute from the baseFilePath
545 $basePath = self
::baseFilePath();
547 return $basePath . $directory;
551 * Make a file path relative to some base dir
558 static function relativize($directory, $basePath) {
559 if (substr($directory, 0, strlen($basePath)) == $basePath) {
560 return substr($directory, strlen($basePath));
567 * Create a path to a temporary file which can endure for multiple requests
569 * TODO: Automatic file cleanup using, eg, TTL policy
571 * @param $prefix string
573 * @return string, path to an openable/writable file
576 static function tempnam($prefix = 'tmp-') {
577 //$config = CRM_Core_Config::singleton();
578 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
579 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
580 $fileName = tempnam(sys_get_temp_dir(), $prefix);
585 * Create a path to a temporary directory which can endure for multiple requests
587 * TODO: Automatic file cleanup using, eg, TTL policy
589 * @param $prefix string
591 * @return string, path to an openable/writable directory; ends with '/'
594 static function tempdir($prefix = 'tmp-') {
595 $fileName = self
::tempnam($prefix);
597 mkdir($fileName, 0700);
598 return $fileName . '/';
602 * Search directory tree for files which match a glob pattern.
604 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
606 * @param $dir string, base dir
607 * @param $pattern string, glob pattern, eg "*.txt"
608 * @return array(string)
610 static function findFiles($dir, $pattern) {
611 $todos = array($dir);
613 while (!empty($todos)) {
614 $subdir = array_shift($todos);
615 $matches = glob("$subdir/$pattern");
616 if (is_array($matches)) {
617 foreach ($matches as $match) {
618 if (!is_dir($match)) {
623 if ($dh = opendir($subdir)) {
624 while (FALSE !== ($entry = readdir($dh))) {
625 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
626 if ($entry{0} == '.') {
628 } elseif (is_dir($path)) {
639 * Determine if $child is a sub-directory of $parent
641 * @param string $parent
642 * @param string $child
643 * @param bool $checkRealPath
647 static function isChildPath($parent, $child, $checkRealPath = TRUE) {
648 if ($checkRealPath) {
649 $parent = realpath($parent);
650 $child = realpath($child);
652 $parentParts = explode('/', rtrim($parent, '/'));
653 $childParts = explode('/', rtrim($child, '/'));
654 while (($parentPart = array_shift($parentParts)) !== NULL) {
655 $childPart = array_shift($childParts);
656 if ($parentPart != $childPart) {
660 if (empty($childParts)) {
661 return FALSE; // same directory
668 * Move $fromDir to $toDir, replacing/deleting any
669 * pre-existing content.
671 * @param string $fromDir the directory which should be moved
672 * @param string $toDir the new location of the directory
673 * @param bool $verbose
675 * @return bool TRUE on success
677 static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
678 if (is_dir($toDir)) {
679 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
684 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
686 CRM_Utils_File
::copyDir($fromDir, $toDir);
687 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
688 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');