Merge pull request #10163 from JohnFF/bugfix/fix_static_warning
[civicrm-core.git] / CRM / Utils / File.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2017
32 */
33
34 /**
35 * class to provide simple static functions for file objects
36 */
37 class CRM_Utils_File {
38
39 /**
40 * Given a file name, determine if the file contents make it an ascii file
41 *
42 * @param string $name
43 * Name of file.
44 *
45 * @return bool
46 * true if file is ascii
47 */
48 public static function isAscii($name) {
49 $fd = fopen($name, "r");
50 if (!$fd) {
51 return FALSE;
52 }
53
54 $ascii = TRUE;
55 while (!feof($fd)) {
56 $line = fgets($fd, 8192);
57 if (!CRM_Utils_String::isAscii($line)) {
58 $ascii = FALSE;
59 break;
60 }
61 }
62
63 fclose($fd);
64 return $ascii;
65 }
66
67 /**
68 * Given a file name, determine if the file contents make it an html file
69 *
70 * @param string $name
71 * Name of file.
72 *
73 * @return bool
74 * true if file is html
75 */
76 public static function isHtml($name) {
77 $fd = fopen($name, "r");
78 if (!$fd) {
79 return FALSE;
80 }
81
82 $html = FALSE;
83 $lineCount = 0;
84 while (!feof($fd) & $lineCount <= 5) {
85 $lineCount++;
86 $line = fgets($fd, 8192);
87 if (!CRM_Utils_String::isHtml($line)) {
88 $html = TRUE;
89 break;
90 }
91 }
92
93 fclose($fd);
94 return $html;
95 }
96
97 /**
98 * Create a directory given a path name, creates parent directories
99 * if needed
100 *
101 * @param string $path
102 * The path name.
103 * @param bool $abort
104 * Should we abort or just return an invalid code.
105 * @return bool|NULL
106 * NULL: Folder already exists or was not specified.
107 * TRUE: Creation succeeded.
108 * FALSE: Creation failed.
109 */
110 public static function createDir($path, $abort = TRUE) {
111 if (is_dir($path) || empty($path)) {
112 return NULL;
113 }
114
115 CRM_Utils_File::createDir(dirname($path), $abort);
116 if (@mkdir($path, 0777) == FALSE) {
117 if ($abort) {
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>";
120
121 CRM_Utils_System::civiExit();
122 }
123 else {
124 return FALSE;
125 }
126 }
127 return TRUE;
128 }
129
130 /**
131 * Delete a directory given a path name, delete children directories
132 * and files if needed
133 *
134 * @param string $target
135 * The path name.
136 * @param bool $rmdir
137 * @param bool $verbose
138 *
139 * @throws Exception
140 */
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");
145 }
146
147 if ($dh = @opendir($target)) {
148 while (FALSE !== ($sibling = readdir($dh))) {
149 if (!in_array($sibling, $exceptions)) {
150 $object = $target . DIRECTORY_SEPARATOR . $sibling;
151
152 if (is_dir($object)) {
153 CRM_Utils_File::cleanDir($object, $rmdir, $verbose);
154 }
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');
158 }
159 }
160 }
161 }
162 closedir($dh);
163
164 if ($rmdir) {
165 if (rmdir($target)) {
166 if ($verbose) {
167 CRM_Core_Session::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
168 }
169 return TRUE;
170 }
171 else {
172 CRM_Core_Session::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
173 }
174 }
175 }
176 }
177
178 /**
179 * Concatenate several files.
180 *
181 * @param array $files
182 * List of file names.
183 * @param string $delim
184 * An optional delimiter to put between files.
185 * @return string
186 */
187 public static function concat($files, $delim = '') {
188 $buf = '';
189 $first = TRUE;
190 foreach ($files as $file) {
191 if (!$first) {
192 $buf .= $delim;
193 }
194 $buf .= file_get_contents($file);
195 $first = FALSE;
196 }
197 return $buf;
198 }
199
200 /**
201 * @param string $source
202 * @param string $destination
203 */
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);
211 }
212 else {
213 copy($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file);
214 }
215 }
216 }
217 closedir($dh);
218 }
219 }
220
221 /**
222 * Given a file name, recode it (in place!) to UTF-8
223 *
224 * @param string $name
225 * Name of file.
226 *
227 * @return bool
228 * whether the file was recoded properly
229 */
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;
236 }
237
238 if (!function_exists('iconv')) {
239
240 return FALSE;
241
242 }
243
244 $contents = file_get_contents($name);
245 if ($contents === FALSE) {
246 return FALSE;
247 }
248
249 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
250 if ($contents === FALSE) {
251 return FALSE;
252 }
253
254 $file = fopen($name, 'w');
255 if ($file === FALSE) {
256 return FALSE;
257 }
258
259 $written = fwrite($file, $contents);
260 $closed = fclose($file);
261 if ($written === FALSE or !$closed) {
262 return FALSE;
263 }
264
265 return TRUE;
266 }
267
268 /**
269 * Appends a slash to the end of a string if it doesn't already end with one
270 *
271 * @param string $path
272 * @param string $slash
273 *
274 * @return string
275 */
276 public static function addTrailingSlash($path, $slash = NULL) {
277 if (!$slash) {
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;
282 }
283 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
284 $path .= $slash;
285 }
286 return $path;
287 }
288
289 /**
290 * Save a fake file somewhere
291 *
292 * @param string $dir
293 * The directory where the file should be saved.
294 * @param string $contents
295 * Optional: the contents of the file.
296 * @param string $fileName
297 *
298 * @return string
299 * The filename saved, or FALSE on failure.
300 */
301 public static function createFakeFile($dir, $contents = 'delete me', $fileName = NULL) {
302 $dir = self::addTrailingSlash($dir);
303 if (!$fileName) {
304 $fileName = 'delete-this-' . CRM_Utils_String::createRandom(10, CRM_Utils_String::ALPHANUMERIC);
305 }
306 $success = file_put_contents($dir . $fileName, $contents);
307
308 return ($success === FALSE) ? FALSE : $fileName;
309 }
310
311 /**
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
319 */
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.');
326 }
327 }
328
329 if ($dsn === NULL) {
330 $db = CRM_Core_DAO::getConnection();
331 }
332 else {
333 require_once 'DB.php';
334 $db = DB::connect($dsn);
335 }
336
337 if (PEAR::isError($db)) {
338 die("Cannot open $dsn: " . $db->getMessage());
339 }
340 if (CRM_Utils_Constant::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System::isDevelopment())) {
341 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
342 }
343 $db->query('SET NAMES utf8');
344 $transactionId = CRM_Utils_Type::escape(CRM_Utils_Request::id(), 'String');
345 $db->query('SET @uniqueID = ' . "'$transactionId'");
346
347 if (!$isQueryString) {
348 $string = $prefix . file_get_contents($fileName);
349 }
350 else {
351 // use filename as query string
352 $string = $prefix . $fileName;
353 }
354
355 // get rid of comments starting with # and --
356
357 $string = self::stripComments($string);
358
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)) {
366 if ($dieOnErrors) {
367 die("Cannot execute $query: " . $res->getMessage());
368 }
369 else {
370 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
371 }
372 }
373 }
374 }
375 }
376 /**
377 *
378 * Strips comment from a possibly multiline SQL string
379 *
380 * @param string $string
381 *
382 * @return string
383 * stripped string
384 */
385 public static function stripComments($string) {
386 return preg_replace("/^(#|--).*\R*/m", "", $string);
387 }
388
389 /**
390 * @param $ext
391 *
392 * @return bool
393 */
394 public static function isExtensionSafe($ext) {
395 static $extensions = NULL;
396 if (!$extensions) {
397 $extensions = CRM_Core_OptionGroup::values('safe_file_extension', TRUE);
398
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')
407 )
408 )
409 ) {
410 unset($extensions['html']);
411 unset($extensions['htm']);
412 }
413 }
414 // support lower and uppercase file extensions
415 return isset($extensions[strtolower($ext)]) ? TRUE : FALSE;
416 }
417
418 /**
419 * Determine whether a given file is listed in the PHP include path.
420 *
421 * @param string $name
422 * Name of file.
423 *
424 * @return bool
425 * whether the file can be include()d or require()d
426 */
427 public static function isIncludable($name) {
428 $x = @fopen($name, 'r', TRUE);
429 if ($x) {
430 fclose($x);
431 return TRUE;
432 }
433 else {
434 return FALSE;
435 }
436 }
437
438 /**
439 * Remove the 32 bit md5 we add to the fileName also remove the unknown tag if we added it.
440 *
441 * @param $name
442 *
443 * @return mixed
444 */
445 public static function cleanFileName($name) {
446 // replace the last 33 character before the '.' with null
447 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
448 return $name;
449 }
450
451 /**
452 * Make a valid file name.
453 *
454 * @param string $name
455 *
456 * @return string
457 */
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))
463 );
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";
469 }
470 else {
471 return CRM_Utils_String::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array::value('extension', $info);
472 }
473 }
474
475 /**
476 * Copies a file
477 *
478 * @param $filePath
479 * @return mixed
480 */
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);
487 return $newPath;
488 }
489
490 /**
491 * Get files for the extension.
492 *
493 * @param string $path
494 * @param string $ext
495 *
496 * @return array
497 */
498 public static function getFilesByExtension($path, $ext) {
499 $path = self::addTrailingSlash($path);
500 $files = array();
501 if ($dh = opendir($path)) {
502 while (FALSE !== ($elem = readdir($dh))) {
503 if (substr($elem, -(strlen($ext) + 1)) == '.' . $ext) {
504 $files[] .= $path . $elem;
505 }
506 }
507 closedir($dh);
508 }
509 return $files;
510 }
511
512 /**
513 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
514 *
515 * @param string $dir
516 * The directory to be secured.
517 * @param bool $overwrite
518 */
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
525 <Files "*">
526 Order allow,deny
527 Deny from all
528 </Files>
529
530 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);
535 }
536 }
537 }
538 }
539
540 /**
541 * Restrict remote users from browsing the given directory.
542 *
543 * @param $publicDir
544 */
545 public static function restrictBrowsing($publicDir) {
546 if (!is_dir($publicDir) || !is_writable($publicDir)) {
547 return;
548 }
549
550 // base dir
551 $nobrowse = realpath($publicDir) . '/index.html';
552 if (!file_exists($nobrowse)) {
553 @file_put_contents($nobrowse, '');
554 }
555
556 // child dirs
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, '');
563 }
564 }
565 }
566 }
567
568 /**
569 * Create the base file path from which all our internal directories are
570 * offset. This is derived from the template compile directory set
571 */
572 public static function baseFilePath() {
573 static $_path = NULL;
574 if (!$_path) {
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");
578 }
579 $templateCompileDir = CIVICRM_TEMPLATE_COMPILEDIR;
580
581 $path = dirname($templateCompileDir);
582
583 //this fix is to avoid creation of upload dirs inside templates_c directory
584 $checkPath = explode(DIRECTORY_SEPARATOR, $path);
585
586 $cnt = count($checkPath) - 1;
587 if ($checkPath[$cnt] == 'templates_c') {
588 unset($checkPath[$cnt]);
589 $path = implode(DIRECTORY_SEPARATOR, $checkPath);
590 }
591
592 $_path = CRM_Utils_File::addTrailingSlash($path);
593 }
594 return $_path;
595 }
596
597 /**
598 * Determine if a path is absolute.
599 *
600 * @param string $path
601 *
602 * @return bool
603 * TRUE if absolute. FALSE if relative.
604 */
605 public static function isAbsolute($path) {
606 if (substr($path, 0, 1) === DIRECTORY_SEPARATOR) {
607 return TRUE;
608 }
609 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
610 if (preg_match('!^[a-zA-Z]:[/\\\\]!', $path)) {
611 return TRUE;
612 }
613 }
614 return FALSE;
615 }
616
617 /**
618 * @param $directory
619 *
620 * @return string
621 */
622 public static function relativeDirectory($directory) {
623 // Do nothing on windows
624 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
625 return $directory;
626 }
627
628 // check if directory is relative, if so return immediately
629 if (!self::isAbsolute($directory)) {
630 return $directory;
631 }
632
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));
639 }
640
641 // return the original value
642 return $directory;
643 }
644
645 /**
646 * @param $directory
647 * @param string|NULL $basePath
648 * The base path when evaluating relative paths. Should include trailing slash.
649 *
650 * @return string
651 */
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)) {
656 return $directory;
657 }
658
659 // check if directory is already absolute, if so return immediately
660 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR) {
661 return $directory;
662 }
663
664 // make everything absolute from the baseFilePath
665 $basePath = ($basePath === NULL) ? self::baseFilePath() : $basePath;
666
667 // ensure that $basePath has a trailing slash
668 $basePath = self::addTrailingSlash($basePath);
669 return $basePath . $directory;
670 }
671
672 /**
673 * Make a file path relative to some base dir.
674 *
675 * @param $directory
676 * @param $basePath
677 *
678 * @return string
679 */
680 public static function relativize($directory, $basePath) {
681 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
682 $directory = strtr($directory, '\\', '/');
683 $basePath = strtr($basePath, '\\', '/');
684 }
685 if (substr($directory, 0, strlen($basePath)) == $basePath) {
686 return substr($directory, strlen($basePath));
687 }
688 else {
689 return $directory;
690 }
691 }
692
693 /**
694 * Create a path to a temporary file which can endure for multiple requests.
695 *
696 * @todo Automatic file cleanup using, eg, TTL policy
697 *
698 * @param string $prefix
699 *
700 * @return string, path to an openable/writable file
701 * @see tempnam
702 */
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);
708 return $fileName;
709 }
710
711 /**
712 * Create a path to a temporary directory which can endure for multiple requests.
713 *
714 * @todo Automatic file cleanup using, eg, TTL policy
715 *
716 * @param string $prefix
717 *
718 * @return string, path to an openable/writable directory; ends with '/'
719 * @see tempnam
720 */
721 public static function tempdir($prefix = 'tmp-') {
722 $fileName = self::tempnam($prefix);
723 unlink($fileName);
724 mkdir($fileName, 0700);
725 return $fileName . '/';
726 }
727
728 /**
729 * Search directory tree for files which match a glob pattern.
730 *
731 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
732 *
733 * @param string $dir
734 * base dir.
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)
740 */
741 public static function findFiles($dir, $pattern, $relative = FALSE) {
742 if (!is_dir($dir)) {
743 return array();
744 }
745 $dir = rtrim($dir, '/');
746 $todos = array($dir);
747 $result = array();
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;
755 }
756 }
757 }
758 if ($dh = opendir($subdir)) {
759 while (FALSE !== ($entry = readdir($dh))) {
760 $path = $subdir . DIRECTORY_SEPARATOR . $entry;
761 if ($entry{0} == '.') {
762 // ignore
763 }
764 elseif (is_dir($path)) {
765 $todos[] = $path;
766 }
767 }
768 closedir($dh);
769 }
770 }
771 return $result;
772 }
773
774 /**
775 * Determine if $child is a sub-directory of $parent
776 *
777 * @param string $parent
778 * @param string $child
779 * @param bool $checkRealPath
780 *
781 * @return bool
782 */
783 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
784 if ($checkRealPath) {
785 $parent = realpath($parent);
786 $child = realpath($child);
787 }
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) {
793 return FALSE;
794 }
795 }
796 if (empty($childParts)) {
797 return FALSE; // same directory
798 }
799 else {
800 return TRUE;
801 }
802 }
803
804 /**
805 * Move $fromDir to $toDir, replacing/deleting any
806 * pre-existing content.
807 *
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
813 *
814 * @return bool
815 * TRUE on success
816 */
817 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
818 if (is_dir($toDir)) {
819 if (!self::cleanDir($toDir, TRUE, $verbose)) {
820 return FALSE;
821 }
822 }
823
824 // return rename($fromDir, $toDir); CRM-11987, https://bugs.php.net/bug.php?id=54097
825
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');
829 return FALSE;
830 }
831 return TRUE;
832 }
833
834 /**
835 * Format file.
836 *
837 * @param array $param
838 * @param string $fileName
839 * @param array $extraParams
840 */
841 public static function formatFile(&$param, $fileName, $extraParams = array()) {
842 if (empty($param[$fileName])) {
843 return;
844 }
845
846 $fileParams = array(
847 'uri' => $param[$fileName]['name'],
848 'type' => $param[$fileName]['type'],
849 'location' => $param[$fileName]['name'],
850 'upload_date' => date('YmdHis'),
851 ) + $extraParams;
852
853 $param[$fileName] = $fileParams;
854 }
855
856 /**
857 * Return formatted file URL, like for image file return image url with image icon
858 *
859 * @param string $path
860 * Absoulte file path
861 * @param string $fileType
862 * @param string $url
863 * File preview link e.g. https://example.com/civicrm/file?reset=1&filename=image.png&mime-type=image/png
864 *
865 * @return string $url
866 */
867 public static function getFileURL($path, $fileType, $url = NULL) {
868 if (empty($path) || empty($fileType)) {
869 return '';
870 }
871 elseif (empty($url)) {
872 $fileName = basename($path);
873 $url = CRM_Utils_System::url('civicrm/file', "reset=1&filename={$fileName}&mime-type={$fileType}");
874 }
875 switch ($fileType) {
876 case 'image/jpeg':
877 case 'image/pjpeg':
878 case 'image/gif':
879 case 'image/x-png':
880 case 'image/png':
881 case 'image/jpg':
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/>
886 </a>";
887 break;
888
889 default:
890 $url = sprintf('<a href="%s">%s</a>', $url, basename($path));
891 break;
892 }
893
894 return $url;
895 }
896
897 /**
898 * Return formatted image icon
899 *
900 * @param string $imageURL
901 * Contact's image url
902 *
903 * @return string $url
904 */
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);
909
910 $path = CRM_Core_Config::singleton()->customFileUploadDir . $query['photo'];
911 $mimeType = 'image/' . strtolower(pathinfo($path, PATHINFO_EXTENSION));
912
913 return self::getFileURL($path, $mimeType);
914 }
915
916
917 /**
918 * Get file icon class for specific MIME Type
919 *
920 * @param string $mimeType
921 * @return string
922 */
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);
926 }
927 $iconClasses = Civi::$statics[__CLASS__]['mimeIcons'];
928 foreach ($iconClasses as $text => $icon) {
929 if (strpos($mimeType, $text) === 0) {
930 return $icon;
931 }
932 }
933 return $iconClasses['*'];
934 }
935
936 }