CRM-16499 - Fix relative Angular paths in Windows
[civicrm-core.git] / CRM / Utils / File.php
index 255a0c118f332d7ff07326858eda407807214375..a8ca3b77c3c17fb6955fd9a0bf4374cebd8a6197 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 4.5                                                |
+ | CiviCRM version 4.6                                                |
  +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2014                                |
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
  +--------------------------------------------------------------------+
  | This file is a part of CiviCRM.                                    |
  |                                                                    |
  | GNU Affero General Public License or the licensing of CiviCRM,     |
  | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
  +--------------------------------------------------------------------+
-*/
+ */
 
 /**
  *
  * @package CRM
- * @copyright CiviCRM LLC (c) 2004-2014
+ * @copyright CiviCRM LLC (c) 2004-2015
  * $Id: $
  *
  */
@@ -41,12 +41,13 @@ class CRM_Utils_File {
   /**
    * Given a file name, determine if the file contents make it an ascii file
    *
-   * @param string $name name of file
+   * @param string $name
+   *   Name of file.
    *
-   * @return boolean     true if file is ascii
-   * @access public
+   * @return bool
+   *   true if file is ascii
    */
-  static function isAscii($name) {
+  public static function isAscii($name) {
     $fd = fopen($name, "r");
     if (!$fd) {
       return FALSE;
@@ -68,12 +69,13 @@ class CRM_Utils_File {
   /**
    * Given a file name, determine if the file contents make it an html file
    *
-   * @param string $name name of file
+   * @param string $name
+   *   Name of file.
    *
-   * @return boolean     true if file is html
-   * @access public
+   * @return bool
+   *   true if file is html
    */
-  static function isHtml($name) {
+  public static function isHtml($name) {
     $fd = fopen($name, "r");
     if (!$fd) {
       return FALSE;
@@ -95,17 +97,17 @@ class CRM_Utils_File {
   }
 
   /**
-   * create a directory given a path name, creates parent directories
+   * Create a directory given a path name, creates parent directories
    * if needed
    *
-   * @param string $path  the path name
-   * @param boolean $abort should we abort or just return an invalid code
+   * @param string $path
+   *   The path name.
+   * @param bool $abort
+   *   Should we abort or just return an invalid code.
    *
    * @return void
-   * @access public
-   * @static
    */
-  static function createDir($path, $abort = TRUE) {
+  public static function createDir($path, $abort = TRUE) {
     if (is_dir($path) || empty($path)) {
       return;
     }
@@ -126,21 +128,18 @@ class CRM_Utils_File {
   }
 
   /**
-   * delete a directory given a path name, delete children directories
+   * Delete a directory given a path name, delete children directories
    * and files if needed
    *
-   * @param $target
+   * @param string $target
+   *   The path name.
    * @param bool $rmdir
    * @param bool $verbose
    *
    * @throws Exception
-   * @internal param string $path the path name
-   *
    * @return void
-   * @access public
-   * @static
    */
-  static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
+  public static function cleanDir($target, $rmdir = TRUE, $verbose = TRUE) {
     static $exceptions = array('.', '..');
     if ($target == '' || $target == '/') {
       throw new Exception("Overly broad deletion");
@@ -157,10 +156,10 @@ class CRM_Utils_File {
           elseif (is_file($object)) {
             if (!unlink($object)) {
               CRM_Core_Session::setStatus(ts('Unable to remove file %1', array(1 => $object)), ts('Warning'), 'error');
+            }
           }
         }
       }
-      }
       closedir($dh);
 
       if ($rmdir) {
@@ -169,19 +168,41 @@ class CRM_Utils_File {
             CRM_Core_Session::setStatus(ts('Removed directory %1', array(1 => $target)), '', 'success');
           }
           return TRUE;
-      }
+        }
         else {
           CRM_Core_Session::setStatus(ts('Unable to remove directory %1', array(1 => $target)), ts('Warning'), 'error');
+        }
+      }
     }
   }
+
+  /**
+   * Concatenate several files.
+   *
+   * @param array $files
+   *   List of file names.
+   * @param string $delim
+   *   An optional delimiter to put between files.
+   * @return string
+   */
+  public static function concat($files, $delim = '') {
+    $buf = '';
+    $first = TRUE;
+    foreach ($files as $file) {
+      if (!$first) {
+        $buf .= $delim;
+      }
+      $buf .= file_get_contents($file);
+      $first = FALSE;
     }
+    return $buf;
   }
 
   /**
-   * @param $source
-   * @param $destination
+   * @param string $source
+   * @param string $destination
    */
-  static function copyDir($source, $destination) {
+  public static function copyDir($source, $destination) {
     if ($dh = opendir($source)) {
       @mkdir($destination);
       while (FALSE !== ($file = readdir($dh))) {
@@ -201,12 +222,13 @@ class CRM_Utils_File {
   /**
    * Given a file name, recode it (in place!) to UTF-8
    *
-   * @param string $name name of file
+   * @param string $name
+   *   Name of file.
    *
-   * @return boolean  whether the file was recoded properly
-   * @access public
+   * @return bool
+   *   whether the file was recoded properly
    */
-  static function toUtf8($name) {
+  public static function toUtf8($name) {
     static $config = NULL;
     static $legacyEncoding = NULL;
     if ($config == NULL) {
@@ -251,10 +273,8 @@ class CRM_Utils_File {
    * @param string $slash
    *
    * @return string
-   * @access public
-   * @static
    */
-  static function addTrailingSlash($path, $slash = NULL) {
+  public static function addTrailingSlash($path, $slash = NULL) {
     if (!$slash) {
       // FIXME: Defaulting to backslash on windows systems can produce unexpected results, esp for URL strings which should always use forward-slashes.
       // I think this fn should default to forward-slash instead.
@@ -268,12 +288,12 @@ class CRM_Utils_File {
 
   /**
    * @param $dsn
-   * @param $fileName
+   * @param string $fileName
    * @param null $prefix
    * @param bool $isQueryString
    * @param bool $dieOnErrors
    */
-  static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
+  public static function sourceSQLFile($dsn, $fileName, $prefix = NULL, $isQueryString = FALSE, $dieOnErrors = TRUE) {
     require_once 'DB.php';
 
     $db = DB::connect($dsn);
@@ -320,7 +340,7 @@ class CRM_Utils_File {
    *
    * @return bool
    */
-  static function isExtensionSafe($ext) {
+  public static function isExtensionSafe($ext) {
     static $extensions = NULL;
     if (!$extensions) {
       $extensions = CRM_Core_OptionGroup::values('safe_file_extension', TRUE);
@@ -330,11 +350,12 @@ class CRM_Utils_File {
       // allow html/htm extension ONLY if the user is admin
       // and/or has access CiviMail
       if (!(CRM_Core_Permission::check('access CiviMail') ||
-          CRM_Core_Permission::check('administer CiviCRM') ||
-          (CRM_Mailing_Info::workflowEnabled() &&
-            CRM_Core_Permission::check('create mailings')
-          )
-        )) {
+        CRM_Core_Permission::check('administer CiviCRM') ||
+        (CRM_Mailing_Info::workflowEnabled() &&
+          CRM_Core_Permission::check('create mailings')
+        )
+      )
+      ) {
         unset($extensions['html']);
         unset($extensions['htm']);
       }
@@ -344,13 +365,15 @@ class CRM_Utils_File {
   }
 
   /**
-   * Determine whether a given file is listed in the PHP include path
+   * Determine whether a given file is listed in the PHP include path.
    *
-   * @param string $name name of file
+   * @param string $name
+   *   Name of file.
    *
-   * @return boolean  whether the file can be include()d or require()d
+   * @return bool
+   *   whether the file can be include()d or require()d
    */
-  static function isIncludable($name) {
+  public static function isIncludable($name) {
     $x = @fopen($name, 'r', TRUE);
     if ($x) {
       fclose($x);
@@ -362,23 +385,23 @@ class CRM_Utils_File {
   }
 
   /**
-   * remove the 32 bit md5 we add to the fileName
+   * Remove the 32 bit md5 we add to the fileName
    * also remove the unknown tag if we added it
    */
-  static function cleanFileName($name) {
+  public static function cleanFileName($name) {
     // replace the last 33 character before the '.' with null
     $name = preg_replace('/(_[\w]{32})\./', '.', $name);
     return $name;
   }
 
   /**
-   * @param $name
+   * @param string $name
    *
    * @return string
    */
-  static function makeFileName($name) {
-    $uniqID   = md5(uniqid(rand(), TRUE));
-    $info     = pathinfo($name);
+  public static function makeFileName($name) {
+    $uniqID = md5(uniqid(rand(), TRUE));
+    $info = pathinfo($name);
     $basename = substr($info['basename'],
       0, -(strlen(CRM_Utils_Array::value('extension', $info)) + (CRM_Utils_Array::value('extension', $info) == '' ? 0 : 1))
     );
@@ -399,8 +422,8 @@ class CRM_Utils_File {
    *
    * @return array
    */
-  static function getFilesByExtension($path, $ext) {
-    $path  = self::addTrailingSlash($path);
+  public static function getFilesByExtension($path, $ext) {
+    $path = self::addTrailingSlash($path);
     $files = array();
     if ($dh = opendir($path)) {
       while (FALSE !== ($elem = readdir($dh))) {
@@ -416,10 +439,11 @@ class CRM_Utils_File {
   /**
    * Restrict access to a given directory (by planting there a restrictive .htaccess file)
    *
-   * @param string $dir the directory to be secured
+   * @param string $dir
+   *   The directory to be secured.
    * @param bool $overwrite
    */
-  static function restrictAccess($dir, $overwrite = FALSE) {
+  public static function restrictAccess($dir, $overwrite = FALSE) {
     // note: empty value for $dir can play havoc, since that might result in putting '.htaccess' to root dir
     // of site, causing site to stop functioning.
     // FIXME: we should do more checks here -
@@ -445,7 +469,7 @@ HTACCESS;
    *
    * @param $publicDir
    */
-  static function restrictBrowsing($publicDir) {
+  public static function restrictBrowsing($publicDir) {
     if (!is_dir($publicDir) || !is_writable($publicDir)) {
       return;
     }
@@ -472,7 +496,7 @@ HTACCESS;
    * Create the base file path from which all our internal directories are
    * offset. This is derived from the template compile directory set
    */
-  static function baseFilePath($templateCompileDir = NULL) {
+  public static function baseFilePath($templateCompileDir = NULL) {
     static $_path = NULL;
     if (!$_path) {
       if ($templateCompileDir == NULL) {
@@ -496,19 +520,37 @@ HTACCESS;
     return $_path;
   }
 
+  /**
+   * Determine if a path is absolute.
+   *
+   * @return bool
+   *   TRUE if absolute. FALSE if relative.
+   */
+  public static function isAbsolute($path) {
+    if (substr($path, 0, 1) === DIRECTORY_SEPARATOR) {
+      return TRUE;
+    }
+    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+      if (preg_match('!^[a-zA-Z]:[/\\\\]!', $path)) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
   /**
    * @param $directory
    *
    * @return string
    */
-  static function relativeDirectory($directory) {
+  public static function relativeDirectory($directory) {
     // Do nothing on windows
     if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
       return $directory;
     }
 
     // check if directory is relative, if so return immediately
-    if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR) {
+    if (!self::isAbsolute($directory)) {
       return $directory;
     }
 
@@ -529,7 +571,7 @@ HTACCESS;
    *
    * @return string
    */
-  static function absoluteDirectory($directory) {
+  public static function absoluteDirectory($directory) {
     // check if directory is already absolute, if so return immediately
     // Note: Windows PHP accepts any mix of "/" or "\", so "C:\htdocs" or "C:/htdocs" would be a valid absolute path
     if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && preg_match(';^[a-zA-Z]:[/\\\\];', $directory)) {
@@ -548,32 +590,37 @@ HTACCESS;
   }
 
   /**
-   * Make a file path relative to some base dir
+   * Make a file path relative to some base dir.
    *
    * @param $directory
    * @param $basePath
    *
    * @return string
    */
-  static function relativize($directory, $basePath) {
+  public static function relativize($directory, $basePath) {
+    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+      $directory = strtr($directory, '\\', '/');
+      $basePath = strtr($basePath, '\\', '/');
+    }
     if (substr($directory, 0, strlen($basePath)) == $basePath) {
       return substr($directory, strlen($basePath));
-    } else {
+    }
+    else {
       return $directory;
     }
   }
 
   /**
-   * Create a path to a temporary file which can endure for multiple requests
+   * Create a path to a temporary file which can endure for multiple requests.
    *
    * TODO: Automatic file cleanup using, eg, TTL policy
    *
-   * @param $prefix string
+   * @param string $prefix
    *
    * @return string, path to an openable/writable file
    * @see tempnam
    */
-  static function tempnam($prefix = 'tmp-') {
+  public static function tempnam($prefix = 'tmp-') {
     //$config = CRM_Core_Config::singleton();
     //$nonce = md5(uniqid() . $config->dsn . $config->userFrameworkResourceURL);
     //$fileName = "{$config->configAndLogDir}" . $prefix . $nonce . $suffix;
@@ -582,16 +629,16 @@ HTACCESS;
   }
 
   /**
-   * Create a path to a temporary directory which can endure for multiple requests
+   * Create a path to a temporary directory which can endure for multiple requests.
    *
    * TODO: Automatic file cleanup using, eg, TTL policy
    *
-   * @param $prefix string
+   * @param string $prefix
    *
    * @return string, path to an openable/writable directory; ends with '/'
    * @see tempnam
    */
-  static function tempdir($prefix = 'tmp-') {
+  public static function tempdir($prefix = 'tmp-') {
     $fileName = self::tempnam($prefix);
     unlink($fileName);
     mkdir($fileName, 0700);
@@ -603,11 +650,16 @@ HTACCESS;
    *
    * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored.
    *
-   * @param $dir string, base dir
-   * @param $pattern string, glob pattern, eg "*.txt"
+   * @param string $dir
+   *   base dir.
+   * @param string $pattern
+   *   glob pattern, eg "*.txt".
+   * @param bool $relative
+   *   TRUE if paths should be made relative to $dir
    * @return array(string)
    */
-  static function findFiles($dir, $pattern) {
+  public static function findFiles($dir, $pattern, $relative = FALSE) {
+    $dir = rtrim($dir, '/');
     $todos = array($dir);
     $result = array();
     while (!empty($todos)) {
@@ -616,7 +668,7 @@ HTACCESS;
       if (is_array($matches)) {
         foreach ($matches as $match) {
           if (!is_dir($match)) {
-            $result[] = $match;
+            $result[] = $relative ? CRM_Utils_File::relativize($match, "$dir/") : $match;
           }
         }
       }
@@ -625,7 +677,8 @@ HTACCESS;
           $path = $subdir . DIRECTORY_SEPARATOR . $entry;
           if ($entry{0} == '.') {
             // ignore
-          } elseif (is_dir($path)) {
+          }
+          elseif (is_dir($path)) {
             $todos[] = $path;
           }
         }
@@ -644,7 +697,7 @@ HTACCESS;
    *
    * @return bool
    */
-  static function isChildPath($parent, $child, $checkRealPath = TRUE) {
+  public static function isChildPath($parent, $child, $checkRealPath = TRUE) {
     if ($checkRealPath) {
       $parent = realpath($parent);
       $child = realpath($child);
@@ -659,7 +712,8 @@ HTACCESS;
     }
     if (empty($childParts)) {
       return FALSE; // same directory
-    } else {
+    }
+    else {
       return TRUE;
     }
   }
@@ -668,13 +722,16 @@ HTACCESS;
    * Move $fromDir to $toDir, replacing/deleting any
    * pre-existing content.
    *
-   * @param string $fromDir the directory which should be moved
-   * @param string $toDir the new location of the directory
+   * @param string $fromDir
+   *   The directory which should be moved.
+   * @param string $toDir
+   *   The new location of the directory.
    * @param bool $verbose
    *
-   * @return bool TRUE on success
+   * @return bool
+   *   TRUE on success
    */
-  static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
+  public static function replaceDir($fromDir, $toDir, $verbose = FALSE) {
     if (is_dir($toDir)) {
       if (!self::cleanDir($toDir, TRUE, $verbose)) {
         return FALSE;
@@ -685,10 +742,10 @@ HTACCESS;
 
     CRM_Utils_File::copyDir($fromDir, $toDir);
     if (!CRM_Utils_File::cleanDir($fromDir, TRUE, FALSE)) {
-       CRM_Core_Session::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');
+      CRM_Core_Session::setStatus(ts('Failed to clean temp dir: %1', array(1 => $fromDir)), '', 'alert');
       return FALSE;
     }
     return TRUE;
   }
-}
 
+}