| 1 | <?php |
| 2 | /* |
| 3 | +--------------------------------------------------------------------+ |
| 4 | | CiviCRM version 4.7 | |
| 5 | +--------------------------------------------------------------------+ |
| 6 | | Copyright CiviCRM LLC (c) 2004-2015 | |
| 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-2015 |
| 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 | */ |
| 106 | public static function createDir($path, $abort = TRUE) { |
| 107 | if (is_dir($path) || empty($path)) { |
| 108 | return; |
| 109 | } |
| 110 | |
| 111 | CRM_Utils_File::createDir(dirname($path), $abort); |
| 112 | if (@mkdir($path, 0777) == FALSE) { |
| 113 | if ($abort) { |
| 114 | $docLink = CRM_Utils_System::docURL2('Moving an Existing Installation to a New Server or Location', NULL, NULL, NULL, NULL, "wiki"); |
| 115 | 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 | |
| 117 | CRM_Utils_System::civiExit(); |
| 118 | } |
| 119 | else { |
| 120 | return FALSE; |
| 121 | } |
| 122 | } |
| 123 | return TRUE; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Delete a directory given a path name, delete children directories |
| 128 | * and files if needed |
| 129 | * |
| 130 | * @param string $target |
| 131 | * The path name. |
| 132 | * @param bool $rmdir |
| 133 | * @param bool $verbose |
| 134 | * |
| 135 | * @throws Exception |
| 136 | */ |
| 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"); |
| 141 | } |
| 142 | |
| 143 | if ($dh = @opendir($target)) { |
| 144 | while (FALSE !== ($sibling = readdir($dh))) { |
| 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($dh); |
| 159 | |
| 160 | if ($rmdir) { |
| 161 | if (rmdir($target)) { |
| 162 | if ($verbose) { |
| 163 | CRM_Core_Session::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success'); |
| 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 | |
| 174 | /** |
| 175 | * Concatenate several files. |
| 176 | * |
| 177 | * @param array $files |
| 178 | * List of file names. |
| 179 | * @param string $delim |
| 180 | * An optional delimiter to put between files. |
| 181 | * @return string |
| 182 | */ |
| 183 | public static function concat($files, $delim = '') { |
| 184 | $buf = ''; |
| 185 | $first = TRUE; |
| 186 | foreach ($files as $file) { |
| 187 | if (!$first) { |
| 188 | $buf .= $delim; |
| 189 | } |
| 190 | $buf .= file_get_contents($file); |
| 191 | $first = FALSE; |
| 192 | } |
| 193 | return $buf; |
| 194 | } |
| 195 | |
| 196 | /** |
| 197 | * @param string $source |
| 198 | * @param string $destination |
| 199 | */ |
| 200 | public static function copyDir($source, $destination) { |
| 201 | if ($dh = opendir($source)) { |
| 202 | @mkdir($destination); |
| 203 | while (FALSE !== ($file = readdir($dh))) { |
| 204 | if (($file != '.') && ($file != '..')) { |
| 205 | if (is_dir($source . DIRECTORY_SEPARATOR . $file)) { |
| 206 | CRM_Utils_File::copyDir($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file); |
| 207 | } |
| 208 | else { |
| 209 | copy($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file); |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | closedir($dh); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | /** |
| 218 | * Given a file name, recode it (in place!) to UTF-8 |
| 219 | * |
| 220 | * @param string $name |
| 221 | * Name of file. |
| 222 | * |
| 223 | * @return bool |
| 224 | * whether the file was recoded properly |
| 225 | */ |
| 226 | public static function toUtf8($name) { |
| 227 | static $config = NULL; |
| 228 | static $legacyEncoding = NULL; |
| 229 | if ($config == NULL) { |
| 230 | $config = CRM_Core_Config::singleton(); |
| 231 | $legacyEncoding = $config->legacyEncoding; |
| 232 | } |
| 233 | |
| 234 | if (!function_exists('iconv')) { |
| 235 | |
| 236 | return FALSE; |
| 237 | |
| 238 | } |
| 239 | |
| 240 | $contents = file_get_contents($name); |
| 241 | if ($contents === FALSE) { |
| 242 | return FALSE; |
| 243 | } |
| 244 | |
| 245 | $contents = iconv($legacyEncoding, 'UTF-8', $contents); |
| 246 | if ($contents === FALSE) { |
| 247 | return FALSE; |
| 248 | } |
| 249 | |
| 250 | $file = fopen($name, 'w'); |
| 251 | if ($file === FALSE) { |
| 252 | return FALSE; |
| 253 | } |
| 254 | |
| 255 | $written = fwrite($file, $contents); |
| 256 | $closed = fclose($file); |
| 257 | if ($written === FALSE or !$closed) { |
| 258 | return FALSE; |
| 259 | } |
| 260 | |
| 261 | return TRUE; |
| 262 | } |
| 263 | |
| 264 | /** |
| 265 | * Appends a slash to the end of a string if it doesn't already end with one |
| 266 | * |
| 267 | * @param string $path |
| 268 | * @param string $slash |
| 269 | * |
| 270 | * @return string |
| 271 | */ |
| 272 | public static function addTrailingSlash($path, $slash = NULL) { |
| 273 | if (!$slash) { |
| 274 | // FIXME: Defaulting to backslash on windows systems can produce |
| 275 | // unexpected results, esp for URL strings which should always use forward-slashes. |
| 276 | // I think this fn should default to forward-slash instead. |
| 277 | $slash = DIRECTORY_SEPARATOR; |
| 278 | } |
| 279 | if (!in_array(substr($path, -1, 1), array('/', '\\'))) { |
| 280 | $path .= $slash; |
| 281 | } |
| 282 | return $path; |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * @param $dsn |
| 287 | * @param string $fileName |
| 288 | * @param null $prefix |
| 289 | * @param bool $isQueryString |
| 290 | * @param bool $dieOnErrors |
| 291 | */ |
| 292 | public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) { |
| 293 | require_once 'DB.php'; |
| 294 | |
| 295 | $db = DB::connect($dsn); |
| 296 | if (PEAR::isError($db)) { |
| 297 | die("Cannot open $dsn: " . $db->getMessage()); |
| 298 | } |
| 299 | if (CRM_Utils_Constant::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System::isDevelopment())) { |
| 300 | $db->query('SET SESSION sql_mode = STRICT_TRANS_TABLES'); |
| 301 | } |
| 302 | |
| 303 | if (!$isQueryString) { |
| 304 | $string = $prefix . file_get_contents($fileName); |
| 305 | } |
| 306 | else { |
| 307 | // use filename as query string |
| 308 | $string = $prefix . $fileName; |
| 309 | } |
| 310 | |
| 311 | // get rid of comments starting with # and -- |
| 312 | |
| 313 | $string = preg_replace("/^#[^\n]*$/m", "\n", $string); |
| 314 | $string = preg_replace("/^(--[^-]).*/m", "\n", $string); |
| 315 | |
| 316 | $queries = preg_split('/;\s*$/m', $string); |
| 317 | foreach ($queries as $query) { |
| 318 | $query = trim($query); |
| 319 | if (!empty($query)) { |
| 320 | CRM_Core_Error::debug_query($query); |
| 321 | $res = &$db->query($query); |
| 322 | if (PEAR::isError($res)) { |
| 323 | if ($dieOnErrors) { |
| 324 | die("Cannot execute $query: " . $res->getMessage()); |
| 325 | } |
| 326 | else { |
| 327 | echo "Cannot execute $query: " . $res->getMessage() . "<p>"; |
| 328 | } |
| 329 | } |
| 330 | } |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | /** |
| 335 | * @param $ext |
| 336 | * |
| 337 | * @return bool |
| 338 | */ |
| 339 | public static function isExtensionSafe($ext) { |
| 340 | static $extensions = NULL; |
| 341 | if (!$extensions) { |
| 342 | $extensions = CRM_Core_OptionGroup::values('safe_file_extension', TRUE); |
| 343 | |
| 344 | // make extensions to lowercase |
| 345 | $extensions = array_change_key_case($extensions, CASE_LOWER); |
| 346 | // allow html/htm extension ONLY if the user is admin |
| 347 | // and/or has access CiviMail |
| 348 | if (!(CRM_Core_Permission::check('access CiviMail') || |
| 349 | CRM_Core_Permission::check('administer CiviCRM') || |
| 350 | (CRM_Mailing_Info::workflowEnabled() && |
| 351 | CRM_Core_Permission::check('create mailings') |
| 352 | ) |
| 353 | ) |
| 354 | ) { |
| 355 | unset($extensions['html']); |
| 356 | unset($extensions['htm']); |
| 357 | } |
| 358 | } |
| 359 | // support lower and uppercase file extensions |
| 360 | return isset($extensions[strtolower($ext)]) ? TRUE : FALSE; |
| 361 | } |
| 362 | |
| 363 | /** |
| 364 | * Determine whether a given file is listed in the PHP include path. |
| 365 | * |
| 366 | * @param string $name |
| 367 | * Name of file. |
| 368 | * |
| 369 | * @return bool |
| 370 | * whether the file can be include()d or require()d |
| 371 | */ |
| 372 | public static function isIncludable($name) { |
| 373 | $x = @fopen($name, 'r', TRUE); |
| 374 | if ($x) { |
| 375 | fclose($x); |
| 376 | return TRUE; |
| 377 | } |
| 378 | else { |
| 379 | return FALSE; |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | /** |
| 384 | * Remove the 32 bit md5 we add to the fileName also remove the unknown tag if we added it. |
| 385 | * |
| 386 | * @param $name |
| 387 | * |
| 388 | * @return mixed |
| 389 | */ |
| 390 | public static function cleanFileName($name) { |
| 391 | // replace the last 33 character before the '.' with null |
| 392 | $name = preg_replace('/(_[\w]{32})\./', '.', $name); |
| 393 | return $name; |
| 394 | } |
| 395 | |
| 396 | /** |
| 397 | * @param string $name |
| 398 | * |
| 399 | * @return string |
| 400 | */ |
| 401 | public static function makeFileName($name) { |
| 402 | $uniqID = md5(uniqid(rand(), TRUE)); |
| 403 | $info = pathinfo($name); |
| 404 | $basename = substr($info['basename'], |
| 405 | 0, -(strlen(CRM_Utils_Array::value('extension', $info)) + (CRM_Utils_Array::value('extension', $info) == '' ? 0 : 1)) |
| 406 | ); |
| 407 | if (!self::isExtensionSafe(CRM_Utils_Array::value('extension', $info))) { |
| 408 | // munge extension so it cannot have an embbeded dot in it |
| 409 | // The maximum length of a filename for most filesystems is 255 chars. |
| 410 | // We'll truncate at 240 to give some room for the extension. |
| 411 | return CRM_Utils_String::munge("{$basename}_" . CRM_Utils_Array::value('extension', $info) . "_{$uniqID}", '_', 240) . ".unknown"; |
| 412 | } |
| 413 | else { |
| 414 | return CRM_Utils_String::munge("{$basename}_{$uniqID}", '_', 240) . "." . CRM_Utils_Array::value('extension', $info); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | /** |
| 419 | * @param $path |
| 420 | * @param $ext |
| 421 | * |
| 422 | * @return array |
| 423 | */ |
| 424 | public static function getFilesByExtension($path, $ext) { |
| 425 | $path = self::addTrailingSlash($path); |
| 426 | $files = array(); |
| 427 | if ($dh = opendir($path)) { |
| 428 | while (FALSE !== ($elem = readdir($dh))) { |
| 429 | if (substr($elem, -(strlen($ext) + 1)) == '.' . $ext) { |
| 430 | $files[] .= $path . $elem; |
| 431 | } |
| 432 | } |
| 433 | closedir($dh); |
| 434 | } |
| 435 | return $files; |
| 436 | } |
| 437 | |
| 438 | /** |
| 439 | * Restrict access to a given directory (by planting there a restrictive .htaccess file) |
| 440 | * |
| 441 | * @param string $dir |
| 442 | * The directory to be secured. |
| 443 | * @param bool $overwrite |
| 444 | */ |
| 445 | public static function restrictAccess($dir, $overwrite = FALSE) { |
| 446 | // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir |
| 447 | // of site, causing site to stop functioning. |
| 448 | // FIXME: we should do more checks here - |
| 449 | if (!empty($dir) && is_dir($dir)) { |
| 450 | $htaccess = <<<HTACCESS |
| 451 | <Files "*"> |
| 452 | Order allow,deny |
| 453 | Deny from all |
| 454 | </Files> |
| 455 | |
| 456 | HTACCESS; |
| 457 | $file = $dir . '.htaccess'; |
| 458 | if ($overwrite || !file_exists($file)) { |
| 459 | if (file_put_contents($file, $htaccess) === FALSE) { |
| 460 | CRM_Core_Error::movedSiteError($file); |
| 461 | } |
| 462 | } |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | /** |
| 467 | * Restrict remote users from browsing the given directory. |
| 468 | * |
| 469 | * @param $publicDir |
| 470 | */ |
| 471 | public static function restrictBrowsing($publicDir) { |
| 472 | if (!is_dir($publicDir) || !is_writable($publicDir)) { |
| 473 | return; |
| 474 | } |
| 475 | |
| 476 | // base dir |
| 477 | $nobrowse = realpath($publicDir) . '/index.html'; |
| 478 | if (!file_exists($nobrowse)) { |
| 479 | @file_put_contents($nobrowse, ''); |
| 480 | } |
| 481 | |
| 482 | // child dirs |
| 483 | $dir = new RecursiveDirectoryIterator($publicDir); |
| 484 | foreach ($dir as $name => $object) { |
| 485 | if (is_dir($name) && $name != '..') { |
| 486 | $nobrowse = realpath($name) . '/index.html'; |
| 487 | if (!file_exists($nobrowse)) { |
| 488 | @file_put_contents($nobrowse, ''); |
| 489 | } |
| 490 | } |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | /** |
| 495 | * Create the base file path from which all our internal directories are |
| 496 | * offset. This is derived from the template compile directory set |
| 497 | */ |
| 498 | public static function baseFilePath() { |
| 499 | static $_path = NULL; |
| 500 | if (!$_path) { |
| 501 | // Note: Don't rely on $config; that creates a dependency loop. |
| 502 | if (!defined('CIVICRM_TEMPLATE_COMPILEDIR')) { |
| 503 | throw new RuntimeException("Undefined constant: CIVICRM_TEMPLATE_COMPILEDIR"); |
| 504 | } |
| 505 | $templateCompileDir = CIVICRM_TEMPLATE_COMPILEDIR; |
| 506 | |
| 507 | $path = dirname($templateCompileDir); |
| 508 | |
| 509 | //this fix is to avoid creation of upload dirs inside templates_c directory |
| 510 | $checkPath = explode(DIRECTORY_SEPARATOR, $path); |
| 511 | |
| 512 | $cnt = count($checkPath) - 1; |
| 513 | if ($checkPath[$cnt] == 'templates_c') { |
| 514 | unset($checkPath[$cnt]); |
| 515 | $path = implode(DIRECTORY_SEPARATOR, $checkPath); |
| 516 | } |
| 517 | |
| 518 | $_path = CRM_Utils_File::addTrailingSlash($path); |
| 519 | } |
| 520 | return $_path; |
| 521 | } |
| 522 | |
| 523 | /** |
| 524 | * Determine if a path is absolute. |
| 525 | * |
| 526 | * @param string $path |
| 527 | * |
| 528 | * @return bool |
| 529 | * TRUE if absolute. FALSE if relative. |
| 530 | */ |
| 531 | public static function isAbsolute($path) { |
| 532 | if (substr($path, 0, 1) === DIRECTORY_SEPARATOR) { |
| 533 | return TRUE; |
| 534 | } |
| 535 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
| 536 | if (preg_match('!^[a-zA-Z]:[/\\\\]!', $path)) { |
| 537 | return TRUE; |
| 538 | } |
| 539 | } |
| 540 | return FALSE; |
| 541 | } |
| 542 | |
| 543 | /** |
| 544 | * @param $directory |
| 545 | * |
| 546 | * @return string |
| 547 | */ |
| 548 | public static function relativeDirectory($directory) { |
| 549 | // Do nothing on windows |
| 550 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
| 551 | return $directory; |
| 552 | } |
| 553 | |
| 554 | // check if directory is relative, if so return immediately |
| 555 | if (!self::isAbsolute($directory)) { |
| 556 | return $directory; |
| 557 | } |
| 558 | |
| 559 | // make everything relative from the baseFilePath |
| 560 | $basePath = self::baseFilePath(); |
| 561 | // check if basePath is a substr of $directory, if so |
| 562 | // return rest of string |
| 563 | if (substr($directory, 0, strlen($basePath)) == $basePath) { |
| 564 | return substr($directory, strlen($basePath)); |
| 565 | } |
| 566 | |
| 567 | // return the original value |
| 568 | return $directory; |
| 569 | } |
| 570 | |
| 571 | /** |
| 572 | * @param $directory |
| 573 | * @param string|NULL $basePath |
| 574 | * The base path when evaluating relative paths. Should include trailing slash. |
| 575 | * |
| 576 | * @return string |
| 577 | */ |
| 578 | public static function absoluteDirectory($directory, $basePath = NULL) { |
| 579 | // check if directory is already absolute, if so return immediately |
| 580 | // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path |
| 581 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) { |
| 582 | return $directory; |
| 583 | } |
| 584 | |
| 585 | // check if directory is already absolute, if so return immediately |
| 586 | if (substr($directory, 0, 1) == DIRECTORY_SEPARATOR) { |
| 587 | return $directory; |
| 588 | } |
| 589 | |
| 590 | // make everything absolute from the baseFilePath |
| 591 | $basePath = ($basePath === NULL) ? self::baseFilePath() : $basePath; |
| 592 | |
| 593 | return $basePath . $directory; |
| 594 | } |
| 595 | |
| 596 | /** |
| 597 | * Make a file path relative to some base dir. |
| 598 | * |
| 599 | * @param $directory |
| 600 | * @param $basePath |
| 601 | * |
| 602 | * @return string |
| 603 | */ |
| 604 | public static function relativize($directory, $basePath) { |
| 605 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
| 606 | $directory = strtr($directory, '\\', '/'); |
| 607 | $basePath = strtr($basePath, '\\', '/'); |
| 608 | } |
| 609 | if (substr($directory, 0, strlen($basePath)) == $basePath) { |
| 610 | return substr($directory, strlen($basePath)); |
| 611 | } |
| 612 | else { |
| 613 | return $directory; |
| 614 | } |
| 615 | } |
| 616 | |
| 617 | /** |
| 618 | * Create a path to a temporary file which can endure for multiple requests. |
| 619 | * |
| 620 | * @todo Automatic file cleanup using, eg, TTL policy |
| 621 | * |
| 622 | * @param string $prefix |
| 623 | * |
| 624 | * @return string, path to an openable/writable file |
| 625 | * @see tempnam |
| 626 | */ |
| 627 | public static function tempnam($prefix = 'tmp-') { |
| 628 | // $config = CRM_Core_Config::singleton(); |
| 629 | // $nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL); |
| 630 | // $fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix; |
| 631 | $fileName = tempnam(sys_get_temp_dir(), $prefix); |
| 632 | return $fileName; |
| 633 | } |
| 634 | |
| 635 | /** |
| 636 | * Create a path to a temporary directory which can endure for multiple requests. |
| 637 | * |
| 638 | * @todo Automatic file cleanup using, eg, TTL policy |
| 639 | * |
| 640 | * @param string $prefix |
| 641 | * |
| 642 | * @return string, path to an openable/writable directory; ends with '/' |
| 643 | * @see tempnam |
| 644 | */ |
| 645 | public static function tempdir($prefix = 'tmp-') { |
| 646 | $fileName = self::tempnam($prefix); |
| 647 | unlink($fileName); |
| 648 | mkdir($fileName, 0700); |
| 649 | return $fileName . '/'; |
| 650 | } |
| 651 | |
| 652 | /** |
| 653 | * Search directory tree for files which match a glob pattern. |
| 654 | * |
| 655 | * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored. |
| 656 | * |
| 657 | * @param string $dir |
| 658 | * base dir. |
| 659 | * @param string $pattern |
| 660 | * glob pattern, eg "*.txt". |
| 661 | * @param bool $relative |
| 662 | * TRUE if paths should be made relative to $dir |
| 663 | * @return array(string) |
| 664 | */ |
| 665 | public static function findFiles($dir, $pattern, $relative = FALSE) { |
| 666 | $dir = rtrim($dir, '/'); |
| 667 | $todos = array($dir); |
| 668 | $result = array(); |
| 669 | while (!empty($todos)) { |
| 670 | $subdir = array_shift($todos); |
| 671 | $matches = glob("$subdir/$pattern"); |
| 672 | if (is_array($matches)) { |
| 673 | foreach ($matches as $match) { |
| 674 | if (!is_dir($match)) { |
| 675 | $result[] = $relative ? CRM_Utils_File::relativize($match, "$dir/") : $match; |
| 676 | } |
| 677 | } |
| 678 | } |
| 679 | if ($dh = opendir($subdir)) { |
| 680 | while (FALSE !== ($entry = readdir($dh))) { |
| 681 | $path = $subdir . DIRECTORY_SEPARATOR . $entry; |
| 682 | if ($entry{0} == '.') { |
| 683 | // ignore |
| 684 | } |
| 685 | elseif (is_dir($path)) { |
| 686 | $todos[] = $path; |
| 687 | } |
| 688 | } |
| 689 | closedir($dh); |
| 690 | } |
| 691 | } |
| 692 | return $result; |
| 693 | } |
| 694 | |
| 695 | /** |
| 696 | * Determine if $child is a sub-directory of $parent |
| 697 | * |
| 698 | * @param string $parent |
| 699 | * @param string $child |
| 700 | * @param bool $checkRealPath |
| 701 | * |
| 702 | * @return bool |
| 703 | */ |
| 704 | public static function isChildPath($parent, $child, $checkRealPath = TRUE) { |
| 705 | if ($checkRealPath) { |
| 706 | $parent = realpath($parent); |
| 707 | $child = realpath($child); |
| 708 | } |
| 709 | $parentParts = explode('/', rtrim($parent, '/')); |
| 710 | $childParts = explode('/', rtrim($child, '/')); |
| 711 | while (($parentPart = array_shift($parentParts)) !== NULL) { |
| 712 | $childPart = array_shift($childParts); |
| 713 | if ($parentPart != $childPart) { |
| 714 | return FALSE; |
| 715 | } |
| 716 | } |
| 717 | if (empty($childParts)) { |
| 718 | return FALSE; // same directory |
| 719 | } |
| 720 | else { |
| 721 | return TRUE; |
| 722 | } |
| 723 | } |
| 724 | |
| 725 | /** |
| 726 | * Move $fromDir to $toDir, replacing/deleting any |
| 727 | * pre-existing content. |
| 728 | * |
| 729 | * @param string $fromDir |
| 730 | * The directory which should be moved. |
| 731 | * @param string $toDir |
| 732 | * The new location of the directory. |
| 733 | * @param bool $verbose |
| 734 | * |
| 735 | * @return bool |
| 736 | * TRUE on success |
| 737 | */ |
| 738 | public static function replaceDir($fromDir, $toDir, $verbose = FALSE) { |
| 739 | if (is_dir($toDir)) { |
| 740 | if (!self::cleanDir($toDir, TRUE, $verbose)) { |
| 741 | return FALSE; |
| 742 | } |
| 743 | } |
| 744 | |
| 745 | // return rename($fromDir, $toDir); CRM-11987, https://bugs.php.net/bug.php?id=54097 |
| 746 | |
| 747 | CRM_Utils_File::copyDir($fromDir, $toDir); |
| 748 | if (!CRM_Utils_File::cleanDir($fromDir, TRUE, FALSE)) { |
| 749 | CRM_Core_Session::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert'); |
| 750 | return FALSE; |
| 751 | } |
| 752 | return TRUE; |
| 753 | } |
| 754 | |
| 755 | } |