* @copyright 1997-2005 The PHP Group * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: Archive.php,v 1.85 2005/08/16 08:48:59 vincentlascaux Exp $ * @link http://pear.php.net/package/File_Archive */ /** * To have access to PEAR::isError and PEAR::raiseError * We should probably use lazy include and remove this inclusion... */ require_once "PEAR.php"; function File_Archive_cleanCache($file, $group) { $file = split('_', $file); if (count($file) != 3) { return false; //not a File_Archive file, keep it } $name = $file[2]; $name = urldecode($name); $group = $file[1]; //clean the cache only for files in File_Archive groups return substr($group, 0, 11) == 'FileArchive' && !file_exists($name); //and only if the related file no longer exists } /** * Factory to access the most common File_Archive features * It uses lazy include, so you dont have to include the files from * File/Archive/* directories */ class File_Archive { function& _option($name) { static $container = array( 'zipCompressionLevel' => 9, 'gzCompressionLevel' => 9, 'tmpDirectory' => '.', 'cache' => null, 'appendRemoveDuplicates' => false, 'blockSize' => 65536, 'cacheCondition' => false ); return $container[$name]; } /** * Sets an option that will be used by default by all readers or writers * Option names are case sensitive * Currently, the following options are used: * * "cache" * Instance of a Cache_Lite object used to cache some compressed * data to speed up future compressions of files * Default: null (no cache used) * * "zipCompressionLevel" * Value between 0 and 9 specifying the default compression level used * by Zip writers (0 no compression, 9 highest compression) * Default: 9 * * "gzCompressionLevel" * Value between 0 and 9 specifying the default compression level used * by Gz writers (0 no compression, 9 highest compression) * Default: 9 * * "tmpDirectory" * Directory where the temporary files generated by File_Archive will * be created * Default: '.' * * "appendRemoveDuplicates" * If set to true, the appender created will by default remove the * file present in the archive when adding a new one. This will slow the * appending of files to archives * Default: false * * "blockSize" * To transfer data from a reader to a writer, some chunks a read from the * source and written to the writer. This parameter controls the size of the * chunks * Default: 64kB * * "cacheCondition" * This parameter specifies when a cache should be used. When the cache is * used, the data of the reader is saved in a temporary file for future access. * The cached reader will be read only once, even if you read it several times. * This can be usefull to read compressed files or downloaded files (from http or ftp) * The possible values for this option are * - false: never use cache * - a regexp: A cache will be used if the specified URL matches the regexp * preg_match is used * Default: false * Example: '/^(http|ftp):\/\//' will cache all files downloaded via http or ftp * */ function setOption($name, $value) { $option =& File_Archive::_option($name); $option = $value; if ($name == 'cache' && $value !== null) { //TODO: ask to Cache_Lite to allow that $value->_fileNameProtection = false; } } /** * Retrieve the value of an option */ function getOption($name) { return File_Archive::_option($name); } /** * Create a reader to read the URL $URL. * If the URL is a directory, it will recursively read that directory. * If $uncompressionLevel is not null, the archives (files with extension * tar, zip, gz or tgz) will be considered as directories (up to a depth of * $uncompressionLevel if $uncompressionLevel > 0). The reader will only * read files with a directory depth of $directoryDepth. It reader will * replace the given URL ($URL) with $symbolic in the public filenames * The default symbolic name is the last filename in the URL (or '' for * directories) * * Examples: * Considere the following file system *
     * a.txt
     * b.tar (archive that contains the following files)
     *     c.txt
     *     d.tgz (archive that contains the following files)
     *         e.txt
     *         dir1/
     *             f.txt
     * dir2/
     *     g.txt
     *     dir3/
     *         h.tar (archive that contains the following files)
     *             i.txt
     * 
* * read('.') will return a reader that gives access to following * files (recursively read current dir): *
     * a.txt
     * b.tar
     * dir2/g.txt
     * dir2/dir3/h.tar
     * 
* * read('.', 'myBaseDir') will return the following reader: *
     * myBaseDir/a.txt
     * myBaseDir/b.tar
     * myBaseDir/dir2/g.txt
     * myBaseDir/dir2/dir3/h.tar
     * 
* * read('.', '', -1) will return the following reader (uncompress * everything) *
     * a.txt
     * b.tar/c.txt
     * b.tar/d.tgz/e.txt
     * b.tar/d.tgz/dir1/f.txt
     * dir2/g.txt
     * dir2/dir3/h.tar/i.txt
     * 
