remove a bunch of unused variable assignments
[civicrm-core.git] / CRM / Utils / File.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
TO
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
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * $Id: $
33 *
34 */
35
36/**
37 * class to provide simple static functions for file objects
38 */
39class CRM_Utils_File {
40
41 /**
42 * Given a file name, determine if the file contents make it an ascii file
43 *
44 * @param string $name name of file
45 *
46 * @return boolean true if file is ascii
6a488035 47 */
00be9182 48 public static function isAscii($name) {
6a488035
TO
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 name of file
71 *
72 * @return boolean true if file is html
6a488035 73 */
00be9182 74 public static function isHtml($name) {
6a488035
TO
75 $fd = fopen($name, "r");
76 if (!$fd) {
77 return FALSE;
78 }
79
80 $html = FALSE;
81 $lineCount = 0;
82 while (!feof($fd) & $lineCount <= 5) {
83 $lineCount++;
84 $line = fgets($fd, 8192);
85 if (!CRM_Utils_String::isHtml($line)) {
86 $html = TRUE;
87 break;
88 }
89 }
90
91 fclose($fd);
92 return $html;
93 }
94
95 /**
100fef9d 96 * Create a directory given a path name, creates parent directories
6a488035
TO
97 * if needed
98 *
99 * @param string $path the path name
100 * @param boolean $abort should we abort or just return an invalid code
101 *
102 * @return void
6a488035
TO
103 * @static
104 */
00be9182 105 public static function createDir($path, $abort = TRUE) {
6a488035
TO
106 if (is_dir($path) || empty($path)) {
107 return;
108 }
109
110 CRM_Utils_File::createDir(dirname($path), $abort);
111 if (@mkdir($path, 0777) == FALSE) {
112 if ($abort) {
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>";
115
116 CRM_Utils_System::civiExit();
117 }
118 else {
119 return FALSE;
120 }
121 }
122 return TRUE;
123 }
124
125 /**
100fef9d 126 * Delete a directory given a path name, delete children directories
6a488035
TO
127 * and files if needed
128 *
c490a46a 129 * @param string $target the path name
f4aaa82a
EM
130 * @param bool $rmdir
131 * @param bool $verbose
132 *
133 * @throws Exception
6a488035 134 * @return void
6a488035
TO
135 * @static
136 */
00be9182 137 public static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
6a488035
TO
138 static $exceptions = array('.', '..');
139 if ($target == '' || $target == '/') {
140 throw new Exception("Overly broad deletion");
141 }
142
948d11bf 143 if ($sourcedir = opendir($target)) {
6a488035
TO
144 while (FALSE !== ($sibling = readdir($sourcedir))) {
145 if (!in_array($sibling, $exceptions)) {
146 $object = $target . DIRECTORY_SEPARATOR . $sibling;
147
148 if (is_dir($object)) {
149 CRM_Utils_File::cleanDir($object, $rmdir, $verbose);
150 }
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');
154 }
155 }
156 }
157 }
158 closedir($sourcedir);
159
160 if ($rmdir) {
161 if (rmdir($target)) {
162 if ($verbose) {
450f494d 163 CRM_Core_Session::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
6a488035
TO
164 }
165 return TRUE;
166 }
167 else {
168 CRM_Core_Session::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
169 }
170 }
171 }
172 }
173
5bc392e6
EM
174 /**
175 * @param $source
176 * @param $destination
177 */
948d11bf
CB
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);
185 }
186 else {
187 copy($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file);
188 }
6a488035
TO
189 }
190 }
948d11bf 191 closedir($dir);
6a488035 192 }
6a488035
TO
193 }
194
195 /**
196 * Given a file name, recode it (in place!) to UTF-8
197 *
198 * @param string $name name of file
199 *
200 * @return boolean whether the file was recoded properly
6a488035 201 */
00be9182 202 public static function toUtf8($name) {
6a488035
TO
203 static $config = NULL;
204 static $legacyEncoding = NULL;
205 if ($config == NULL) {
206 $config = CRM_Core_Config::singleton();
207 $legacyEncoding = $config->legacyEncoding;
208 }
209
210 if (!function_exists('iconv')) {
211
212 return FALSE;
213
214 }
215
216 $contents = file_get_contents($name);
217 if ($contents === FALSE) {
218 return FALSE;
219 }
220
221 $contents = iconv($legacyEncoding, 'UTF-8', $contents);
222 if ($contents === FALSE) {
223 return FALSE;
224 }
225
226 $file = fopen($name, 'w');
227 if ($file === FALSE) {
228 return FALSE;
229 }
230
231 $written = fwrite($file, $contents);
232 $closed = fclose($file);
233 if ($written === FALSE or !$closed) {
234 return FALSE;
235 }
236
237 return TRUE;
238 }
239
240 /**
5c8cb77f 241 * Appends a slash to the end of a string if it doesn't already end with one
6a488035 242 *
5c8cb77f
CW
243 * @param string $path
244 * @param string $slash
f4aaa82a 245 *
6a488035 246 * @return string
6a488035
TO
247 * @static
248 */
00be9182 249 public static function addTrailingSlash($path, $slash = NULL) {
5c8cb77f
CW
250 if (!$slash) {
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;
6a488035 254 }
5c8cb77f
CW
255 if (!in_array(substr($path, -1, 1), array('/', '\\'))) {
256 $path .= $slash;
6a488035 257 }
5c8cb77f 258 return $path;
6a488035
TO
259 }
260
5bc392e6
EM
261 /**
262 * @param $dsn
100fef9d 263 * @param string $fileName
5bc392e6
EM
264 * @param null $prefix
265 * @param bool $isQueryString
266 * @param bool $dieOnErrors
267 */
00be9182 268 public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
6a488035
TO
269 require_once 'DB.php';
270
271 $db = DB::connect($dsn);
272 if (PEAR::isError($db)) {
273 die("Cannot open $dsn: " . $db->getMessage());
274 }
275 if (CRM_Utils_Constant::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System::isDevelopment())) {
276 $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES');
277 }
278
279 if (!$isQueryString) {
280 $string = $prefix . file_get_contents($fileName);
281 }
282 else {
283 // use filename as query string
284 $string = $prefix . $fileName;
285 }
286
287 //get rid of comments starting with # and --
288
289 $string = preg_replace("/^#[^\n]*$/m", "\n", $string);
290 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
291
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)) {
299 if ($dieOnErrors) {
300 die("Cannot execute $query: " . $res->getMessage());
301 }
302 else {
303 echo "Cannot execute $query: " . $res->getMessage() . "<p>";
304 }
305 }
306 }
307 }
308 }
309
5bc392e6
EM
310 /**
311 * @param $ext
312 *
313 * @return bool
314 */
00be9182 315 public static function isExtensionSafe($ext) {
6a488035
TO
316 static $extensions = NULL;
317 if (!$extensions) {
318 $extensions = CRM_Core_OptionGroup::values('safe_file_extension', TRUE);
319
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')
328 )
329 )) {
330 unset($extensions['html']);
331 unset($extensions['htm']);
332 }
333 }
334 //support lower and uppercase file extensions
335 return isset($extensions[strtolower($ext)]) ? TRUE : FALSE;
336 }
337
338 /**
339 * Determine whether a given file is listed in the PHP include path
340 *
341 * @param string $name name of file
342 *
343 * @return boolean whether the file can be include()d or require()d
344 */
00be9182 345 public static function isIncludable($name) {
6a488035
TO
346 $x = @fopen($name, 'r', TRUE);
347 if ($x) {
348 fclose($x);
349 return TRUE;
350 }
351 else {
352 return FALSE;
353 }
354 }
355
356 /**
100fef9d 357 * Remove the 32 bit md5 we add to the fileName
6a488035
TO
358 * also remove the unknown tag if we added it
359 */
00be9182 360 public static function cleanFileName($name) {
6a488035
TO
361 // replace the last 33 character before the '.' with null
362 $name = preg_replace('/(_[\w]{32})\./', '.', $name);
363 return $name;
364 }
365
5bc392e6 366 /**
100fef9d 367 * @param string $name
5bc392e6
EM
368 *
369 * @return string
370 */
00be9182 371 public static function makeFileName($name) {
6a488035
TO
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))
376 );
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";
382 }
383 else {
384 return CRM_Utils_String::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array::value('extension', $info);
385 }
386 }
387
5bc392e6
EM
388 /**
389 * @param $path
390 * @param $ext
391 *
392 * @return array
393 */
00be9182 394 public static function getFilesByExtension($path, $ext) {
6a488035 395 $path = self::addTrailingSlash($path);
948d11bf
CB
396 if ($dh = opendir($path)) {
397 $files = array();
398 while (FALSE !== ($elem = readdir($dh))) {
399 if (substr($elem, -(strlen($ext) + 1)) == '.' . $ext) {
400 $files[] .= $path . $elem;
401 }
6a488035 402 }
948d11bf
CB
403 closedir($dh);
404 return $files;
6a488035 405 }
6a488035
TO
406 }
407
408 /**
409 * Restrict access to a given directory (by planting there a restrictive .htaccess file)
410 *
f4aaa82a
EM
411 * @param string $dir the directory to be secured
412 * @param bool $overwrite
6a488035 413 */
00be9182 414 public static function restrictAccess($dir, $overwrite = FALSE) {
6a488035
TO
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 -
ea3b22b5 418 if (!empty($dir) && is_dir($dir)) {
6a488035
TO
419 $htaccess = <<<HTACCESS
420<Files "*">
421 Order allow,deny
422 Deny from all
423</Files>
424
425HTACCESS;
426 $file = $dir . '.htaccess';
ea3b22b5
TO
427 if ($overwrite || !file_exists($file)) {
428 if (file_put_contents($file, $htaccess) === FALSE) {
429 CRM_Core_Error::movedSiteError($file);
430 }
6a488035
TO
431 }
432 }
433 }
434
af5201d4
TO
435 /**
436 * Restrict remote users from browsing the given directory.
437 *
438 * @param $publicDir
439 */
00be9182 440 public static function restrictBrowsing($publicDir) {
9404eeac
TO
441 if (!is_dir($publicDir) || !is_writable($publicDir)) {
442 return;
443 }
444
af5201d4
TO
445 // base dir
446 $nobrowse = realpath($publicDir) . '/index.html';
447 if (!file_exists($nobrowse)) {
448 @file_put_contents($nobrowse, '');
449 }
450
451 // child dirs
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, '');
458 }
459 }
460 }
461 }
462
6a488035
TO
463 /**
464 * Create the base file path from which all our internal directories are
465 * offset. This is derived from the template compile directory set
466 */
00be9182 467 public static function baseFilePath($templateCompileDir = NULL) {
6a488035
TO
468 static $_path = NULL;
469 if (!$_path) {
470 if ($templateCompileDir == NULL) {
471 $config = CRM_Core_Config::singleton();
472 $templateCompileDir = $config->templateCompileDir;
473 }
474
475 $path = dirname($templateCompileDir);
476
477 //this fix is to avoid creation of upload dirs inside templates_c directory
478 $checkPath = explode(DIRECTORY_SEPARATOR, $path);
479
480 $cnt = count($checkPath) - 1;
481 if ($checkPath[$cnt] == 'templates_c') {
482 unset($checkPath[$cnt]);
483 $path = implode(DIRECTORY_SEPARATOR, $checkPath);
484 }
485
486 $_path = CRM_Utils_File::addTrailingSlash($path);
487 }
488 return $_path;
489 }
490
5bc392e6
EM
491 /**
492 * @param $directory
493 *
494 * @return string
495 */
00be9182 496 public static function relativeDirectory($directory) {
6a488035
TO
497 // Do nothing on windows
498 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
499 return $directory;
500 }
501
502 // check if directory is relative, if so return immediately
503 if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR) {
504 return $directory;
505 }
506
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));
513 }
514
515 // return the original value
516 return $directory;
517 }
518
5bc392e6
EM
519 /**
520 * @param $directory
521 *
522 * @return string
523 */
00be9182 524 public static function absoluteDirectory($directory) {
acc609a7
TO
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)) {
6a488035
TO
528 return $directory;
529 }
530
531 // check if directory is already absolute, if so return immediately
532 if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR) {
533 return $directory;
534 }
535
536 // make everything absolute from the baseFilePath
537 $basePath = self::baseFilePath();
538
539 return $basePath . $directory;
540 }
541
542 /**
543 * Make a file path relative to some base dir
544 *
f4aaa82a
EM
545 * @param $directory
546 * @param $basePath
547 *
6a488035
TO
548 * @return string
549 */
00be9182 550 public static function relativize($directory, $basePath) {
6a488035
TO
551 if (substr($directory, 0, strlen($basePath)) == $basePath) {
552 return substr($directory, strlen($basePath));
553 } else {
554 return $directory;
555 }
556 }
557
558 /**
559 * Create a path to a temporary file which can endure for multiple requests
560 *
561 * TODO: Automatic file cleanup using, eg, TTL policy
562 *
563 * @param $prefix string
564 *
565 * @return string, path to an openable/writable file
566 * @see tempnam
567 */
00be9182 568 public static function tempnam($prefix = 'tmp-') {
6a488035
TO
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);
573 return $fileName;
574 }
575
576 /**
577 * Create a path to a temporary directory which can endure for multiple requests
578 *
579 * TODO: Automatic file cleanup using, eg, TTL policy
580 *
581 * @param $prefix string
582 *
583 * @return string, path to an openable/writable directory; ends with '/'
584 * @see tempnam
585 */
00be9182 586 public static function tempdir($prefix = 'tmp-') {
6a488035
TO
587 $fileName = self::tempnam($prefix);
588 unlink($fileName);
589 mkdir($fileName, 0700);
590 return $fileName . '/';
591 }
592
593 /**
d7166b43
TO
594 * Search directory tree for files which match a glob pattern.
595 *
596 * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
6a488035
TO
597 *
598 * @param $dir string, base dir
599 * @param $pattern string, glob pattern, eg "*.txt"
600 * @return array(string)
601 */
00be9182 602 public static function findFiles($dir, $pattern) {
6a488035
TO
603 $todos = array($dir);
604 $result = array();
605 while (!empty($todos)) {
606 $subdir = array_shift($todos);
0b72a00f
TO
607 $matches = glob("$subdir/$pattern");
608 if (is_array($matches)) {
609 foreach ($matches as $match) {
002f1716
TO
610 if (!is_dir($match)) {
611 $result[] = $match;
612 }
6a488035
TO
613 }
614 }
948d11bf 615 if ($dh = opendir($subdir)) {
6a488035
TO
616 while (FALSE !== ($entry = readdir($dh))) {
617 $path = $subdir . DIRECTORY_SEPARATOR . $entry;
d7166b43
TO
618 if ($entry{0} == '.') {
619 // ignore
6a488035
TO
620 } elseif (is_dir($path)) {
621 $todos[] = $path;
622 }
623 }
624 closedir($dh);
625 }
626 }
627 return $result;
628 }
629
630 /**
631 * Determine if $child is a sub-directory of $parent
632 *
633 * @param string $parent
634 * @param string $child
f4aaa82a
EM
635 * @param bool $checkRealPath
636 *
6a488035
TO
637 * @return bool
638 */
00be9182 639 public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
6a488035
TO
640 if ($checkRealPath) {
641 $parent = realpath($parent);
642 $child = realpath($child);
643 }
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) {
649 return FALSE;
650 }
651 }
652 if (empty($childParts)) {
653 return FALSE; // same directory
654 } else {
655 return TRUE;
656 }
657 }
658
659 /**
660 * Move $fromDir to $toDir, replacing/deleting any
661 * pre-existing content.
662 *
663 * @param string $fromDir the directory which should be moved
664 * @param string $toDir the new location of the directory
f4aaa82a
EM
665 * @param bool $verbose
666 *
6a488035
TO
667 * @return bool TRUE on success
668 */
00be9182 669 public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
6a488035
TO
670 if (is_dir($toDir)) {
671 if (!self::cleanDir($toDir, TRUE, $verbose)) {
672 return FALSE;
673 }
674 }
675
676 // return rename($fromDir, $toDir); // CRM-11987, https://bugs.php.net/bug.php?id=54097
677
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');
681 return FALSE;
682 }
683 return TRUE;
684 }
685}