3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
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
48 public static function isAscii($name) {
49 $fd = fopen($name, "r");
56 $line = fgets($fd, 8192);
57 if (!CRM_Utils_String
::isAscii($line)) {
68 * Given a file name, determine if the file contents make it an html file
70 * @param string $name name of file
72 * @return boolean true if file is html
74 public static function isHtml($name) {
75 $fd = fopen($name, "r");
82 while (!feof($fd) & $lineCount <= 5) {
84 $line = fgets($fd, 8192);
85 if (!CRM_Utils_String
::isHtml($line)) {
96 * Create a directory given a path name, creates parent directories
99 * @param string $path the path name
100 * @param boolean $abort should we abort or just return an invalid code
105 public static function createDir($path, $abort = TRUE) {
106 if (is_dir($path) ||
empty($path)) {
110 CRM_Utils_File
::createDir(dirname($path), $abort);
111 if (@mkdir
($path, 0777) == FALSE) {
113 $docLink = CRM_Utils_System
::docURL2('Moving an Existing Installation to a New Server or Location', NULL, NULL, NULL, NULL, "wiki");
114 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>";
116 CRM_Utils_System
::civiExit();
126 * Delete a directory given a path name, delete children directories
127 * and files if needed
129 * @param string $target the path name
131 * @param bool $verbose
137 public static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
138 static $exceptions = array('.', '..');
139 if ($target == '' ||
$target == '/') {
140 throw new Exception("Overly broad deletion");
143 if ($sourcedir = opendir($target)) {
144 while (FALSE !== ($sibling = readdir($sourcedir))) {
145 if (!in_array($sibling, $exceptions)) {
146 $object = $target . DIRECTORY_SEPARATOR
. $sibling;
148 if (is_dir($object)) {
149 CRM_Utils_File
::cleanDir($object, $rmdir, $verbose);
151 elseif (is_file($object)) {
152 if (!unlink($object)) {
153 CRM_Core_Session
::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
158 closedir($sourcedir);
161 if (rmdir($target)) {
163 CRM_Core_Session
::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
168 CRM_Core_Session
::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
176 * @param $destination
178 static function copyDir($source, $destination) {
179 if ($dir = opendir($source)) {
180 @mkdir
($destination);
181 while (FALSE !== ($file = readdir($dir))) {
182 if (($file != '.') && ($file != '..')) {
183 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
184 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
187 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
196 * Given a file name, recode it (in place!) to UTF-8
198 * @param string $name name of file
200 * @return boolean whether the file was recoded properly
202 public static function toUtf8($name) {
203 static $config = NULL;
204 static $legacyEncoding = NULL;
205 if ($config == NULL) {
206 $config = CRM_Core_Config
::singleton();
207 $legacyEncoding = $config->legacyEncoding
;
210 if (!function_exists('iconv')) {
216 $contents = file_get_contents($name);
217 if ($contents === FALSE) {
221 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
222 if ($contents === FALSE) {
226 $file = fopen($name, 'w');
227 if ($file === FALSE) {
231 $written = fwrite($file, $contents);
232 $closed = fclose($file);
233 if ($written === FALSE or !$closed) {
241 * Appends a slash to the end of a string if it doesn't already end with one
243 * @param string $path
244 * @param string $slash
249 public static function addTrailingSlash($path, $slash = NULL) {
251 // FIXME: Defaulting to backslash on windows systems can produce unexpected results, esp for URL strings which should always use forward-slashes.
252 // I think this fn should default to forward-slash instead.
253 $slash = DIRECTORY_SEPARATOR
;
255 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
263 * @param string $fileName
264 * @param null $prefix
265 * @param bool $isQueryString
266 * @param bool $dieOnErrors
268 public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
269 require_once 'DB.php';
271 $db = DB
::connect($dsn);
272 if (PEAR
::isError($db)) {
273 die("Cannot open $dsn: " . $db->getMessage());
275 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
276 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
279 if (!$isQueryString) {
280 $string = $prefix . file_get_contents($fileName);
283 // use filename as query string
284 $string = $prefix . $fileName;
287 //get rid of comments starting with # and --
289 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
290 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
292 $queries = preg_split('/;\s*$/m', $string);
293 foreach ($queries as $query) {
294 $query = trim($query);
295 if (!empty($query)) {
296 CRM_Core_Error
::debug_query($query);
297 $res = &$db->query($query);
298 if (PEAR
::isError($res)) {
300 die("Cannot execute $query: " . $res->getMessage());
303 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
315 public static function isExtensionSafe($ext) {
316 static $extensions = NULL;
318 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
320 //make extensions to lowercase
321 $extensions = array_change_key_case($extensions, CASE_LOWER
);
322 // allow html/htm extension ONLY if the user is admin
323 // and/or has access CiviMail
324 if (!(CRM_Core_Permission
::check('access CiviMail') ||
325 CRM_Core_Permission
::check('administer CiviCRM') ||
326 (CRM_Mailing_Info
::workflowEnabled() &&
327 CRM_Core_Permission
::check('create mailings')
330 unset($extensions['html']);
331 unset($extensions['htm']);
334 //support lower and uppercase file extensions
335 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
339 * Determine whether a given file is listed in the PHP include path
341 * @param string $name name of file
343 * @return boolean whether the file can be include()d or require()d
345 public static function isIncludable($name) {
346 $x = @fopen
($name, 'r', TRUE);
357 * Remove the 32 bit md5 we add to the fileName
358 * also remove the unknown tag if we added it
360 public static function cleanFileName($name) {
361 // replace the last 33 character before the '.' with null
362 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
367 * @param string $name
371 public static function makeFileName($name) {
372 $uniqID = md5(uniqid(rand(), TRUE));
373 $info = pathinfo($name);
374 $basename = substr($info['basename'],
375 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
377 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
378 // munge extension so it cannot have an embbeded dot in it
379 // The maximum length of a filename for most filesystems is 255 chars.
380 // We'll truncate at 240 to give some room for the extension.
381 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
384 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
394 public static function getFilesByExtension($path, $ext) {
395 $path = self
::addTrailingSlash($path);
396 if ($dh = opendir($path)) {
398 while (FALSE !== ($elem = readdir($dh))) {
399 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
400 $files[] .= $path . $elem;
409 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
411 * @param string $dir the directory to be secured
412 * @param bool $overwrite
414 public static function restrictAccess($dir, $overwrite = FALSE) {
415 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
416 // of site, causing site to stop functioning.
417 // FIXME: we should do more checks here -
418 if (!empty($dir) && is_dir($dir)) {
419 $htaccess = <<<HTACCESS
426 $file = $dir . '.htaccess';
427 if ($overwrite ||
!file_exists($file)) {
428 if (file_put_contents($file, $htaccess) === FALSE) {
429 CRM_Core_Error
::movedSiteError($file);
436 * Restrict remote users from browsing the given directory.
440 public static function restrictBrowsing($publicDir) {
441 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
446 $nobrowse = realpath($publicDir) . '/index.html';
447 if (!file_exists($nobrowse)) {
448 @file_put_contents
($nobrowse, '');
452 $dir = new RecursiveDirectoryIterator($publicDir);
453 foreach ($dir as $name => $object) {
454 if (is_dir($name) && $name != '..') {
455 $nobrowse = realpath($name) . '/index.html';
456 if (!file_exists($nobrowse)) {
457 @file_put_contents
($nobrowse, '');
464 * Create the base file path from which all our internal directories are
465 * offset. This is derived from the template compile directory set
467 public static function baseFilePath($templateCompileDir = NULL) {
468 static $_path = NULL;
470 if ($templateCompileDir == NULL) {
471 $config = CRM_Core_Config
::singleton();
472 $templateCompileDir = $config->templateCompileDir
;
475 $path = dirname($templateCompileDir);
477 //this fix is to avoid creation of upload dirs inside templates_c directory
478 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
480 $cnt = count($checkPath) - 1;
481 if ($checkPath[$cnt] == 'templates_c') {
482 unset($checkPath[$cnt]);
483 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
486 $_path = CRM_Utils_File
::addTrailingSlash($path);
496 public static function relativeDirectory($directory) {
497 // Do nothing on windows
498 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
502 // check if directory is relative, if so return immediately
503 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR
) {
507 // make everything relative from the baseFilePath
508 $basePath = self
::baseFilePath();
509 // check if basePath is a substr of $directory, if so
510 // return rest of string
511 if (substr($directory, 0, strlen($basePath)) == $basePath) {
512 return substr($directory, strlen($basePath));
515 // return the original value
524 public static function absoluteDirectory($directory) {
525 // check if directory is already absolute, if so return immediately
526 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
527 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
531 // check if directory is already absolute, if so return immediately
532 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
536 // make everything absolute from the baseFilePath
537 $basePath = self
::baseFilePath();
539 return $basePath . $directory;
543 * Make a file path relative to some base dir
550 public static function relativize($directory, $basePath) {
551 if (substr($directory, 0, strlen($basePath)) == $basePath) {
552 return substr($directory, strlen($basePath));
559 * Create a path to a temporary file which can endure for multiple requests
561 * TODO: Automatic file cleanup using, eg, TTL policy
563 * @param $prefix string
565 * @return string, path to an openable/writable file
568 public static function tempnam($prefix = 'tmp-') {
569 //$config = CRM_Core_Config::singleton();
570 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
571 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
572 $fileName = tempnam(sys_get_temp_dir(), $prefix);
577 * Create a path to a temporary directory which can endure for multiple requests
579 * TODO: Automatic file cleanup using, eg, TTL policy
581 * @param $prefix string
583 * @return string, path to an openable/writable directory; ends with '/'
586 public static function tempdir($prefix = 'tmp-') {
587 $fileName = self
::tempnam($prefix);
589 mkdir($fileName, 0700);
590 return $fileName . '/';
594 * Search directory tree for files which match a glob pattern.
596 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
598 * @param $dir string, base dir
599 * @param $pattern string, glob pattern, eg "*.txt"
600 * @return array(string)
602 public static function findFiles($dir, $pattern) {
603 $todos = array($dir);
605 while (!empty($todos)) {
606 $subdir = array_shift($todos);
607 $matches = glob("$subdir/$pattern");
608 if (is_array($matches)) {
609 foreach ($matches as $match) {
610 if (!is_dir($match)) {
615 if ($dh = opendir($subdir)) {
616 while (FALSE !== ($entry = readdir($dh))) {
617 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
618 if ($entry{0} == '.') {
620 } elseif (is_dir($path)) {
631 * Determine if $child is a sub-directory of $parent
633 * @param string $parent
634 * @param string $child
635 * @param bool $checkRealPath
639 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
640 if ($checkRealPath) {
641 $parent = realpath($parent);
642 $child = realpath($child);
644 $parentParts = explode('/', rtrim($parent, '/'));
645 $childParts = explode('/', rtrim($child, '/'));
646 while (($parentPart = array_shift($parentParts)) !== NULL) {
647 $childPart = array_shift($childParts);
648 if ($parentPart != $childPart) {
652 if (empty($childParts)) {
653 return FALSE; // same directory
660 * Move $fromDir to $toDir, replacing/deleting any
661 * pre-existing content.
663 * @param string $fromDir the directory which should be moved
664 * @param string $toDir the new location of the directory
665 * @param bool $verbose
667 * @return bool TRUE on success
669 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
670 if (is_dir($toDir)) {
671 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
676 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
678 CRM_Utils_File
::copyDir($fromDir, $toDir);
679 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
680 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');