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');
182 * @param $destination
184 static function copyDir($source, $destination) {
185 $dir = opendir($source);
186 @mkdir
($destination);
187 while (FALSE !== ($file = readdir($dir))) {
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);
201 * Given a file name, recode it (in place!) to UTF-8
203 * @param string $name name of file
205 * @return boolean whether the file was recoded properly
208 static function toUtf8($name) {
209 static $config = NULL;
210 static $legacyEncoding = NULL;
211 if ($config == NULL) {
212 $config = CRM_Core_Config
::singleton();
213 $legacyEncoding = $config->legacyEncoding
;
216 if (!function_exists('iconv')) {
222 $contents = file_get_contents($name);
223 if ($contents === FALSE) {
227 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
228 if ($contents === FALSE) {
232 $file = fopen($name, 'w');
233 if ($file === FALSE) {
237 $written = fwrite($file, $contents);
238 $closed = fclose($file);
239 if ($written === FALSE or !$closed) {
247 * Appends trailing slashed to paths
250 * @param null $separator
256 static function addTrailingSlash($name, $separator = NULL) {
258 $separator = DIRECTORY_SEPARATOR
;
261 if (substr($name, -1, 1) != $separator) {
270 * @param null $prefix
271 * @param bool $isQueryString
272 * @param bool $dieOnErrors
274 static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
275 require_once 'DB.php';
277 $db = DB
::connect($dsn);
278 if (PEAR
::isError($db)) {
279 die("Cannot open $dsn: " . $db->getMessage());
281 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
282 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
285 if (!$isQueryString) {
286 $string = $prefix . file_get_contents($fileName);
289 // use filename as query string
290 $string = $prefix . $fileName;
293 //get rid of comments starting with # and --
295 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
296 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
298 $queries = preg_split('/;\s*$/m', $string);
299 foreach ($queries as $query) {
300 $query = trim($query);
301 if (!empty($query)) {
302 CRM_Core_Error
::debug_query($query);
303 $res = &$db->query($query);
304 if (PEAR
::isError($res)) {
306 die("Cannot execute $query: " . $res->getMessage());
309 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
321 static function isExtensionSafe($ext) {
322 static $extensions = NULL;
324 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
326 //make extensions to lowercase
327 $extensions = array_change_key_case($extensions, CASE_LOWER
);
328 // allow html/htm extension ONLY if the user is admin
329 // and/or has access CiviMail
330 if (!(CRM_Core_Permission
::check('access CiviMail') ||
331 CRM_Core_Permission
::check('administer CiviCRM') ||
332 (CRM_Mailing_Info
::workflowEnabled() &&
333 CRM_Core_Permission
::check('create mailings')
336 unset($extensions['html']);
337 unset($extensions['htm']);
340 //support lower and uppercase file extensions
341 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
345 * Determine whether a given file is listed in the PHP include path
347 * @param string $name name of file
349 * @return boolean whether the file can be include()d or require()d
351 static function isIncludable($name) {
352 $x = @fopen
($name, 'r', TRUE);
363 * remove the 32 bit md5 we add to the fileName
364 * also remove the unknown tag if we added it
366 static function cleanFileName($name) {
367 // replace the last 33 character before the '.' with null
368 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
377 static function makeFileName($name) {
378 $uniqID = md5(uniqid(rand(), TRUE));
379 $info = pathinfo($name);
380 $basename = substr($info['basename'],
381 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
383 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
384 // munge extension so it cannot have an embbeded dot in it
385 // The maximum length of a filename for most filesystems is 255 chars.
386 // We'll truncate at 240 to give some room for the extension.
387 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
390 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
400 static function getFilesByExtension($path, $ext) {
401 $path = self
::addTrailingSlash($path);
402 $dh = opendir($path);
404 while (FALSE !== ($elem = readdir($dh))) {
405 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
406 $files[] .= $path . $elem;
414 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
416 * @param string $dir the directory to be secured
417 * @param bool $overwrite
419 static function restrictAccess($dir, $overwrite = FALSE) {
420 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
421 // of site, causing site to stop functioning.
422 // FIXME: we should do more checks here -
423 if (!empty($dir) && is_dir($dir)) {
424 $htaccess = <<<HTACCESS
431 $file = $dir . '.htaccess';
432 if ($overwrite ||
!file_exists($file)) {
433 if (file_put_contents($file, $htaccess) === FALSE) {
434 CRM_Core_Error
::movedSiteError($file);
441 * Restrict remote users from browsing the given directory.
445 static function restrictBrowsing($publicDir) {
446 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
451 $nobrowse = realpath($publicDir) . '/index.html';
452 if (!file_exists($nobrowse)) {
453 @file_put_contents
($nobrowse, '');
457 $dir = new RecursiveDirectoryIterator($publicDir);
458 foreach ($dir as $name => $object) {
459 if (is_dir($name) && $name != '..') {
460 $nobrowse = realpath($name) . '/index.html';
461 if (!file_exists($nobrowse)) {
462 @file_put_contents
($nobrowse, '');
469 * Create the base file path from which all our internal directories are
470 * offset. This is derived from the template compile directory set
472 static function baseFilePath($templateCompileDir = NULL) {
473 static $_path = NULL;
475 if ($templateCompileDir == NULL) {
476 $config = CRM_Core_Config
::singleton();
477 $templateCompileDir = $config->templateCompileDir
;
480 $path = dirname($templateCompileDir);
482 //this fix is to avoid creation of upload dirs inside templates_c directory
483 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
485 $cnt = count($checkPath) - 1;
486 if ($checkPath[$cnt] == 'templates_c') {
487 unset($checkPath[$cnt]);
488 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
491 $_path = CRM_Utils_File
::addTrailingSlash($path);
501 static function relativeDirectory($directory) {
502 // Do nothing on windows
503 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
507 // check if directory is relative, if so return immediately
508 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR
) {
512 // make everything relative from the baseFilePath
513 $basePath = self
::baseFilePath();
514 // check if basePath is a substr of $directory, if so
515 // return rest of string
516 if (substr($directory, 0, strlen($basePath)) == $basePath) {
517 return substr($directory, strlen($basePath));
520 // return the original value
529 static function absoluteDirectory($directory) {
530 // check if directory is already absolute, if so return immediately
531 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
532 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
536 // check if directory is already absolute, if so return immediately
537 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
541 // make everything absolute from the baseFilePath
542 $basePath = self
::baseFilePath();
544 return $basePath . $directory;
548 * Make a file path relative to some base dir
555 static function relativize($directory, $basePath) {
556 if (substr($directory, 0, strlen($basePath)) == $basePath) {
557 return substr($directory, strlen($basePath));
564 * Create a path to a temporary file which can endure for multiple requests
566 * TODO: Automatic file cleanup using, eg, TTL policy
568 * @param $prefix string
570 * @return string, path to an openable/writable file
573 static function tempnam($prefix = 'tmp-') {
574 //$config = CRM_Core_Config::singleton();
575 //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
576 //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
577 $fileName = tempnam(sys_get_temp_dir(), $prefix);
582 * Create a path to a temporary directory which can endure for multiple requests
584 * TODO: Automatic file cleanup using, eg, TTL policy
586 * @param $prefix string
588 * @return string, path to an openable/writable directory; ends with '/'
591 static function tempdir($prefix = 'tmp-') {
592 $fileName = self
::tempnam($prefix);
594 mkdir($fileName, 0700);
595 return $fileName . '/';
599 * Search directory tree for files which match a glob pattern.
601 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
603 * @param $dir string, base dir
604 * @param $pattern string, glob pattern, eg "*.txt"
605 * @return array(string)
607 static function findFiles($dir, $pattern) {
608 $todos = array($dir);
610 while (!empty($todos)) {
611 $subdir = array_shift($todos);
612 $matches = glob("$subdir/$pattern");
613 if (is_array($matches)) {
614 foreach ($matches as $match) {
615 if (!is_dir($match)) {
620 $dh = opendir($subdir);
622 while (FALSE !== ($entry = readdir($dh))) {
623 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
624 if ($entry{0} == '.') {
626 } elseif (is_dir($path)) {
637 * Determine if $child is a sub-directory of $parent
639 * @param string $parent
640 * @param string $child
641 * @param bool $checkRealPath
645 static function isChildPath($parent, $child, $checkRealPath = TRUE) {
646 if ($checkRealPath) {
647 $parent = realpath($parent);
648 $child = realpath($child);
650 $parentParts = explode('/', rtrim($parent, '/'));
651 $childParts = explode('/', rtrim($child, '/'));
652 while (($parentPart = array_shift($parentParts)) !== NULL) {
653 $childPart = array_shift($childParts);
654 if ($parentPart != $childPart) {
658 if (empty($childParts)) {
659 return FALSE; // same directory
666 * Move $fromDir to $toDir, replacing/deleting any
667 * pre-existing content.
669 * @param string $fromDir the directory which should be moved
670 * @param string $toDir the new location of the directory
671 * @param bool $verbose
673 * @return bool TRUE on success
675 static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
676 if (is_dir($toDir)) {
677 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
682 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
684 CRM_Utils_File
::copyDir($fromDir, $toDir);
685 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
686 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');
693 * Create a static file (e.g. css or js) in the dynamic resource directory
694 * Note: if the file already exists it will be overwritten
695 * @param string $fileName
696 * @param string $contents
698 static function addDynamicResource($fileName, $contents) {
699 // First ensure the directory exists
700 $path = self
::dynamicResourcePath();
701 if (!is_dir($path)) {
702 self
::createDir($path);
703 self
::restrictBrowsing($path);
705 file_put_contents("$path/$fileName", $contents);
709 * Get the path of a dynamic resource file
710 * With no fileName supplied, returns the path of the directory
711 * @param string $fileName
714 static function dynamicResourcePath($fileName = NULL) {
715 $config = CRM_Core_Config
::singleton();
716 // FIXME: Use self::baseFilePath once url issue has been resolved
717 // Windows PHP accepts any mix of "/" or "\"; simpler if we only deal with one of those
718 $imageUploadDir = str_replace('\\', '/', $config->imageUploadDir
);
719 $path = self
::addTrailingSlash(str_replace('/persist/contribute', '', $imageUploadDir)) . 'dynamic';
720 if ($fileName !== NULL) {
721 $path .= "/$fileName";
727 * Get the URL of a dynamic resource file
728 * @param string $fileName
731 static function dynamicResourceUrl($fileName) {
732 $config = CRM_Core_Config
::singleton();
733 // FIXME: Need a better way of getting the url of the baseFilePath
734 return self
::addTrailingSlash(str_replace('/persist/contribute', '', $config->imageUploadURL
), '/') . 'dynamic/' . $fileName;
738 * Delete all files from the dynamic resource directory
740 static function flushDynamicResources() {
741 $files = glob(self
::dynamicResourcePath('*'));
742 foreach ($files ?
$files : array() as $file) {
743 if (is_file($file)) {