* * read('.', '', 1) will uncompress only one level (so d.tgz will * not be uncompressed): *
     * a.txt
     * b.tar/c.txt
     * b.tar/d.tgz
     * dir2/g.txt
     * dir2/dir3/h.tar/i.txt
     * 
* * read('.', '', 0, 0) will not recurse into subdirectories *
     * a.txt
     * b.tar
     * 
* * read('.', '', 0, 1) will recurse only one level in * subdirectories *
     * a.txt
     * b.tar
     * dir2/g.txt
     * 
* * read('.', '', -1, 2) will uncompress everything and recurse in * only 2 levels in subdirectories or archives *
     * a.txt
     * b.tar/c.txt
     * b.tar/d.tgz/e.txt
     * dir2/g.txt
     * 
* * The recursion level is determined by the real path, not the symbolic one. * So read('.', 'myBaseDir', -1, 2) will result to the same files: *
     * myBaseDir/a.txt
     * myBaseDir/b.tar/c.txt
     * myBaseDir/b.tar/d.tgz/e.txt (accepted because the real depth is 2)
     * myBaseDir/dir2/g.txt
     * 
* * Use readSource to do the same thing, reading from a specified reader instead of * reading from the system files * * To read a single file, you can do read('a.txt', 'public_name.txt') * If no public name is provided, the default one is the name of the file * read('dir2/g.txt') contains the single file named 'g.txt' * read('b.tar/c.txt') contains the single file named 'c.txt' * * Note: This function uncompress files reading their extension * The compressed files must have a tar, zip, gz or tgz extension * Since it is impossible for some URLs to use is_dir or is_file, this * function may not work with * URLs containing folders which name ends with such an extension */ function readSource(&$source, $URL, $symbolic = null, $uncompression = 0, $directoryDepth = -1) { return File_Archive::_readSource($source, $URL, $reachable, $baseDir, $symbolic, $uncompression, $directoryDepth); } /** * This function performs exactly as readSource, but with two additional parameters * ($reachable and $baseDir) that will be set so that $reachable."/".$baseDir == $URL * and $reachable can be reached (in case of error) * * @access private */ function _readSource(&$toConvert, $URL, &$reachable, &$baseDir, $symbolic = null, $uncompression = 0, $directoryDepth = -1) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } if (is_array($URL)) { $converted = array(); foreach($URL as $key => $foo) { $converted[] =& File_Archive::_convertToReader($URL[$key]); } return File_Archive::readMulti($converted); } //No need to uncompress more than $directoryDepth //That's not perfect, and some archives will still be uncompressed just //to be filtered out :( if ($directoryDepth >= 0) { $uncompressionLevel = min($uncompression, $directoryDepth); } else { $uncompressionLevel = $uncompression; } require_once 'File/Archive/Reader.php'; $std = File_Archive_Reader::getStandardURL($URL); //Modify the symbolic name if necessary $slashPos = strrpos($std, '/'); if ($symbolic === null) { if ($slashPos === false) { $realSymbolic = $std; } else { $realSymbolic = substr($std, $slashPos+1); } } else { $realSymbolic = $symbolic; } if ($slashPos !== false) { $baseFile = substr($std, 0, $slashPos+1); $lastFile = substr($std, $slashPos+1); } else { $baseFile = ''; $lastFile = $std; } if (strpos($lastFile, '*')!==false || strpos($lastFile, '?')!==false) { //We have to build a regexp here $regexp = str_replace( array('\*', '\?'), array('[^/]*', '[^/]'), preg_quote($lastFile) ); $result = File_Archive::_readSource($source, $baseFile, $reachable, $baseDir, null, 0, -1); return File_Archive::filter( File_Archive::predEreg('^'.$regexp.'$'), $result ); } //If the URL can be interpreted as a directory, and we are reading from the file system if ((empty($URL) || is_dir($URL)) && $source === null) { require_once "File/Archive/Reader/Directory.php"; require_once "File/Archive/Reader/ChangeName.php"; if ($uncompressionLevel != 0) { require_once "File/Archive/Reader/Uncompress.php"; $result = new File_Archive_Reader_Uncompress( new File_Archive_Reader_Directory($std, '', $directoryDepth), $uncompressionLevel ); } else { $result = new File_Archive_Reader_Directory($std, '', $directoryDepth); } if ($directoryDepth >= 0) { require_once 'File/Archive/Reader/Filter.php'; require_once 'File/Archive/Predicate/MaxDepth.php'; $tmp =& File_Archive::filter( new File_Archive_Predicate_MaxDepth($directoryDepth), $result ); unset($result); $result =& $tmp; } if (!empty($realSymbolic)) { if ($symbolic === null) { $realSymbolic = ''; } $tmp =& new File_Archive_Reader_AddBaseName( $realSymbolic, $result ); unset($result); $result =& $tmp; } //If the URL can be interpreted as a file, and we are reading from the file system } else if (is_file($URL) && substr($URL, -1)!='/' && $source === null) { require_once "File/Archive/Reader/File.php"; $result = new File_Archive_Reader_File($URL, $realSymbolic); //Else, we will have to build a complex reader } else { require_once "File/Archive/Reader/File.php"; $realPath = $std; // Try to find a file with a known extension in the path ( // (to manage URLs like archive.tar/directory/file) $pos = 0; do { if ($pos+1setBaseDir($std); if (PEAR::isError($isDir)) { return $isDir; } if ($isDir && $symbolic==null) { //Default symbolic name for directories is empty $realSymbolic = ''; } if ($directoryDepth >= 0) { //Limit the maximum depth if necessary require_once "File/Archive/Predicate/MaxDepth.php"; $tmp = new File_Archive_Reader_Filter( new File_Archive_Predicate( $directoryDepth + substr_count(substr($std, $pos+1), '/') ), $result ); unset($result); $result =& $tmp; } if ($std != $realSymbolic) { require_once "File/Archive/Reader/ChangeName.php"; //Change the base name to the symbolic one if necessary $tmp = new File_Archive_Reader_ChangeBaseName( $std, $realSymbolic, $result ); unset($result); $result =& $tmp; } } $cacheCondition = File_Archive::getOption('cacheCondition'); if ($cacheCondition !== false && preg_match($cacheCondition, $URL)) { $tmp =& File_Archive::cache($result); unset($result); $result =& $tmp; } return $result; } function read($URL, $symbolic = null, $uncompression = 0, $directoryDepth = -1) { $source = null; return File_Archive::readSource($source, $URL, $symbolic, $uncompression, $directoryDepth); } /** * Create a file reader on an uploaded file. The reader will read * $_FILES[$name]['tmp_name'] and will have $_FILES[$name]['name'] * as a symbolic filename. * * A PEAR error is returned if one of the following happen * - $_FILES[$name] is not set * - $_FILES[$name]['error'] is not 0 * - is_uploaded_file returns false * * @param string $name Index of the file in the $_FILES array * @return File_Archive_Reader File reader on the uploaded file */ function readUploadedFile($name) { if (!isset($_FILES[$name])) { return PEAR::raiseError("File $name has not been uploaded"); } switch ($_FILES[$name]['error']) { case 0: //No error break; case 1: return PEAR::raiseError( "The upload size limit didn't allow to upload file ". $_FILES[$name]['name'] ); case 2: return PEAR::raiseError( "The form size limit didn't allow to upload file ". $_FILES[$name]['name'] ); case 3: return PEAR::raiseError( "The file was not entirely uploaded" ); case 4: return PEAR::raiseError( "The uploaded file is empty" ); default: return PEAR::raiseError( "Unknown error ".$_FILES[$name]['error']." in file upload. ". "Please, report a bug" ); } if (!is_uploaded_file($_FILES[$name]['tmp_name'])) { return PEAR::raiseError("The file is not an uploaded file"); } require_once "File/Archive/Reader/File.php"; return new File_Archive_Reader_File( $_FILES[$name]['tmp_name'], $_FILES[$name]['name'], $_FILES[$name]['type'] ); } /** * Adds a cache layer above the specified reader * The data of the reader is saved in a temporary file for future access. * The cached reader will be read only once, even if you read it several times. * This can be usefull to read compressed files or downloaded files (from http or ftp) * * @param mixed $toConvert The reader to cache * It can be a File_Archive_Reader or a string, which will be converted using the * read function */ function cache(&$toConvert) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } require_once 'File/Archive/Reader/Cache.php'; return new File_Archive_Reader_Cache($source); } /** * Try to interpret the object as a reader * Strings are converted to readers using File_Archive::read * Arrays are converted to readers using File_Archive::readMulti * * @access private */ function &_convertToReader(&$source) { if (is_string($source)) { $cacheCondition = File_Archive::getOption('cacheCondition'); if ($cacheCondition !== false && preg_match($cacheCondition, $source)) { return File_Archive::cache(File_Archive::read($source)); } else { return File_Archive::read($source); } } else if (is_array($source)) { return File_Archive::readMulti($source); } else { return $source; } } /** * Try to interpret the object as a writer * Strings are converted to writers using File_Archive::appender * Arrays are converted to writers using a multi writer * * @access private */ function &_convertToWriter(&$dest) { if (is_string($dest)) { return File_Archive::appender($dest); } else if (is_array($dest)) { require_once 'File/Archive/Writer/Multi.php'; $writer = new File_Archive_Writer_Multi(); foreach($dest as $key => $foo) { $writer->addWriter($dest[$key]); } } else { return $dest; } } /** * Check if a file with a specific extension can be read as an archive * with File_Archive::read* * This function is case sensitive. * * @param string $extension the checked extension * @return bool whether this file can be understood reading its extension * Currently, supported extensions are tar, zip, gz, tgz, tbz, bz2, * bzip2, ar, deb */ function isKnownExtension($extension) { return $extension == 'tar' || $extension == 'zip' || $extension == 'gz' || $extension == 'tgz' || $extension == 'tbz' || $extension == 'bz2' || $extension == 'bzip2' || $extension == 'ar' || $extension == 'deb' /* || $extension == 'cab' || $extension == 'rar' */; } /** * Create a reader that will read the single file source $source as * a specific archive * * @param string $extension determines the kind of archive $source contains * $extension is case sensitive * @param File_Archive_Reader $source stores the archive * @param bool $sourceOpened specifies if the archive is already opened * if false, next will be called on source * Closing the returned archive will close $source iif $sourceOpened * is true * @return A File_Archive_Reader that uncompresses the archive contained in * $source interpreting it as a $extension archive * If $extension is not handled return false */ function readArchive($extension, &$toConvert, $sourceOpened = false) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } switch($extension) { case 'tgz': return File_Archive::readArchive('tar', File_Archive::readArchive('gz', $source, $sourceOpened) ); case 'tbz': return File_Archive::readArchive('tar', File_Archive::readArchive('bz2', $source, $sourceOpened) ); case 'tar': require_once 'File/Archive/Reader/Tar.php'; return new File_Archive_Reader_Tar($source, $sourceOpened); case 'gz': case 'gzip': require_once 'File/Archive/Reader/Gzip.php'; return new File_Archive_Reader_Gzip($source, $sourceOpened); case 'zip': require_once 'File/Archive/Reader/Zip.php'; return new File_Archive_Reader_Zip($source, $sourceOpened); case 'bz2': case 'bzip2': require_once 'File/Archive/Reader/Bzip2.php'; return new File_Archive_Reader_Bzip2($source, $sourceOpened); case 'deb': case 'ar': require_once 'File/Archive/Reader/Ar.php'; return new File_Archive_Reader_Ar($source, $sourceOpened); /* case 'cab': require_once 'File/Archive/Reader/Cab.php'; return new File_Archive_Reader_Cab($source, $sourceOpened); case 'rar': require_once "File/Archive/Reader/Rar.php"; return new File_Archive_Reader_Rar($source, $sourceOpened); */ default: return false; } } /** * Contains only one file with data read from a memory buffer * * @param string $memory content of the file * @param string $filename public name of the file * @param array $stat statistics of the file. Index 7 (size) will be * overwritten to match the size of $memory * @param string $mime mime type of the file. Default will determine the * mime type thanks to the extension of $filename * @see File_Archive_Reader_Memory */ function readMemory($memory, $filename, $stat=array(), $mime=null) { require_once "File/Archive/Reader/Memory.php"; return new File_Archive_Reader_Memory($memory, $filename, $stat, $mime); } /** * Contains several other sources. Take care the sources don't have several * files with the same filename. The sources are given as a parameter, or * can be added thanks to the reader addSource method * * @param array $sources Array of strings or readers that will be added to * the multi reader. If the parameter is a string, a reader will be * built thanks to the read function * @see File_Archive_Reader_Multi, File_Archive::read() */ function readMulti($sources = array()) { require_once "File/Archive/Reader/Multi.php"; $result = new File_Archive_Reader_Multi(); foreach ($sources as $index => $foo) { $s =& File_Archive::_convertToReader($sources[$index]); if (PEAR::isError($s)) { return $s; } else { $result->addSource($s); } } return $result; } /** * Make the files of a source appear as one large file whose content is the * concatenation of the content of all the files * * @param File_Archive_Reader $source The source whose files must be * concatened * @param string $filename name of the only file of the created reader * @param array $stat statistics of the file. Index 7 (size) will be * overwritten to match the total size of the files * @param string $mime mime type of the file. Default will determine the * mime type thanks to the extension of $filename * @see File_Archive_Reader_Concat */ function readConcat(&$toConvert, $filename, $stat=array(), $mime=null) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } require_once "File/Archive/Reader/Concat.php"; return new File_Archive_Reader_Concat($source, $filename, $stat, $mime); } /** * Removes from a source the files that do not follow a given predicat * * @param File_Archive_Predicate $predicate Only the files for which * $predicate->isTrue() will be kept * @param File_Archive_Reader $source Source that will be filtered * @see File_Archive_Reader_Filter */ function filter($predicate, &$toConvert) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } require_once "File/Archive/Reader/Filter.php"; return new File_Archive_Reader_Filter($predicate, $source); } /** * Predicate that always evaluate to true * * @see File_Archive_Predicate_True */ function predTrue() { require_once "File/Archive/Predicate/True.php"; return new File_Archive_Predicate_True(); } /** * Predicate that always evaluate to false * * @see File_Archive_Predicate_False */ function predFalse() { require_once "File/Archive/Predicate/False.php"; return new File_Archive_Predicate_False(); } /** * Predicate that evaluates to the logical AND of the parameters * You can add other predicates thanks to the * File_Archive_Predicate_And::addPredicate() function * * @param File_Archive_Predicate (any number of them) * @see File_Archive_Predicate_And */ function predAnd() { require_once "File/Archive/Predicate/And.php"; $pred = new File_Archive_Predicate_And(); $args = func_get_args(); foreach ($args as $p) { $pred->addPredicate($p); } return $pred; } /** * Predicate that evaluates to the logical OR of the parameters * You can add other predicates thanks to the * File_Archive_Predicate_Or::addPredicate() function * * @param File_Archive_Predicate (any number of them) * @see File_Archive_Predicate_Or */ function predOr() { require_once "File/Archive/Predicate/Or.php"; $pred = new File_Archive_Predicate_Or(); $args = func_get_args(); foreach ($args as $p) { $pred->addPredicate($p); } return $pred; } /** * Negate a predicate * * @param File_Archive_Predicate $pred Predicate to negate * @see File_Archive_Predicate_Not */ function predNot($pred) { require_once "File/Archive/Predicate/Not.php"; return new File_Archive_Predicate_Not($pred); } /** * Evaluates to true iif the file is larger than a given size * * @param int $size the minimal size of the files (in Bytes) * @see File_Archive_Predicate_MinSize */ function predMinSize($size) { require_once "File/Archive/Predicate/MinSize.php"; return new File_Archive_Predicate_MinSize($size); } /** * Evaluates to true iif the file has been modified after a given time * * @param int $time Unix timestamp of the minimal modification time of the * files * @see File_Archive_Predicate_MinTime */ function predMinTime($time) { require_once "File/Archive/Predicate/MinTime.php"; return new File_Archive_Predicate_MinTime($time); } /** * Evaluates to true iif the file has less that a given number of * directories in its path * * @param int $depth Maximal number of directories in path of the files * @see File_Archive_Predicate_MaxDepth */ function predMaxDepth($depth) { require_once "File/Archive/Predicate/MaxDepth.php"; return new File_Archive_Predicate_MaxDepth($depth); } /** * Evaluates to true iif the extension of the file is in a given list * * @param array or string $list List or comma separated string of possible * extension of the files * @see File_Archive_Predicate_Extension */ function predExtension($list) { require_once "File/Archive/Predicate/Extension.php"; return new File_Archive_Predicate_Extension($list); } /** * Evaluates to true iif the MIME type of the file is in a given list * * @param array or string $list List or comma separated string of possible * MIME types of the files. You may enter wildcards like "image/*" to * select all the MIME in class image * @see File_Archive_Predicate_MIME, MIME_Type::isWildcard() */ function predMIME($list) { require_once "File/Archive/Predicate/MIME.php"; return new File_Archive_Predicate_MIME($list); } /** * Evaluates to true iif the name of the file follow a given regular * expression * * @param string $ereg regular expression that the filename must follow * @see File_Archive_Predicate_Ereg, ereg() */ function predEreg($ereg) { require_once "File/Archive/Predicate/Ereg.php"; return new File_Archive_Predicate_Ereg($ereg); } /** * Evaluates to true iif the name of the file follow a given regular * expression (case insensitive version) * * @param string $ereg regular expression that the filename must follow * @see File_Archive_Predicate_Eregi, eregi */ function predEregi($ereg) { require_once "File/Archive/Predicate/Eregi.php"; return new File_Archive_Predicate_Eregi($ereg); } /** * Evaluates to true only after a given number of evaluations * This can be used to select files by index since the evaluation is done * once per file * * @param array The indexes for which the returned predicate will return true * are the keys of the array * The predicate will return true if isset($indexes[$pos]) */ function predIndex($indexes) { require_once "File/Archive/Predicate/Index.php"; return new File_Archive_Predicate_Index($indexes); } /** * Custom predicate built by supplying a string expression * * Here are different ways to create a predicate that keeps only files * with names shorter than 100 chars * * File_Archive::predCustom("return strlen($name)<100;") * File_Archive::predCustom("strlen($name)<100;") * File_Archive::predCustom("strlen($name)<100") * File_Archive::predCustom("strlen($source->getFilename())<100") * * * @param string $expression String containing an expression that evaluates * to a boolean. If the expression doesn't contain a return * statement, it will be added at the begining of the expression * A ';' will be added at the end of the expression so that you don't * have to write it. You may use the $name variable to refer to the * current filename (with path...), $time for the modification time * (unix timestamp), $size for the size of the file in bytes, $mime * for the MIME type of the file * @see File_Archive_Predicate_Custom */ function predCustom($expression) { require_once "File/Archive/Predicate/Custom.php"; return new File_Archive_Predicate_Custom($expression); } /** * Send the files as a mail attachment * * @param Mail $mail Object used to send mail (see Mail::factory) * @param array or String $to An array or a string with comma separated * recipients * @param array $headers The headers that will be passed to the Mail_mime * object * @param string $message Text body of the mail * @see File_Archive_Writer_Mail */ function toMail($to, $headers, $message, $mail = null) { require_once "File/Archive/Writer/Mail.php"; return new File_Archive_Writer_Mail($to, $headers, $message, $mail); } /** * Write the files on the hard drive * * @param string $baseDir if specified, the files will be created in that * directory. If they don't exist, the directories will automatically * be created * @see File_Archive_Writer_Files */ function toFiles($baseDir = "") { require_once "File/Archive/Writer/Files.php"; return new File_Archive_Writer_Files($baseDir); } /** * Send the content of the files to a memory buffer * * toMemory returns a writer where the data will be written. * In this case, the data is accessible using the getData member * * toVariable returns a writer that will write into the given * variable * * @param out $data if specified, the data will be written to this buffer * Else, you can retrieve the buffer with the * File_Archive_Writer_Memory::getData() function * @see File_Archive_Writer_Memory */ function toMemory() { $v = ''; return File_Archive::toVariable($v); } function toVariable(&$v) { require_once "File/Archive/Writer/Memory.php"; return new File_Archive_Writer_Memory($v); } /** * Duplicate the writing operation on two writers * * @param File_Archive_Writer $a, $b writers where data will be duplicated * @see File_Archive_Writer_Multi */ function toMulti(&$aC, &$bC) { $a =& File_Archive::_convertToWriter($aC); $b =& File_Archive::_convertToWriter($bC); if (PEAR::isError($a)) { return $a; } if (PEAR::isError($b)) { return $b; } require_once "File/Archive/Writer/Multi.php"; $writer = new File_Archive_Writer_Multi(); $writer->addWriter($a); $writer->addWriter($b); return $writer; } /** * Send the content of the files to the standard output (so to the client * for a website) * * @param bool $sendHeaders If true some headers will be sent to force the * download of the file. Default value is true * @see File_Archive_Writer_Output */ function toOutput($sendHeaders = true) { require_once "File/Archive/Writer/Output.php"; return new File_Archive_Writer_Output($sendHeaders); } /** * Compress the data to a tar, gz, tar/gz or zip format * * @param string $filename name of the archive file * @param File_Archive_Writer $innerWriter writer where the archive will be * written * @param string $type can be one of tgz, tbz, tar, zip, gz, gzip, bz2, * bzip2 (default is the extension of $filename) or any composition * of them (for example tar.gz or tar.bz2). The case of this * parameter is not important. * @param array $stat Statistics of the archive (see stat function) * @param bool $autoClose If set to true, $innerWriter will be closed when * the returned archive is close. Default value is true. */ function toArchive($filename, &$toConvert, $type = null, $stat = array(), $autoClose = true) { $innerWriter =& File_Archive::_convertToWriter($toConvert); if (PEAR::isError($innerWriter)) { return $innerWriter; } $shortcuts = array("tgz" , "tbz" ); $reals = array("tar.gz", "tar.bz2"); if ($type === null) { $extensions = strtolower($filename); } else { $extensions = strtolower($type); } $extensions = explode('.', str_replace($shortcuts, $reals, $extensions)); if ($innerWriter !== null) { $writer =& $innerWriter; } else { $writer = File_Archive::toFiles(); } $nbCompressions = 0; $currentFilename = $filename; while (($extension = array_pop($extensions)) !== null) { unset($next); switch($extension) { case "tar": require_once "File/Archive/Writer/Tar.php"; $next = new File_Archive_Writer_Tar( $currentFilename, $writer, $stat, $autoClose ); unset($writer); $writer =& $next; break; case "zip": require_once "File/Archive/Writer/Zip.php"; $next = new File_Archive_Writer_Zip( $currentFilename, $writer, $stat, $autoClose ); unset($writer); $writer =& $next; break; case "gz": case "gzip": require_once "File/Archive/Writer/Gzip.php"; $next = new File_Archive_Writer_Gzip( $currentFilename, $writer, $stat, $autoClose ); unset($writer); $writer =& $next; break; case "bz2": case "bzip2": require_once "File/Archive/Writer/Bzip2.php"; $next = new File_Archive_Writer_Bzip2( $currentFilename, $writer, $stat, $autoClose ); unset($writer); $writer =& $next; break; case "deb": case "ar": require_once "File/Archive/Writer/Ar.php"; $next = new File_Archive_Writer_Ar( $currentFilename, $writer, $stat, $autoClose ); unset($writer); $writer =& $next; break; default: if ($type !== null || $nbCompressions == 0) { return PEAR::raiseError("Archive $extension unknown"); } break; } $nbCompressions ++; $autoClose = true; $currentFilename = implode(".", $extensions); } return $writer; } /** * File_Archive::extract($source, $dest) is equivalent to $source->extract($dest) * If $source is a PEAR error, the error will be returned * It is thus easier to use this function than $source->extract, since it reduces the number of * error checking and doesn't force you to define a variable $source * * You may use strings as source and dest. In that case the source is automatically * converted to a reader using File_Archive::read and the dest is converted to a * writer using File_Archive::appender * Since PHP doesn't allow to pass literal strings by ref, you will have to use temporary * variables. * File_Archive::extract($src = 'archive.zip/', $dest = 'dir') will extract the archive to 'dir' * It is the same as * File_Archive::extract( * File_Archive::read('archive.zip/'), * File_Archive::appender('dir') * ); * You may use any variable in the extract function ($from/$to, $a/$b...). * * @param File_Archive_Reader $source The source that will be read * @param File_Archive_Writer $dest Where to copy $source files * @param bool $autoClose if true (default), $dest will be closed after the extraction * @param int $bufferSize Size of the buffer to use to move data from the reader to the buffer * If $bufferSize <= 0 (default), the blockSize option is used * You shouldn't need to change that * @return null or a PEAR error if an error occured */ function extract(&$sourceToConvert, &$destToConvert, $autoClose = true, $bufferSize = 0) { $source =& File_Archive::_convertToReader($sourceToConvert); if (PEAR::isError($source)) { return $source; } $dest =& File_Archive::_convertToWriter($destToConvert); return $source->extract($dest, $autoClose, $bufferSize); } /** * Create a writer that can be used to append files to an archive inside a source * If the archive can't be found in the source, it will be created * If source is set to null, File_Archive::toFiles will be assumed * If type is set to null, the type of the archive will be determined looking at * the extension in the URL * stat is the array of stat (returned by stat() PHP function of Reader getStat()) * to use if the archive must be created * * This function allows to create or append data to nested archives. Only one * archive will be created and if your creation requires creating several nested * archives, a PEAR error will be returned * * After this call, $source will be closed and should not be used until the * returned writer is closed. * * @param File_Archive_Reader $source A reader where some files will be appended * @param string $URL URL to reach the archive in the source. * if $URL is null, a writer to append files to the $source reader will * be returned * @param bool $unique If true, the duplicate files will be deleted on close * Default is false (and setting it to true may have some performance * consequences) * @param string $type Extension of the archive (or null to use the one in the URL) * @param array $stat Used only if archive is created, array of stat as returned * by PHP stat function or Reader getStat function: stats of the archive) * Time (index 9) will be overwritten to current time * @return File_Archive_Writer a writer that you can use to append files to the reader */ function appenderFromSource(&$toConvert, $URL = null, $unique = null, $type = null, $stat = array()) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } if ($unique == null) { $unique = File_Archive::getOption("appendRemoveDuplicates"); } //Do not report the fact that the archive does not exist as an error PEAR::pushErrorHandling(PEAR_ERROR_RETURN); if ($URL === null) { $result =& $source; } else { if ($type === null) { $result = File_Archive::_readSource($source, $URL.'/', $reachable, $baseDir); } else { $result = File_Archive::readArchive( $type, File_Archive::_readSource($source, $URL, $reachable, $baseDir) ); } } PEAR::popErrorHandling(); if (!PEAR::isError($result)) { if ($unique) { require_once "File/Archive/Writer/UniqueAppender.php"; return new File_Archive_Writer_UniqueAppender($result); } else { return $result->makeAppendWriter(); } } //The source can't be found and has to be created $stat[9] = $stat['mtime'] = time(); if (empty($baseDir)) { if ($source !== null) { $writer =& $source->makeWriter(); } else { $writer =& File_Archive::toFiles(); } if (PEAR::isError($writer)) { return $writer; } PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $result = File_Archive::toArchive($reachable, $writer, $type); PEAR::popErrorHandling(); if (PEAR::isError($result)) { $result = File_Archive::toFiles($reachable); } } else { $reachedSource = File_Archive::readSource($source, $reachable); if (PEAR::isError($reachedSource)) { return $reachedSource; } $writer = $reachedSource->makeWriter(); if (PEAR::isError($writer)) { return $writer; } PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $result = File_Archive::toArchive($baseDir, $writer, $type); PEAR::popErrorHandling(); if (PEAR::isError($result)) { require_once "File/Archive/Writer/AddBaseName.php"; $result = new File_Archive_Writer_AddBaseName( $baseDir, $writer); if (PEAR::isError($result)) { return $result; } } } return $result; } /** * Create a writer that allows appending new files to an existing archive * This function actes as appendToSource with source being the system files * $URL can't be null here * * @param File_Archive_Reader $source A reader where some files will be appended * @return File_Archive_Writer a writer that you can use to append files to the reader */ function appender($URL, $unique = null, $type = null, $stat = array()) { $source = null; return File_Archive::appenderFromSource($source, $URL, $unique, $type, $stat); } /** * Remove the files that follow a given predicate from the source * If URL is null, the files will be removed from the source directly * Else, URL must link to a source from which the files will be removed * * @param File_Archive_Predicate $pred The files that follow the predicate * (for which $pred->isTrue($source) is true) will be erased * @param File_Archive_Reader $source A reader that contains the files to remove */ function removeFromSource(&$pred, &$toConvert, $URL = null) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } if ($URL === null) { $result = &$source; } else { if (substr($URL, -1) !== '/') { $URL .= '/'; } $result = File_Archive::readSource($source, $URL); } $writer = $result->makeWriterRemoveFiles($pred); if (PEAR::isError($writer)) { return $writer; } $writer->close(); } /** * Remove the files that follow a given predicate from the archive specified * in $URL * * @param $URL URL of the archive where some files must be removed */ function remove($pred, $URL) { $source = null; return File_Archive::removeFromSource($pred, $source, $URL); } /** * Remove duplicates from a source, keeping the most recent one (or the one that has highest pos in * the archive if the files have same date or no date specified) * * @param File_Archive_Reader a reader that may contain duplicates */ function removeDuplicatesFromSource(&$toConvert, $URL = null) { $source =& File_Archive::_convertToReader($toConvert); if (PEAR::isError($source)) { return $source; } if ($URL !== null && substr($URL, -1) != '/') { $URL .= '/'; } if ($source === null) { $source = File_Archive::read($URL); } require_once "File/Archive/Predicate/Duplicate.php"; $pred = new File_Archive_Predicate_Duplicate($source); $source->close(); return File_Archive::removeFromSource( $pred, $source, null ); } /** * Remove duplicates from the archive specified in the URL */ function removeDuplicates($URL) { $source = null; return File_Archive::removeDuplicatesFromSource($source, $URL); } } ?>