3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
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-2017
35 * class to provide simple static functions for file objects
37 class CRM_Utils_File
{
40 * Given a file name, determine if the file contents make it an ascii file
46 * 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
74 * true if file is html
76 public 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
104 * Should we abort or just return an invalid code.
106 * NULL: Folder already exists or was not specified.
107 * TRUE: Creation succeeded.
108 * FALSE: Creation failed.
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
141 public static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
142 static $exceptions = array('.', '..');
143 if ($target == '' ||
$target == '/' ||
!$target) {
144 throw new Exception("Overly broad deletion");
147 if ($dh = @opendir
($target)) {
148 while (FALSE !== ($sibling = readdir($dh))) {
149 if (!in_array($sibling, $exceptions)) {
150 $object = $target . DIRECTORY_SEPARATOR
. $sibling;
152 if (is_dir($object)) {
153 CRM_Utils_File
::cleanDir($object, $rmdir, $verbose);
155 elseif (is_file($object)) {
156 if (!unlink($object)) {
157 CRM_Core_Session
::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
165 if (rmdir($target)) {
167 CRM_Core_Session
::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
172 CRM_Core_Session
::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
179 * Concatenate several files.
181 * @param array $files
182 * List of file names.
183 * @param string $delim
184 * An optional delimiter to put between files.
187 public static function concat($files, $delim = '') {
190 foreach ($files as $file) {
194 $buf .= file_get_contents($file);
201 * @param string $source
202 * @param string $destination
204 public static function copyDir($source, $destination) {
205 if ($dh = opendir($source)) {
206 @mkdir
($destination);
207 while (FALSE !== ($file = readdir($dh))) {
208 if (($file != '.') && ($file != '..')) {
209 if (is_dir($source . DIRECTORY_SEPARATOR
. $file)) {
210 CRM_Utils_File
::copyDir($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
213 copy($source . DIRECTORY_SEPARATOR
. $file, $destination . DIRECTORY_SEPARATOR
. $file);
222 * Given a file name, recode it (in place!) to UTF-8
224 * @param string $name
228 * whether the file was recoded properly
230 public static function toUtf8($name) {
231 static $config = NULL;
232 static $legacyEncoding = NULL;
233 if ($config == NULL) {
234 $config = CRM_Core_Config
::singleton();
235 $legacyEncoding = $config->legacyEncoding
;
238 if (!function_exists('iconv')) {
244 $contents = file_get_contents($name);
245 if ($contents === FALSE) {
249 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
250 if ($contents === FALSE) {
254 $file = fopen($name, 'w');
255 if ($file === FALSE) {
259 $written = fwrite($file, $contents);
260 $closed = fclose($file);
261 if ($written === FALSE or !$closed) {
269 * Appends a slash to the end of a string if it doesn't already end with one
271 * @param string $path
272 * @param string $slash
276 public static function addTrailingSlash($path, $slash = NULL) {
278 // FIXME: Defaulting to backslash on windows systems can produce
279 // 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('/', '\\'))) {
290 * Save a fake file somewhere
293 * The directory where the file should be saved.
294 * @param string $contents
295 * Optional: the contents of the file.
296 * @param string $fileName
299 * The filename saved, or FALSE on failure.
301 public static function createFakeFile($dir, $contents = 'delete me', $fileName = NULL) {
302 $dir = self
::addTrailingSlash($dir);
304 $fileName = 'delete-this-' . CRM_Utils_String
::createRandom(10, CRM_Utils_String
::ALPHANUMERIC
);
306 $success = file_put_contents($dir . $fileName, $contents);
308 return ($success === FALSE) ?
FALSE : $fileName;
312 * @param string|NULL $dsn
313 * Use NULL to load the default/active connection from CRM_Core_DAO.
314 * Otherwise, give a full DSN string.
315 * @param string $fileName
316 * @param null $prefix
317 * @param bool $isQueryString
318 * @param bool $dieOnErrors
320 public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
321 if (!$isQueryString) {
322 if (FALSE === file_get_contents($fileName)) {
323 // Our file cannot be found.
324 // Using 'die' here breaks this on extension upgrade.
325 throw new CRM_Exception('Could not find the SQL file.');
330 $db = CRM_Core_DAO
::getConnection();
333 require_once 'DB.php';
334 $db = DB
::connect($dsn);
337 if (PEAR
::isError($db)) {
338 die("Cannot open $dsn: " . $db->getMessage());
340 if (CRM_Utils_Constant
::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System
::isDevelopment())) {
341 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
343 $db->query('SET NAMES utf8');
344 $transactionId = CRM_Utils_Type
::escape(CRM_Utils_Request
::id(), 'String');
345 $db->query('SET @uniqueID = ' . "'$transactionId'");
347 if (!$isQueryString) {
348 $string = $prefix . file_get_contents($fileName);
351 // use filename as query string
352 $string = $prefix . $fileName;
355 // get rid of comments starting with # and --
357 $string = self
::stripComments($string);
359 $queries = preg_split('/;\s*$/m', $string);
360 foreach ($queries as $query) {
361 $query = trim($query);
362 if (!empty($query)) {
363 CRM_Core_Error
::debug_query($query);
364 $res = &$db->query($query);
365 if (PEAR
::isError($res)) {
367 die("Cannot execute $query: " . $res->getMessage());
370 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
378 * Strips comment from a possibly multiline SQL string
380 * @param string $string
385 public static function stripComments($string) {
386 return preg_replace("/^(#|--).*\R*/m", "", $string);
394 public static function isExtensionSafe($ext) {
395 static $extensions = NULL;
397 $extensions = CRM_Core_OptionGroup
::values('safe_file_extension', TRUE);
399 // make extensions to lowercase
400 $extensions = array_change_key_case($extensions, CASE_LOWER
);
401 // allow html/htm extension ONLY if the user is admin
402 // and/or has access CiviMail
403 if (!(CRM_Core_Permission
::check('access CiviMail') ||
404 CRM_Core_Permission
::check('administer CiviCRM') ||
405 (CRM_Mailing_Info
::workflowEnabled() &&
406 CRM_Core_Permission
::check('create mailings')
410 unset($extensions['html']);
411 unset($extensions['htm']);
414 // support lower and uppercase file extensions
415 return isset($extensions[strtolower($ext)]) ?
TRUE : FALSE;
419 * Determine whether a given file is listed in the PHP include path.
421 * @param string $name
425 * whether the file can be include()d or require()d
427 public static function isIncludable($name) {
428 $x = @fopen
($name, 'r', TRUE);
439 * Remove the 32 bit md5 we add to the fileName also remove the unknown tag if we added it.
445 public static function cleanFileName($name) {
446 // replace the last 33 character before the '.' with null
447 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
452 * Make a valid file name.
454 * @param string $name
458 public static function makeFileName($name) {
459 $uniqID = md5(uniqid(rand(), TRUE));
460 $info = pathinfo($name);
461 $basename = substr($info['basename'],
462 0, -(strlen(CRM_Utils_Array
::value('extension', $info)) +
(CRM_Utils_Array
::value('extension', $info) == '' ?
0 : 1))
464 if (!self
::isExtensionSafe(CRM_Utils_Array
::value('extension', $info))) {
465 // munge extension so it cannot have an embbeded dot in it
466 // The maximum length of a filename for most filesystems is 255 chars.
467 // We'll truncate at 240 to give some room for the extension.
468 return CRM_Utils_String
::munge("{$basename}_" . CRM_Utils_Array
::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown";
471 return CRM_Utils_String
::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array
::value('extension', $info);
481 public static function duplicate($filePath) {
482 $oldName = pathinfo($filePath, PATHINFO_FILENAME
);
483 $uniqID = md5(uniqid(rand(), TRUE));
484 $newName = preg_replace('/(_[\w]{32})$/', '', $oldName) . '_' . $uniqID;
485 $newPath = str_replace($oldName, $newName, $filePath);
486 copy($filePath, $newPath);
491 * Get files for the extension.
493 * @param string $path
498 public static function getFilesByExtension($path, $ext) {
499 $path = self
::addTrailingSlash($path);
501 if ($dh = opendir($path)) {
502 while (FALSE !== ($elem = readdir($dh))) {
503 if (substr($elem, -(strlen($ext) +
1)) == '.' . $ext) {
504 $files[] .= $path . $elem;
513 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
516 * The directory to be secured.
517 * @param bool $overwrite
519 public static function restrictAccess($dir, $overwrite = FALSE) {
520 // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
521 // of site, causing site to stop functioning.
522 // FIXME: we should do more checks here -
523 if (!empty($dir) && is_dir($dir)) {
524 $htaccess = <<<HTACCESS
531 $file = $dir . '.htaccess';
532 if ($overwrite ||
!file_exists($file)) {
533 if (file_put_contents($file, $htaccess) === FALSE) {
534 CRM_Core_Error
::movedSiteError($file);
541 * Restrict remote users from browsing the given directory.
545 public static function restrictBrowsing($publicDir) {
546 if (!is_dir($publicDir) ||
!is_writable($publicDir)) {
551 $nobrowse = realpath($publicDir) . '/index.html';
552 if (!file_exists($nobrowse)) {
553 @file_put_contents
($nobrowse, '');
557 $dir = new RecursiveDirectoryIterator($publicDir);
558 foreach ($dir as $name => $object) {
559 if (is_dir($name) && $name != '..') {
560 $nobrowse = realpath($name) . '/index.html';
561 if (!file_exists($nobrowse)) {
562 @file_put_contents
($nobrowse, '');
569 * Create the base file path from which all our internal directories are
570 * offset. This is derived from the template compile directory set
572 public static function baseFilePath() {
573 static $_path = NULL;
575 // Note: Don't rely on $config; that creates a dependency loop.
576 if (!defined('CIVICRM_TEMPLATE_COMPILEDIR')) {
577 throw new RuntimeException("Undefined constant: CIVICRM_TEMPLATE_COMPILEDIR");
579 $templateCompileDir = CIVICRM_TEMPLATE_COMPILEDIR
;
581 $path = dirname($templateCompileDir);
583 //this fix is to avoid creation of upload dirs inside templates_c directory
584 $checkPath = explode(DIRECTORY_SEPARATOR
, $path);
586 $cnt = count($checkPath) - 1;
587 if ($checkPath[$cnt] == 'templates_c') {
588 unset($checkPath[$cnt]);
589 $path = implode(DIRECTORY_SEPARATOR
, $checkPath);
592 $_path = CRM_Utils_File
::addTrailingSlash($path);
598 * Determine if a path is absolute.
600 * @param string $path
603 * TRUE if absolute. FALSE if relative.
605 public static function isAbsolute($path) {
606 if (substr($path, 0, 1) === DIRECTORY_SEPARATOR
) {
609 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
610 if (preg_match('!^[a-zA-Z]:[/\\\\]!', $path)) {
622 public static function relativeDirectory($directory) {
623 // Do nothing on windows
624 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
628 // check if directory is relative, if so return immediately
629 if (!self
::isAbsolute($directory)) {
633 // make everything relative from the baseFilePath
634 $basePath = self
::baseFilePath();
635 // check if basePath is a substr of $directory, if so
636 // return rest of string
637 if (substr($directory, 0, strlen($basePath)) == $basePath) {
638 return substr($directory, strlen($basePath));
641 // return the original value
647 * @param string|NULL $basePath
648 * The base path when evaluating relative paths. Should include trailing slash.
652 public static function absoluteDirectory($directory, $basePath = NULL) {
653 // check if directory is already absolute, if so return immediately
654 // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
655 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
659 // check if directory is already absolute, if so return immediately
660 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR
) {
664 // make everything absolute from the baseFilePath
665 $basePath = ($basePath === NULL) ? self
::baseFilePath() : $basePath;
667 // ensure that $basePath has a trailing slash
668 $basePath = self
::addTrailingSlash($basePath);
669 return $basePath . $directory;
673 * Make a file path relative to some base dir.
680 public static function relativize($directory, $basePath) {
681 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN') {
682 $directory = strtr($directory, '\\', '/');
683 $basePath = strtr($basePath, '\\', '/');
685 if (substr($directory, 0, strlen($basePath)) == $basePath) {
686 return substr($directory, strlen($basePath));
694 * Create a path to a temporary file which can endure for multiple requests.
696 * @todo Automatic file cleanup using, eg, TTL policy
698 * @param string $prefix
700 * @return string, path to an openable/writable file
703 public static function tempnam($prefix = 'tmp-') {
704 // $config = CRM_Core_Config::singleton();
705 // $nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
706 // $fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
707 $fileName = tempnam(sys_get_temp_dir(), $prefix);
712 * Create a path to a temporary directory which can endure for multiple requests.
714 * @todo Automatic file cleanup using, eg, TTL policy
716 * @param string $prefix
718 * @return string, path to an openable/writable directory; ends with '/'
721 public static function tempdir($prefix = 'tmp-') {
722 $fileName = self
::tempnam($prefix);
724 mkdir($fileName, 0700);
725 return $fileName . '/';
729 * Search directory tree for files which match a glob pattern.
731 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
735 * @param string $pattern
736 * glob pattern, eg "*.txt".
737 * @param bool $relative
738 * TRUE if paths should be made relative to $dir
739 * @return array(string)
741 public static function findFiles($dir, $pattern, $relative = FALSE) {
745 $dir = rtrim($dir, '/');
746 $todos = array($dir);
748 while (!empty($todos)) {
749 $subdir = array_shift($todos);
750 $matches = glob("$subdir/$pattern");
751 if (is_array($matches)) {
752 foreach ($matches as $match) {
753 if (!is_dir($match)) {
754 $result[] = $relative ? CRM_Utils_File
::relativize($match, "$dir/") : $match;
758 if ($dh = opendir($subdir)) {
759 while (FALSE !== ($entry = readdir($dh))) {
760 $path = $subdir . DIRECTORY_SEPARATOR
. $entry;
761 if ($entry{0} == '.') {
764 elseif (is_dir($path)) {
775 * Determine if $child is a sub-directory of $parent
777 * @param string $parent
778 * @param string $child
779 * @param bool $checkRealPath
783 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
784 if ($checkRealPath) {
785 $parent = realpath($parent);
786 $child = realpath($child);
788 $parentParts = explode('/', rtrim($parent, '/'));
789 $childParts = explode('/', rtrim($child, '/'));
790 while (($parentPart = array_shift($parentParts)) !== NULL) {
791 $childPart = array_shift($childParts);
792 if ($parentPart != $childPart) {
796 if (empty($childParts)) {
797 return FALSE; // same directory
805 * Move $fromDir to $toDir, replacing/deleting any
806 * pre-existing content.
808 * @param string $fromDir
809 * The directory which should be moved.
810 * @param string $toDir
811 * The new location of the directory.
812 * @param bool $verbose
817 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
818 if (is_dir($toDir)) {
819 if (!self
::cleanDir($toDir, TRUE, $verbose)) {
824 // return rename($fromDir, $toDir); CRM-11987, https://bugs.php.net/bug.php?id=54097
826 CRM_Utils_File
::copyDir($fromDir, $toDir);
827 if (!CRM_Utils_File
::cleanDir($fromDir, TRUE, FALSE)) {
828 CRM_Core_Session
::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');
837 * @param array $param
838 * @param string $fileName
839 * @param array $extraParams
841 public static function formatFile(&$param, $fileName, $extraParams = array()) {
842 if (empty($param[$fileName])) {
847 'uri' => $param[$fileName]['name'],
848 'type' => $param[$fileName]['type'],
849 'location' => $param[$fileName]['name'],
850 'upload_date' => date('YmdHis'),
853 $param[$fileName] = $fileParams;
857 * Return formatted file URL, like for image file return image url with image icon
859 * @param string $path
861 * @param string $fileType
863 * File preview link e.g. https://example.com/civicrm/file?reset=1&filename=image.png&mime-type=image/png
865 * @return string $url
867 public static function getFileURL($path, $fileType, $url = NULL) {
868 if (empty($path) ||
empty($fileType)) {
871 elseif (empty($url)) {
872 $fileName = basename($path);
873 $url = CRM_Utils_System
::url('civicrm/file', "reset=1&filename={$fileName}&mime-type={$fileType}");
882 list($imageWidth, $imageHeight) = getimagesize($path);
883 list($imageThumbWidth, $imageThumbHeight) = CRM_Contact_BAO_Contact
::getThumbSize($imageWidth, $imageHeight);
884 $url = "<a href=\"$url\" class='crm-image-popup'>
885 <img src=\"$url\" width=$imageThumbWidth height=$imageThumbHeight/>
890 $url = sprintf('<a href="%s">%s</a>', $url, basename($path));
898 * Return formatted image icon
900 * @param string $imageURL
901 * Contact's image url
903 * @return string $url
905 public static function getImageURL($imageURL) {
906 // retrieve image name from $imageURL
907 $imageURL = CRM_Utils_String
::unstupifyUrl($imageURL);
908 parse_str(parse_url($imageURL, PHP_URL_QUERY
), $query);
910 $path = CRM_Core_Config
::singleton()->customFileUploadDir
. $query['photo'];
911 $mimeType = 'image/' . strtolower(pathinfo($path, PATHINFO_EXTENSION
));
913 return self
::getFileURL($path, $mimeType);
918 * Get file icon class for specific MIME Type
920 * @param string $mimeType
923 public static function getIconFromMimeType($mimeType) {
924 if (!isset(Civi
::$statics[__CLASS__
]['mimeIcons'])) {
925 Civi
::$statics[__CLASS__
]['mimeIcons'] = json_decode(file_get_contents(__DIR__
. '/File/mimeIcons.json'), TRUE);
927 $iconClasses = Civi
::$statics[__CLASS__
]['mimeIcons'];
928 foreach ($iconClasses as $text => $icon) {
929 if (strpos($mimeType, $text) === 0) {
933 return $iconClasses['*'];