Chris@18: Chris@18: * @copyright 1997-2009 The Authors Chris@18: * @license http://opensource.org/licenses/bsd-license.php New BSD License Chris@18: * @link http://pear.php.net/package/PEAR Chris@18: * @since File available since Release 0.1 Chris@18: */ Chris@18: Chris@18: /** Chris@18: * base class Chris@18: */ Chris@18: require_once 'PEAR.php'; Chris@18: require_once 'Console/Getopt.php'; Chris@18: Chris@18: $GLOBALS['_System_temp_files'] = array(); Chris@18: Chris@18: /** Chris@18: * System offers cross platform compatible system functions Chris@18: * Chris@18: * Static functions for different operations. Should work under Chris@18: * Unix and Windows. The names and usage has been taken from its respectively Chris@18: * GNU commands. The functions will return (bool) false on error and will Chris@18: * trigger the error with the PHP trigger_error() function (you can silence Chris@18: * the error by prefixing a '@' sign after the function call, but this Chris@18: * is not recommended practice. Instead use an error handler with Chris@18: * {@link set_error_handler()}). Chris@18: * Chris@18: * Documentation on this class you can find in: Chris@18: * http://pear.php.net/manual/ Chris@18: * Chris@18: * Example usage: Chris@18: * if (!@System::rm('-r file1 dir1')) { Chris@18: * print "could not delete file1 or dir1"; Chris@18: * } Chris@18: * Chris@18: * In case you need to to pass file names with spaces, Chris@18: * pass the params as an array: Chris@18: * Chris@18: * System::rm(array('-r', $file1, $dir1)); Chris@18: * Chris@18: * @category pear Chris@18: * @package System Chris@18: * @author Tomas V.V. Cox Chris@18: * @copyright 1997-2006 The PHP Group Chris@18: * @license http://opensource.org/licenses/bsd-license.php New BSD License Chris@18: * @version Release: @package_version@ Chris@18: * @link http://pear.php.net/package/PEAR Chris@18: * @since Class available since Release 0.1 Chris@18: * @static Chris@18: */ Chris@18: class System Chris@18: { Chris@18: /** Chris@18: * returns the commandline arguments of a function Chris@18: * Chris@18: * @param string $argv the commandline Chris@18: * @param string $short_options the allowed option short-tags Chris@18: * @param string $long_options the allowed option long-tags Chris@18: * @return array the given options and there values Chris@18: */ Chris@18: public static function _parseArgs($argv, $short_options, $long_options = null) Chris@18: { Chris@18: if (!is_array($argv) && $argv !== null) { Chris@18: /* Chris@18: // Quote all items that are a short option Chris@18: $av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((? $a) { Chris@18: if (empty($a)) { Chris@18: continue; Chris@18: } Chris@18: $argv[$k] = trim($a) ; Chris@18: } Chris@18: } Chris@18: Chris@18: return Console_Getopt::getopt2($argv, $short_options, $long_options); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Output errors with PHP trigger_error(). You can silence the errors Chris@18: * with prefixing a "@" sign to the function call: @System::mkdir(..); Chris@18: * Chris@18: * @param mixed $error a PEAR error or a string with the error message Chris@18: * @return bool false Chris@18: */ Chris@18: protected static function raiseError($error) Chris@18: { Chris@18: if (PEAR::isError($error)) { Chris@18: $error = $error->getMessage(); Chris@18: } Chris@18: trigger_error($error, E_USER_WARNING); Chris@18: return false; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Creates a nested array representing the structure of a directory Chris@18: * Chris@18: * System::_dirToStruct('dir1', 0) => Chris@18: * Array Chris@18: * ( Chris@18: * [dirs] => Array Chris@18: * ( Chris@18: * [0] => dir1 Chris@18: * ) Chris@18: * Chris@18: * [files] => Array Chris@18: * ( Chris@18: * [0] => dir1/file2 Chris@18: * [1] => dir1/file3 Chris@18: * ) Chris@18: * ) Chris@18: * @param string $sPath Name of the directory Chris@18: * @param integer $maxinst max. deep of the lookup Chris@18: * @param integer $aktinst starting deep of the lookup Chris@18: * @param bool $silent if true, do not emit errors. Chris@18: * @return array the structure of the dir Chris@18: */ Chris@18: protected static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false) Chris@18: { Chris@18: $struct = array('dirs' => array(), 'files' => array()); Chris@18: if (($dir = @opendir($sPath)) === false) { Chris@18: if (!$silent) { Chris@18: System::raiseError("Could not open dir $sPath"); Chris@18: } Chris@18: return $struct; // XXX could not open error Chris@18: } Chris@18: Chris@18: $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ? Chris@18: $list = array(); Chris@18: while (false !== ($file = readdir($dir))) { Chris@18: if ($file != '.' && $file != '..') { Chris@18: $list[] = $file; Chris@18: } Chris@18: } Chris@18: Chris@18: closedir($dir); Chris@18: natsort($list); Chris@18: if ($aktinst < $maxinst || $maxinst == 0) { Chris@18: foreach ($list as $val) { Chris@18: $path = $sPath . DIRECTORY_SEPARATOR . $val; Chris@18: if (is_dir($path) && !is_link($path)) { Chris@18: $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent); Chris@18: $struct = array_merge_recursive($struct, $tmp); Chris@18: } else { Chris@18: $struct['files'][] = $path; Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: return $struct; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Creates a nested array representing the structure of a directory and files Chris@18: * Chris@18: * @param array $files Array listing files and dirs Chris@18: * @return array Chris@18: * @static Chris@18: * @see System::_dirToStruct() Chris@18: */ Chris@18: protected static function _multipleToStruct($files) Chris@18: { Chris@18: $struct = array('dirs' => array(), 'files' => array()); Chris@18: settype($files, 'array'); Chris@18: foreach ($files as $file) { Chris@18: if (is_dir($file) && !is_link($file)) { Chris@18: $tmp = System::_dirToStruct($file, 0); Chris@18: $struct = array_merge_recursive($tmp, $struct); Chris@18: } else { Chris@18: if (!in_array($file, $struct['files'])) { Chris@18: $struct['files'][] = $file; Chris@18: } Chris@18: } Chris@18: } Chris@18: return $struct; Chris@18: } Chris@18: Chris@18: /** Chris@18: * The rm command for removing files. Chris@18: * Supports multiple files and dirs and also recursive deletes Chris@18: * Chris@18: * @param string $args the arguments for rm Chris@18: * @return mixed PEAR_Error or true for success Chris@18: * @static Chris@18: * @access public Chris@18: */ Chris@18: public static function rm($args) Chris@18: { Chris@18: $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-) Chris@18: if (PEAR::isError($opts)) { Chris@18: return System::raiseError($opts); Chris@18: } Chris@18: foreach ($opts[0] as $opt) { Chris@18: if ($opt[0] == 'r') { Chris@18: $do_recursive = true; Chris@18: } Chris@18: } Chris@18: $ret = true; Chris@18: if (isset($do_recursive)) { Chris@18: $struct = System::_multipleToStruct($opts[1]); Chris@18: foreach ($struct['files'] as $file) { Chris@18: if (!@unlink($file)) { Chris@18: $ret = false; Chris@18: } Chris@18: } Chris@18: Chris@18: rsort($struct['dirs']); Chris@18: foreach ($struct['dirs'] as $dir) { Chris@18: if (!@rmdir($dir)) { Chris@18: $ret = false; Chris@18: } Chris@18: } Chris@18: } else { Chris@18: foreach ($opts[1] as $file) { Chris@18: $delete = (is_dir($file)) ? 'rmdir' : 'unlink'; Chris@18: if (!@$delete($file)) { Chris@18: $ret = false; Chris@18: } Chris@18: } Chris@18: } Chris@18: return $ret; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Make directories. Chris@18: * Chris@18: * The -p option will create parent directories Chris@18: * @param string $args the name of the director(y|ies) to create Chris@18: * @return bool True for success Chris@18: */ Chris@18: public static function mkDir($args) Chris@18: { Chris@18: $opts = System::_parseArgs($args, 'pm:'); Chris@18: if (PEAR::isError($opts)) { Chris@18: return System::raiseError($opts); Chris@18: } Chris@18: Chris@18: $mode = 0777; // default mode Chris@18: foreach ($opts[0] as $opt) { Chris@18: if ($opt[0] == 'p') { Chris@18: $create_parents = true; Chris@18: } elseif ($opt[0] == 'm') { Chris@18: // if the mode is clearly an octal number (starts with 0) Chris@18: // convert it to decimal Chris@18: if (strlen($opt[1]) && $opt[1]{0} == '0') { Chris@18: $opt[1] = octdec($opt[1]); Chris@18: } else { Chris@18: // convert to int Chris@18: $opt[1] += 0; Chris@18: } Chris@18: $mode = $opt[1]; Chris@18: } Chris@18: } Chris@18: Chris@18: $ret = true; Chris@18: if (isset($create_parents)) { Chris@18: foreach ($opts[1] as $dir) { Chris@18: $dirstack = array(); Chris@18: while ((!file_exists($dir) || !is_dir($dir)) && Chris@18: $dir != DIRECTORY_SEPARATOR) { Chris@18: array_unshift($dirstack, $dir); Chris@18: $dir = dirname($dir); Chris@18: } Chris@18: Chris@18: while ($newdir = array_shift($dirstack)) { Chris@18: if (!is_writeable(dirname($newdir))) { Chris@18: $ret = false; Chris@18: break; Chris@18: } Chris@18: Chris@18: if (!mkdir($newdir, $mode)) { Chris@18: $ret = false; Chris@18: } Chris@18: } Chris@18: } Chris@18: } else { Chris@18: foreach($opts[1] as $dir) { Chris@18: if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) { Chris@18: $ret = false; Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: return $ret; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Concatenate files Chris@18: * Chris@18: * Usage: Chris@18: * 1) $var = System::cat('sample.txt test.txt'); Chris@18: * 2) System::cat('sample.txt test.txt > final.txt'); Chris@18: * 3) System::cat('sample.txt test.txt >> final.txt'); Chris@18: * Chris@18: * Note: as the class use fopen, urls should work also Chris@18: * Chris@18: * @param string $args the arguments Chris@18: * @return boolean true on success Chris@18: */ Chris@18: public static function &cat($args) Chris@18: { Chris@18: $ret = null; Chris@18: $files = array(); Chris@18: if (!is_array($args)) { Chris@18: $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); Chris@18: } Chris@18: Chris@18: $count_args = count($args); Chris@18: for ($i = 0; $i < $count_args; $i++) { Chris@18: if ($args[$i] == '>') { Chris@18: $mode = 'wb'; Chris@18: $outputfile = $args[$i+1]; Chris@18: break; Chris@18: } elseif ($args[$i] == '>>') { Chris@18: $mode = 'ab+'; Chris@18: $outputfile = $args[$i+1]; Chris@18: break; Chris@18: } else { Chris@18: $files[] = $args[$i]; Chris@18: } Chris@18: } Chris@18: $outputfd = false; Chris@18: if (isset($mode)) { Chris@18: if (!$outputfd = fopen($outputfile, $mode)) { Chris@18: $err = System::raiseError("Could not open $outputfile"); Chris@18: return $err; Chris@18: } Chris@18: $ret = true; Chris@18: } Chris@18: foreach ($files as $file) { Chris@18: if (!$fd = fopen($file, 'r')) { Chris@18: System::raiseError("Could not open $file"); Chris@18: continue; Chris@18: } Chris@18: while ($cont = fread($fd, 2048)) { Chris@18: if (is_resource($outputfd)) { Chris@18: fwrite($outputfd, $cont); Chris@18: } else { Chris@18: $ret .= $cont; Chris@18: } Chris@18: } Chris@18: fclose($fd); Chris@18: } Chris@18: if (is_resource($outputfd)) { Chris@18: fclose($outputfd); Chris@18: } Chris@18: return $ret; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Creates temporary files or directories. This function will remove Chris@18: * the created files when the scripts finish its execution. Chris@18: * Chris@18: * Usage: Chris@18: * 1) $tempfile = System::mktemp("prefix"); Chris@18: * 2) $tempdir = System::mktemp("-d prefix"); Chris@18: * 3) $tempfile = System::mktemp(); Chris@18: * 4) $tempfile = System::mktemp("-t /var/tmp prefix"); Chris@18: * Chris@18: * prefix -> The string that will be prepended to the temp name Chris@18: * (defaults to "tmp"). Chris@18: * -d -> A temporary dir will be created instead of a file. Chris@18: * -t -> The target dir where the temporary (file|dir) will be created. If Chris@18: * this param is missing by default the env vars TMP on Windows or Chris@18: * TMPDIR in Unix will be used. If these vars are also missing Chris@18: * c:\windows\temp or /tmp will be used. Chris@18: * Chris@18: * @param string $args The arguments Chris@18: * @return mixed the full path of the created (file|dir) or false Chris@18: * @see System::tmpdir() Chris@18: */ Chris@18: public static function mktemp($args = null) Chris@18: { Chris@18: static $first_time = true; Chris@18: $opts = System::_parseArgs($args, 't:d'); Chris@18: if (PEAR::isError($opts)) { Chris@18: return System::raiseError($opts); Chris@18: } Chris@18: Chris@18: foreach ($opts[0] as $opt) { Chris@18: if ($opt[0] == 'd') { Chris@18: $tmp_is_dir = true; Chris@18: } elseif ($opt[0] == 't') { Chris@18: $tmpdir = $opt[1]; Chris@18: } Chris@18: } Chris@18: Chris@18: $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp'; Chris@18: if (!isset($tmpdir)) { Chris@18: $tmpdir = System::tmpdir(); Chris@18: } Chris@18: Chris@18: if (!System::mkDir(array('-p', $tmpdir))) { Chris@18: return false; Chris@18: } Chris@18: Chris@18: $tmp = tempnam($tmpdir, $prefix); Chris@18: if (isset($tmp_is_dir)) { Chris@18: unlink($tmp); // be careful possible race condition here Chris@18: if (!mkdir($tmp, 0700)) { Chris@18: return System::raiseError("Unable to create temporary directory $tmpdir"); Chris@18: } Chris@18: } Chris@18: Chris@18: $GLOBALS['_System_temp_files'][] = $tmp; Chris@18: if (isset($tmp_is_dir)) { Chris@18: //$GLOBALS['_System_temp_files'][] = dirname($tmp); Chris@18: } Chris@18: Chris@18: if ($first_time) { Chris@18: PEAR::registerShutdownFunc(array('System', '_removeTmpFiles')); Chris@18: $first_time = false; Chris@18: } Chris@18: Chris@18: return $tmp; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Remove temporary files created my mkTemp. This function is executed Chris@18: * at script shutdown time Chris@18: */ Chris@18: public static function _removeTmpFiles() Chris@18: { Chris@18: if (count($GLOBALS['_System_temp_files'])) { Chris@18: $delete = $GLOBALS['_System_temp_files']; Chris@18: array_unshift($delete, '-r'); Chris@18: System::rm($delete); Chris@18: $GLOBALS['_System_temp_files'] = array(); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * Get the path of the temporal directory set in the system Chris@18: * by looking in its environments variables. Chris@18: * Note: php.ini-recommended removes the "E" from the variables_order setting, Chris@18: * making unavaible the $_ENV array, that s why we do tests with _ENV Chris@18: * Chris@18: * @return string The temporary directory on the system Chris@18: */ Chris@18: public static function tmpdir() Chris@18: { Chris@18: if (OS_WINDOWS) { Chris@18: if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) { Chris@18: return $var; Chris@18: } Chris@18: if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) { Chris@18: return $var; Chris@18: } Chris@18: if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) { Chris@18: return $var; Chris@18: } Chris@18: if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) { Chris@18: return $var; Chris@18: } Chris@18: return getenv('SystemRoot') . '\temp'; Chris@18: } Chris@18: if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) { Chris@18: return $var; Chris@18: } Chris@18: return realpath('/tmp'); Chris@18: } Chris@18: Chris@18: /** Chris@18: * The "which" command (show the full path of a command) Chris@18: * Chris@18: * @param string $program The command to search for Chris@18: * @param mixed $fallback Value to return if $program is not found Chris@18: * Chris@18: * @return mixed A string with the full path or false if not found Chris@18: * @author Stig Bakken Chris@18: */ Chris@18: public static function which($program, $fallback = false) Chris@18: { Chris@18: // enforce API Chris@18: if (!is_string($program) || '' == $program) { Chris@18: return $fallback; Chris@18: } Chris@18: Chris@18: // full path given Chris@18: if (basename($program) != $program) { Chris@18: $path_elements[] = dirname($program); Chris@18: $program = basename($program); Chris@18: } else { Chris@18: $path = getenv('PATH'); Chris@18: if (!$path) { Chris@18: $path = getenv('Path'); // some OSes are just stupid enough to do this Chris@18: } Chris@18: Chris@18: $path_elements = explode(PATH_SEPARATOR, $path); Chris@18: } Chris@18: Chris@18: if (OS_WINDOWS) { Chris@18: $exe_suffixes = getenv('PATHEXT') Chris@18: ? explode(PATH_SEPARATOR, getenv('PATHEXT')) Chris@18: : array('.exe','.bat','.cmd','.com'); Chris@18: // allow passing a command.exe param Chris@18: if (strpos($program, '.') !== false) { Chris@18: array_unshift($exe_suffixes, ''); Chris@18: } Chris@18: } else { Chris@18: $exe_suffixes = array(''); Chris@18: } Chris@18: Chris@18: foreach ($exe_suffixes as $suff) { Chris@18: foreach ($path_elements as $dir) { Chris@18: $file = $dir . DIRECTORY_SEPARATOR . $program . $suff; Chris@18: // It's possible to run a .bat on Windows that is_executable Chris@18: // would return false for. The is_executable check is meaningless... Chris@18: if (OS_WINDOWS) { Chris@18: return $file; Chris@18: } else { Chris@18: if (is_executable($file)) { Chris@18: return $file; Chris@18: } Chris@18: } Chris@18: } Chris@18: } Chris@18: return $fallback; Chris@18: } Chris@18: Chris@18: /** Chris@18: * The "find" command Chris@18: * Chris@18: * Usage: Chris@18: * Chris@18: * System::find($dir); Chris@18: * System::find("$dir -type d"); Chris@18: * System::find("$dir -type f"); Chris@18: * System::find("$dir -name *.php"); Chris@18: * System::find("$dir -name *.php -name *.htm*"); Chris@18: * System::find("$dir -maxdepth 1"); Chris@18: * Chris@18: * Params implemented: Chris@18: * $dir -> Start the search at this directory Chris@18: * -type d -> return only directories Chris@18: * -type f -> return only files Chris@18: * -maxdepth -> max depth of recursion Chris@18: * -name -> search pattern (bash style). Multiple -name param allowed Chris@18: * Chris@18: * @param mixed Either array or string with the command line Chris@18: * @return array Array of found files Chris@18: */ Chris@18: public static function find($args) Chris@18: { Chris@18: if (!is_array($args)) { Chris@18: $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY); Chris@18: } Chris@18: $dir = realpath(array_shift($args)); Chris@18: if (!$dir) { Chris@18: return array(); Chris@18: } Chris@18: $patterns = array(); Chris@18: $depth = 0; Chris@18: $do_files = $do_dirs = true; Chris@18: $args_count = count($args); Chris@18: for ($i = 0; $i < $args_count; $i++) { Chris@18: switch ($args[$i]) { Chris@18: case '-type': Chris@18: if (in_array($args[$i+1], array('d', 'f'))) { Chris@18: if ($args[$i+1] == 'd') { Chris@18: $do_files = false; Chris@18: } else { Chris@18: $do_dirs = false; Chris@18: } Chris@18: } Chris@18: $i++; Chris@18: break; Chris@18: case '-name': Chris@18: $name = preg_quote($args[$i+1], '#'); Chris@18: // our magic characters ? and * have just been escaped, Chris@18: // so now we change the escaped versions to PCRE operators Chris@18: $name = strtr($name, array('\?' => '.', '\*' => '.*')); Chris@18: $patterns[] = '('.$name.')'; Chris@18: $i++; Chris@18: break; Chris@18: case '-maxdepth': Chris@18: $depth = $args[$i+1]; Chris@18: break; Chris@18: } Chris@18: } Chris@18: $path = System::_dirToStruct($dir, $depth, 0, true); Chris@18: if ($do_files && $do_dirs) { Chris@18: $files = array_merge($path['files'], $path['dirs']); Chris@18: } elseif ($do_dirs) { Chris@18: $files = $path['dirs']; Chris@18: } else { Chris@18: $files = $path['files']; Chris@18: } Chris@18: if (count($patterns)) { Chris@18: $dsq = preg_quote(DIRECTORY_SEPARATOR, '#'); Chris@18: $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#'; Chris@18: $ret = array(); Chris@18: $files_count = count($files); Chris@18: for ($i = 0; $i < $files_count; $i++) { Chris@18: // only search in the part of the file below the current directory Chris@18: $filepart = basename($files[$i]); Chris@18: if (preg_match($pattern, $filepart)) { Chris@18: $ret[] = $files[$i]; Chris@18: } Chris@18: } Chris@18: return $ret; Chris@18: } Chris@18: return $files; Chris@18: } Chris@18: }