3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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-2015
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
48 * true if file is ascii
50 public static function isAscii($name) {
51 $fd = fopen($name, "r");
58 $line = fgets($fd, 8192);
59 if (!CRM_Utils_String
::isAscii($line)) {
70 * Given a file name, determine if the file contents make it an html file
76 * true if file is html
78 public static function isHtml($name) {
79 $fd = fopen($name, "r");
86 while (!feof($fd) & $lineCount <= 5) {
88 $line = fgets($fd, 8192);
89 if (!CRM_Utils_String
::isHtml($line)) {
100 * Create a directory given a path name, creates parent directories
103 * @param string $path
106 * Should we abort or just return an invalid code.
110 public static function createDir($path, $abort = TRUE) {
111 if (is_dir($path) ||
empty($path)) {
115 CRM_Utils_File
::createDir(dirname($path), $abort);
116 if (@mkdir
($path, 0777) == FALSE) {
118 $docLink = CRM_Utils_System
::docURL2('Moving an Existing Installation to a New Server or Location', NULL, NULL, NULL, NULL, "wiki");
119 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>";
121 CRM_Utils_System
::civiExit();
131 * Delete a directory given a path name, delete children directories
132 * and files if needed
134 * @param string $target
137 * @param bool $verbose
142 public static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
143 static $exceptions = array('.', '..');
144 if ($target == '' ||
$target == '/') {
145 throw new Exception("Overly broad deletion");
148 if ($dh = @opendir
($target)) {
149 while (FALSE !== ($sibling = readdir($dh))) {
150 if (!in_array($sibling, $exceptions)) {
151 $object = $target . DIRECTORY_SEPARATOR
. $sibling;
153 if (is_dir($object)) {
154 CRM_Utils_File
::cleanDir($object, $rmdir, $verbose);
156 elseif (is_file($object)) {
157 if (!unlink($object)) {
158 CRM_Core_Session
::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
166 if (rmdir($target)) {
168 CRM_Core_Session
::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
173 CRM_Core_Session
::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
180 * Concatenate several files.
182 * @param array $files
183 * List of file names.
184 * @param string $delim
185 * An optional delimiter to put between files.
188 public static function concat($files, $delim = '') {
191 foreach ($files as $file) {
195 $buf .= file_get_contents($file);
202 * @param string $source
203 * @param string $destination
205 public static function copyDir($source, $destination) {
206 if ($dh = opendir($source)) {
207 @mkdir
($destination);
208 while (FALSE !== ($file = readdir($dh))) {
209 if (($file != '.') && ($file != '..')) {
210 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
211 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
214 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
223 * Given a file name, recode it (in place!) to UTF-8
225 * @param string $name
229 * whether the file was recoded properly
231 public static function toUtf8($name) {
232 static $config = NULL;
233 static $legacyEncoding = NULL;
234 if ($config == NULL) {
235 $config = CRM_Core_Config
::singleton();
236 $legacyEncoding = $config->legacyEncoding
;
239 if (!function_exists('iconv')) {
245 $contents = file_get_contents($name);
246 if ($contents === FALSE) {
250 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
251 if ($contents === FALSE) {
255 $file = fopen($name, 'w');
256 if ($file === FALSE) {
260 $written = fwrite($file, $contents);
261 $closed = fclose($file);
262 if ($written === FALSE or !$closed) {
270 * Appends a slash to the end of a string if it doesn't already end with one
272 * @param string $path
273 * @param string $slash
277 public static function addTrailingSlash($path, $slash = NULL) {
279 // FIXME: Defaulting to backslash on windows systems can produce unexpected results, esp for URL strings which should always use forward-slashes.
280 // I think this fn should default to forward-slash instead.
281 $slash = DIRECTORY_SEPARATOR
;
283 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
291 * @param string $fileName
292 * @param null $prefix
293 * @param bool $isQueryString
294 * @param bool $dieOnErrors
296 public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
297 require_once 'DB.php';
299 $db = DB
::connect($dsn);
300 if (PEAR
::isError($db)) {
301 die("Cannot open $dsn: " . $db->getMessage());
303 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
304 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
307 if (!$isQueryString) {
308 $string = $prefix . file_get_contents($fileName);
311 // use filename as query string
312 $string = $prefix . $fileName;
315 //get rid of comments starting with # and --
317 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
318 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
320 $queries = preg_split('/;\s*$/m', $string);
321 foreach ($queries as $query) {
322 $query = trim($query);
323 if (!empty($query)) {
324 CRM_Core_Error
::debug_query($query);
325 $res = &$db->query($query);
326 if (PEAR
::isError($res)) {
328 die("Cannot execute $query: " . $res->getMessage());
331 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
343 public static function isExtensionSafe($ext) {
344 static $extensions = NULL;
346 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
348 //make extensions to lowercase
349 $extensions = array_change_key_case($extensions, CASE_LOWER
);
350 // allow html/htm extension ONLY if the user is admin
351 // and/or has access CiviMail
352 if (!(CRM_Core_Permission
::check('access CiviMail') ||
353 CRM_Core_Permission
::check('administer CiviCRM') ||
354 (CRM_Mailing_Info
::workflowEnabled() &&
355 CRM_Core_Permission
::check('create mailings')
359 unset($extensions['html']);
360 unset($extensions['htm']);
363 //support lower and uppercase file extensions
364 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
368 * Determine whether a given file is listed in the PHP include path.
370 * @param string $name
374 * whether the file can be include()d or require()d
376 public static function isIncludable($name) {
377 $x = @fopen
($name, 'r', TRUE);
388 * Remove the 32 bit md5 we add to the fileName
389 * also remove the unknown tag if we added it
391 public static function cleanFileName($name) {
392 // replace the last 33 character before the '.' with null
393 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
398 * @param string $name
402 public static function makeFileName($name) {
403 $uniqID = md5(uniqid(rand(), TRUE));
404 $info = pathinfo($name);
405 $basename = substr($info['basename'],
406 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
408 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
409 // munge extension so it cannot have an embbeded dot in it
410 // The maximum length of a filename for most filesystems is 255 chars.
411 // We'll truncate at 240 to give some room for the extension.
412 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
415 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
425 public static function getFilesByExtension($path, $ext) {
426 $path = self
::addTrailingSlash($path);
428 if ($dh = opendir($path)) {
429 while (FALSE !== ($elem = readdir($dh))) {
430 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
431 $files[] .= $path . $elem;
440 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
443 * The directory to be secured.
444 * @param bool $overwrite
446 public static function restrictAccess($dir, $overwrite = FALSE) {
447 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
448 // of site, causing site to stop functioning.
449 // FIXME: we should do more checks here -
450 if (!empty($dir) && is_dir($dir)) {
451 $htaccess = <<<HTACCESS
458 $file = $dir . '.htaccess';
459 if ($overwrite ||
!file_exists($file)) {
460 if (file_put_contents($file, $htaccess) === FALSE) {
461 CRM_Core_Error
::movedSiteError($file);
468 * Restrict remote users from browsing the given directory.
472 public static function restrictBrowsing($publicDir) {
473 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
478 $nobrowse = realpath($publicDir) . '/index.html';
479 if (!file_exists($nobrowse)) {
480 @file_put_contents
($nobrowse, '');
484 $dir = new RecursiveDirectoryIterator($publicDir);
485 foreach ($dir as $name => $object) {
486 if (is_dir($name) && $name != '..') {
487 $nobrowse = realpath($name) . '/index.html';
488 if (!file_exists($nobrowse)) {
489 @file_put_contents
($nobrowse, '');
496 * Create the base file path from which all our internal directories are
497 * offset. This is derived from the template compile directory set
499 public static function baseFilePath() {
500 static $_path = NULL;
502 // Note: Don't rely on $config; that creates a dependency loop.
503 if (!defined('CIVICRM_TEMPLATE_COMPILEDIR')) {
504 throw new RuntimeException("Undefined constant: CIVICRM_TEMPLATE_COMPILEDIR");
506 $templateCompileDir = CIVICRM_TEMPLATE_COMPILEDIR
;
508 $path = dirname($templateCompileDir);
510 //this fix is to avoid creation of upload dirs inside templates_c directory
511 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
513 $cnt = count($checkPath) - 1;
514 if ($checkPath[$cnt] == 'templates_c') {
515 unset($checkPath[$cnt]);
516 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
519 $_path = CRM_Utils_File
::addTrailingSlash($path);
525 * Determine if a path is absolute.
528 * TRUE if absolute. FALSE if relative.
530 public static function isAbsolute($path) {
531 if (substr($path, 0, 1) === DIRECTORY_SEPARATOR
) {
534 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
535 if (preg_match('!^[a-zA-Z]:[/\\\\]!', $path)) {
547 public static function relativeDirectory($directory) {
548 // Do nothing on windows
549 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
553 // check if directory is relative, if so return immediately
554 if (!self
::isAbsolute($directory)) {
558 // make everything relative from the baseFilePath
559 $basePath = self
::baseFilePath();
560 // check if basePath is a substr of $directory, if so
561 // return rest of string
562 if (substr($directory, 0, strlen($basePath)) == $basePath) {
563 return substr($directory, strlen($basePath));
566 // return the original value
572 * @param string|NULL $basePath
573 * The base path when evaluating relative paths. Should include trailing slash.
577 public static function absoluteDirectory($directory, $basePath = NULL) {
578 // check if directory is already absolute, if so return immediately
579 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
580 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
584 // check if directory is already absolute, if so return immediately
585 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
589 // make everything absolute from the baseFilePath
590 $basePath = ($basePath === NULL) ? self
::baseFilePath() : $basePath;
592 return $basePath . $directory;
596 * Make a file path relative to some base dir.
603 public static function relativize($directory, $basePath) {
604 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
605 $directory = strtr($directory, '\\', '/');
606 $basePath = strtr($basePath, '\\', '/');
608 if (substr($directory, 0, strlen($basePath)) == $basePath) {
609 return substr($directory, strlen($basePath));
617 * Create a path to a temporary file which can endure for multiple requests.
619 * TODO: Automatic file cleanup using, eg, TTL policy
621 * @param string $prefix
623 * @return string, path to an openable/writable file
626 public static function tempnam($prefix = 'tmp-') {
627 //$config = CRM_Core_Config::singleton();
628 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
629 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
630 $fileName = tempnam(sys_get_temp_dir(), $prefix);
635 * Create a path to a temporary directory which can endure for multiple requests.
637 * TODO: Automatic file cleanup using, eg, TTL policy
639 * @param string $prefix
641 * @return string, path to an openable/writable directory; ends with '/'
644 public static function tempdir($prefix = 'tmp-') {
645 $fileName = self
::tempnam($prefix);
647 mkdir($fileName, 0700);
648 return $fileName . '/';
652 * Search directory tree for files which match a glob pattern.
654 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
658 * @param string $pattern
659 * glob pattern, eg "*.txt".
660 * @param bool $relative
661 * TRUE if paths should be made relative to $dir
662 * @return array(string)
664 public static function findFiles($dir, $pattern, $relative = FALSE) {
665 $dir = rtrim($dir, '/');
666 $todos = array($dir);
668 while (!empty($todos)) {
669 $subdir = array_shift($todos);
670 $matches = glob("$subdir/$pattern");
671 if (is_array($matches)) {
672 foreach ($matches as $match) {
673 if (!is_dir($match)) {
674 $result[] = $relative ? CRM_Utils_File
::relativize($match, "$dir/") : $match;
678 if ($dh = opendir($subdir)) {
679 while (FALSE !== ($entry = readdir($dh))) {
680 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
681 if ($entry{0} == '.') {
684 elseif (is_dir($path)) {
695 * Determine if $child is a sub-directory of $parent
697 * @param string $parent
698 * @param string $child
699 * @param bool $checkRealPath
703 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
704 if ($checkRealPath) {
705 $parent = realpath($parent);
706 $child = realpath($child);
708 $parentParts = explode('/', rtrim($parent, '/'));
709 $childParts = explode('/', rtrim($child, '/'));
710 while (($parentPart = array_shift($parentParts)) !== NULL) {
711 $childPart = array_shift($childParts);
712 if ($parentPart != $childPart) {
716 if (empty($childParts)) {
717 return FALSE; // same directory
725 * Move $fromDir to $toDir, replacing/deleting any
726 * pre-existing content.
728 * @param string $fromDir
729 * The directory which should be moved.
730 * @param string $toDir
731 * The new location of the directory.
732 * @param bool $verbose
737 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
738 if (is_dir($toDir)) {
739 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
744 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
746 CRM_Utils_File
::copyDir($fromDir, $toDir);
747 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
748 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');