1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5 * Factory to access the most common File_Archive features 6 * It uses lazy include, so you dont have to include the files from 7 * File/Archive/* directories 8 * 9 * PHP versions 4 and 5 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Lesser General Public 13 * License as published by the Free Software Foundation; either 14 * version 2.1 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Lesser General Public License for more details. 20 * 21 * You should have received a copy of the GNU Lesser General Public 22 * License along with this library; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA 24 * 25 * @category File Formats 26 * @package File_Archive 27 * @author Vincent Lascaux <vincentlascaux@php.net> 28 * @copyright 1997-2005 The PHP Group 29 * @license http://www.gnu.org/copyleft/lesser.html LGPL 30 * @version CVS: $Id$ 31 * @link http://pear.php.net/package/File_Archive 32 */ 33 34/** 35 * To have access to PEAR::isError and PEAR::raiseError 36 * We should probably use lazy include and remove this inclusion... 37 */ 38require_once "PEAR.php"; 39 40function File_Archive_cleanCache($file, $group) 41{ 42 $file = explode('_', $file); 43 if (count($file) != 3) { 44 return false; //not a File_Archive file, keep it 45 } 46 47 $name = $file[2]; 48 $name = urldecode($name); 49 50 $group = $file[1]; 51 52 //clean the cache only for files in File_Archive groups 53 return substr($group, 0, 11) == 'FileArchive' && 54 !file_exists($name); //and only if the related file no longer exists 55} 56 57/** 58 * Factory to access the most common File_Archive features 59 * It uses lazy include, so you dont have to include the files from 60 * File/Archive/* directories 61 */ 62class File_Archive 63{ 64 function& _option($name) 65 { 66 static $container = array( 67 'zipCompressionLevel' => 9, 68 'gzCompressionLevel' => 9, 69 'tmpDirectory' => '.', 70 'cache' => null, 71 'appendRemoveDuplicates' => false, 72 'blockSize' => 65536, 73 'cacheCondition' => false 74 ); 75 return $container[$name]; 76 } 77 /** 78 * Sets an option that will be used by default by all readers or writers 79 * Option names are case sensitive 80 * Currently, the following options are used: 81 * 82 * "cache" 83 * Instance of a Cache_Lite object used to cache some compressed 84 * data to speed up future compressions of files 85 * Default: null (no cache used) 86 * 87 * "zipCompressionLevel" 88 * Value between 0 and 9 specifying the default compression level used 89 * by Zip writers (0 no compression, 9 highest compression) 90 * Default: 9 91 * 92 * "gzCompressionLevel" 93 * Value between 0 and 9 specifying the default compression level used 94 * by Gz writers (0 no compression, 9 highest compression) 95 * Default: 9 96 * 97 * "tmpDirectory" 98 * Directory where the temporary files generated by File_Archive will 99 * be created 100 * Default: '.' 101 * 102 * "appendRemoveDuplicates" 103 * If set to true, the appender created will by default remove the 104 * file present in the archive when adding a new one. This will slow the 105 * appending of files to archives 106 * Default: false 107 * 108 * "blockSize" 109 * To transfer data from a reader to a writer, some chunks a read from the 110 * source and written to the writer. This parameter controls the size of the 111 * chunks 112 * Default: 64kB 113 * 114 * "cacheCondition" 115 * This parameter specifies when a cache should be used. When the cache is 116 * used, the data of the reader is saved in a temporary file for future access. 117 * The cached reader will be read only once, even if you read it several times. 118 * This can be usefull to read compressed files or downloaded files (from http or ftp) 119 * The possible values for this option are 120 * - false: never use cache 121 * - a regexp: A cache will be used if the specified URL matches the regexp 122 * preg_match is used 123 * Default: false 124 * Example: '/^(http|ftp):\/\//' will cache all files downloaded via http or ftp 125 * 126 */ 127 function setOption($name, $value) 128 { 129 $option =& File_Archive::_option($name); 130 $option = $value; 131 if ($name == 'cache' && $value !== null) { 132 //TODO: ask to Cache_Lite to allow that 133 $value->_fileNameProtection = false; 134 } 135 } 136 137 /** 138 * Retrieve the value of an option 139 */ 140 function getOption($name) 141 { 142 return File_Archive::_option($name); 143 } 144 145 /** 146 * Create a reader to read the URL $URL. 147 * If the URL is a directory, it will recursively read that directory. 148 * If $uncompressionLevel is not null, the archives (files with extension 149 * tar, zip, gz or tgz) will be considered as directories (up to a depth of 150 * $uncompressionLevel if $uncompressionLevel > 0). The reader will only 151 * read files with a directory depth of $directoryDepth. It reader will 152 * replace the given URL ($URL) with $symbolic in the public filenames 153 * The default symbolic name is the last filename in the URL (or '' for 154 * directories) 155 * 156 * Examples: 157 * Considere the following file system 158 * <pre> 159 * a.txt 160 * b.tar (archive that contains the following files) 161 * c.txt 162 * d.tgz (archive that contains the following files) 163 * e.txt 164 * dir1/ 165 * f.txt 166 * dir2/ 167 * g.txt 168 * dir3/ 169 * h.tar (archive that contains the following files) 170 * i.txt 171 * </pre> 172 * 173 * read('.') will return a reader that gives access to following 174 * files (recursively read current dir): 175 * <pre> 176 * a.txt 177 * b.tar 178 * dir2/g.txt 179 * dir2/dir3/h.tar 180 * </pre> 181 * 182 * read('.', 'myBaseDir') will return the following reader: 183 * <pre> 184 * myBaseDir/a.txt 185 * myBaseDir/b.tar 186 * myBaseDir/dir2/g.txt 187 * myBaseDir/dir2/dir3/h.tar 188 * </pre> 189 * 190 * read('.', '', -1) will return the following reader (uncompress 191 * everything) 192 * <pre> 193 * a.txt 194 * b.tar/c.txt 195 * b.tar/d.tgz/e.txt 196 * b.tar/d.tgz/dir1/f.txt 197 * dir2/g.txt 198 * dir2/dir3/h.tar/i.txt 199 * </pre> 200 * 201 * read('.', '', 1) will uncompress only one level (so d.tgz will 202 * not be uncompressed): 203 * <pre> 204 * a.txt 205 * b.tar/c.txt 206 * b.tar/d.tgz 207 * dir2/g.txt 208 * dir2/dir3/h.tar/i.txt 209 * </pre> 210 * 211 * read('.', '', 0, 0) will not recurse into subdirectories 212 * <pre> 213 * a.txt 214 * b.tar 215 * </pre> 216 * 217 * read('.', '', 0, 1) will recurse only one level in 218 * subdirectories 219 * <pre> 220 * a.txt 221 * b.tar 222 * dir2/g.txt 223 * </pre> 224 * 225 * read('.', '', -1, 2) will uncompress everything and recurse in 226 * only 2 levels in subdirectories or archives 227 * <pre> 228 * a.txt 229 * b.tar/c.txt 230 * b.tar/d.tgz/e.txt 231 * dir2/g.txt 232 * </pre> 233 * 234 * The recursion level is determined by the real path, not the symbolic one. 235 * So read('.', 'myBaseDir', -1, 2) will result to the same files: 236 * <pre> 237 * myBaseDir/a.txt 238 * myBaseDir/b.tar/c.txt 239 * myBaseDir/b.tar/d.tgz/e.txt (accepted because the real depth is 2) 240 * myBaseDir/dir2/g.txt 241 * </pre> 242 * 243 * Use readSource to do the same thing, reading from a specified reader instead of 244 * reading from the system files 245 * 246 * To read a single file, you can do read('a.txt', 'public_name.txt') 247 * If no public name is provided, the default one is the name of the file 248 * read('dir2/g.txt') contains the single file named 'g.txt' 249 * read('b.tar/c.txt') contains the single file named 'c.txt' 250 * 251 * Note: This function uncompress files reading their extension 252 * The compressed files must have a tar, zip, gz or tgz extension 253 * Since it is impossible for some URLs to use is_dir or is_file, this 254 * function may not work with 255 * URLs containing folders which name ends with such an extension 256 */ 257 function readSource(&$source, $URL, $symbolic = null, 258 $uncompression = 0, $directoryDepth = -1) 259 { 260 return File_Archive::_readSource($source, $URL, $reachable, $baseDir, 261 $symbolic, $uncompression, $directoryDepth); 262 } 263 264 /** 265 * This function performs exactly as readSource, but with two additional parameters 266 * ($reachable and $baseDir) that will be set so that $reachable."/".$baseDir == $URL 267 * and $reachable can be reached (in case of error) 268 * 269 * @access private 270 */ 271 function _readSource(&$toConvert, $URL, &$reachable, &$baseDir, $symbolic = null, 272 $uncompression = 0, $directoryDepth = -1) 273 { 274 $source =& File_Archive::_convertToReader($toConvert); 275 if (PEAR::isError($source)) { 276 return $source; 277 } 278 if (is_array($URL)) { 279 $converted = array(); 280 foreach($URL as $key => $foo) { 281 $converted[] =& File_Archive::_convertToReader($URL[$key]); 282 } 283 return File_Archive::readMulti($converted); 284 } 285 286 //No need to uncompress more than $directoryDepth 287 //That's not perfect, and some archives will still be uncompressed just 288 //to be filtered out :( 289 if ($directoryDepth >= 0) { 290 $uncompressionLevel = min($uncompression, $directoryDepth); 291 } else { 292 $uncompressionLevel = $uncompression; 293 } 294 295 require_once 'File/Archive/Reader.php'; 296 $std = File_Archive_Reader::getStandardURL($URL); 297 298 //Modify the symbolic name if necessary 299 $slashPos = strrpos($std, '/'); 300 if ($symbolic === null) { 301 if ($slashPos === false) { 302 $realSymbolic = $std; 303 } else { 304 $realSymbolic = substr($std, $slashPos+1); 305 } 306 } else { 307 $realSymbolic = $symbolic; 308 } 309 if ($slashPos !== false) { 310 $baseFile = substr($std, 0, $slashPos+1); 311 $lastFile = substr($std, $slashPos+1); 312 } else { 313 $baseFile = ''; 314 $lastFile = $std; 315 } 316 317 if (strpos($lastFile, '*')!==false || 318 strpos($lastFile, '?')!==false) { 319 //We have to build a regexp here 320 $regexp = str_replace( 321 array('\*', '\?'), 322 array('[^/]*', '[^/]'), 323 preg_quote($lastFile) 324 ); 325 $result = File_Archive::_readSource($source, $baseFile, 326 $reachable, $baseDir, null, 0, -1); 327 return File_Archive::filter( 328 File_Archive::predPreg('/^'.$regexp.'$/'), 329 $result 330 ); 331 } 332 333 //If the URL can be interpreted as a directory, and we are reading from the file system 334 if ((empty($URL) || is_dir($URL)) && $source === null) { 335 require_once "File/Archive/Reader/Directory.php"; 336 337 if ($uncompressionLevel != 0) { 338 require_once "File/Archive/Reader/Uncompress.php"; 339 $result = new File_Archive_Reader_Uncompress( 340 new File_Archive_Reader_Directory($std, '', $directoryDepth), 341 $uncompressionLevel 342 ); 343 } else { 344 $result = new File_Archive_Reader_Directory($std, '', $directoryDepth); 345 } 346 347 if ($directoryDepth >= 0) { 348 require_once 'File/Archive/Reader/Filter.php'; 349 require_once 'File/Archive/Predicate/MaxDepth.php'; 350 351 $tmp =& File_Archive::filter( 352 new File_Archive_Predicate_MaxDepth($directoryDepth), 353 $result 354 ); 355 unset($result); 356 $result =& $tmp; 357 } 358 if (!empty($realSymbolic)) { 359 if ($symbolic === null) { 360 $realSymbolic = ''; 361 } 362 require_once "File/Archive/Reader/ChangeName/AddDirectory.php"; 363 $tmp = new File_Archive_Reader_ChangeName_AddDirectory( 364 $realSymbolic, 365 $result 366 ); 367 unset($result); 368 $result =& $tmp; 369 } 370 371 //If the URL can be interpreted as a file, and we are reading from the file system 372 } else if (is_file($URL) && substr($URL, -1)!='/' && $source === null) { 373 require_once "File/Archive/Reader/File.php"; 374 $result = new File_Archive_Reader_File($URL, $realSymbolic); 375 376 //Else, we will have to build a complex reader 377 } else { 378 require_once "File/Archive/Reader/File.php"; 379 380 $realPath = $std; 381 382 // Try to find a file with a known extension in the path ( 383 // (to manage URLs like archive.tar/directory/file) 384 $pos = 0; 385 do { 386 if ($pos+1<strlen($realPath)) { 387 $pos = strpos($realPath, '/', $pos+1); 388 } else { 389 $pos = false; 390 } 391 if ($pos === false) { 392 $pos = strlen($realPath); 393 } 394 395 $file = substr($realPath, 0, $pos); 396 $baseDir = substr($realPath, $pos+1); 397 $dotPos = strrpos($file, '.'); 398 $extension = ''; 399 if ($dotPos !== false) { 400 $extension = substr($file, $dotPos+1); 401 } 402 } while ($pos < strlen($realPath) && 403 (!File_Archive::isKnownExtension($extension) || 404 (is_dir($file) && $source==null))); 405 406 $reachable = $file; 407 408 //If we are reading from the file system 409 if ($source === null) { 410 //Create a file reader 411 $result = new File_Archive_Reader_File($file); 412 } else { 413 //Select in the source the file $file 414 415 require_once "File/Archive/Reader/Select.php"; 416 $result = new File_Archive_Reader_Select($file, $source); 417 } 418 419 require_once "File/Archive/Reader/Uncompress.php"; 420 $tmp = new File_Archive_Reader_Uncompress($result, $uncompressionLevel); 421 unset($result); 422 $result = $tmp; 423 424 //Select the requested folder in the uncompress reader 425 $isDir = $result->setBaseDir($std); 426 if (PEAR::isError($isDir)) { 427 return $isDir; 428 } 429 if ($isDir && $symbolic==null) { 430 //Default symbolic name for directories is empty 431 $realSymbolic = ''; 432 } 433 434 if ($directoryDepth >= 0) { 435 //Limit the maximum depth if necessary 436 require_once "File/Archive/Reader/Filter.php"; 437 require_once "File/Archive/Predicate/MaxDepth.php"; 438 439 $tmp = new File_Archive_Reader_Filter( 440 new File_Archive_Predicate( 441 $directoryDepth + 442 substr_count(substr($std, $pos+1), '/') 443 ), 444 $result 445 ); 446 unset($result); 447 $result =& $tmp; 448 } 449 450 if ($std != $realSymbolic) { 451 require_once "File/Archive/Reader/ChangeName/Directory.php"; 452 453 //Change the base name to the symbolic one if necessary 454 $tmp = new File_Archive_Reader_ChangeName_Directory( 455 $std, 456 $realSymbolic, 457 $result 458 ); 459 unset($result); 460 $result =& $tmp; 461 } 462 } 463 464 $cacheCondition = File_Archive::getOption('cacheCondition'); 465 if ($cacheCondition !== false && 466 preg_match($cacheCondition, $URL)) { 467 $tmp =& File_Archive::cache($result); 468 unset($result); 469 $result =& $tmp; 470 } 471 472 return $result; 473 } 474 function read($URL, $symbolic = null, 475 $uncompression = 0, $directoryDepth = -1) 476 { 477 $source = null; 478 return File_Archive::readSource($source, $URL, $symbolic, $uncompression, $directoryDepth); 479 } 480 481 /** 482 * Create a file reader on an uploaded file. The reader will read 483 * $_FILES[$name]['tmp_name'] and will have $_FILES[$name]['name'] 484 * as a symbolic filename. 485 * 486 * A PEAR error is returned if one of the following happen 487 * - $_FILES[$name] is not set 488 * - $_FILES[$name]['error'] is not 0 489 * - is_uploaded_file returns false 490 * 491 * @param string $name Index of the file in the $_FILES array 492 * @return File_Archive_Reader File reader on the uploaded file 493 */ 494 function readUploadedFile($name) 495 { 496 if (!isset($_FILES[$name])) { 497 return PEAR::raiseError("File $name has not been uploaded"); 498 } 499 switch ($_FILES[$name]['error']) { 500 case 0: 501 //No error 502 break; 503 case 1: 504 return PEAR::raiseError( 505 "The upload size limit didn't allow to upload file ". 506 $_FILES[$name]['name'] 507 ); 508 case 2: 509 return PEAR::raiseError( 510 "The form size limit didn't allow to upload file ". 511 $_FILES[$name]['name'] 512 ); 513 case 3: 514 return PEAR::raiseError( 515 "The file was not entirely uploaded" 516 ); 517 case 4: 518 return PEAR::raiseError( 519 "The uploaded file is empty" 520 ); 521 default: 522 return PEAR::raiseError( 523 "Unknown error ".$_FILES[$name]['error']." in file upload. ". 524 "Please, report a bug" 525 ); 526 } 527 if (!is_uploaded_file($_FILES[$name]['tmp_name'])) { 528 return PEAR::raiseError("The file is not an uploaded file"); 529 } 530 531 require_once "File/Archive/Reader/File.php"; 532 return new File_Archive_Reader_File( 533 $_FILES[$name]['tmp_name'], 534 $_FILES[$name]['name'], 535 $_FILES[$name]['type'] 536 ); 537 } 538 539 /** 540 * Adds a cache layer above the specified reader 541 * The data of the reader is saved in a temporary file for future access. 542 * The cached reader will be read only once, even if you read it several times. 543 * This can be usefull to read compressed files or downloaded files (from http or ftp) 544 * 545 * @param mixed $toConvert The reader to cache 546 * It can be a File_Archive_Reader or a string, which will be converted using the 547 * read function 548 */ 549 function cache(&$toConvert) 550 { 551 $source =& File_Archive::_convertToReader($toConvert); 552 if (PEAR::isError($source)) { 553 return $source; 554 } 555 556 require_once 'File/Archive/Reader/Cache.php'; 557 return new File_Archive_Reader_Cache($source); 558 } 559 560 /** 561 * Try to interpret the object as a reader 562 * Strings are converted to readers using File_Archive::read 563 * Arrays are converted to readers using File_Archive::readMulti 564 * 565 * @access private 566 */ 567 function &_convertToReader(&$source) 568 { 569 if (is_string($source)) { 570 $cacheCondition = File_Archive::getOption('cacheCondition'); 571 if ($cacheCondition !== false && 572 preg_match($cacheCondition, $source)) { 573 $obj = File_Archive::cache(File_Archive::read($source)); 574 return $obj; 575 } else { 576 $obj = File_Archive::read($source); 577 return $obj; 578 } 579 } else if (is_array($source)) { 580 return File_Archive::readMulti($source); 581 } else { 582 return $source; 583 } 584 } 585 586 /** 587 * Try to interpret the object as a writer 588 * Strings are converted to writers using File_Archive::appender 589 * Arrays are converted to writers using a multi writer 590 * 591 * @access private 592 */ 593 function &_convertToWriter(&$dest) 594 { 595 if (is_string($dest)) { 596 $obj =& File_Archive::appender($dest); 597 return $obj; 598 } else if (is_array($dest)) { 599 require_once 'File/Archive/Writer/Multi.php'; 600 $writer = new File_Archive_Writer_Multi(); 601 foreach($dest as $key => $foo) { 602 $writer->addWriter($dest[$key]); 603 } 604 return $writer; 605 } else { 606 return $dest; 607 } 608 } 609 610 /** 611 * Check if a file with a specific extension can be read as an archive 612 * with File_Archive::read* 613 * This function is case sensitive. 614 * 615 * @param string $extension the checked extension 616 * @return bool whether this file can be understood reading its extension 617 * Currently, supported extensions are tar, zip, jar, gz, tgz, 618 * tbz, bz2, bzip2, ar, deb 619 */ 620 function isKnownExtension($extension) 621 { 622 return $extension == 'tar' || 623 $extension == 'zip' || 624 $extension == 'jar' || 625 $extension == 'gz' || 626 $extension == 'tgz' || 627 $extension == 'tbz' || 628 $extension == 'bz2' || 629 $extension == 'bzip2' || 630 $extension == 'ar' || 631 $extension == 'deb' /* || 632 $extension == 'cab' || 633 $extension == 'rar' */; 634 } 635 636 /** 637 * Create a reader that will read the single file source $source as 638 * a specific archive 639 * 640 * @param string $extension determines the kind of archive $source contains 641 * $extension is case sensitive 642 * @param File_Archive_Reader $source stores the archive 643 * @param bool $sourceOpened specifies if the archive is already opened 644 * if false, next will be called on source 645 * Closing the returned archive will close $source iif $sourceOpened 646 * is true 647 * @return A File_Archive_Reader that uncompresses the archive contained in 648 * $source interpreting it as a $extension archive 649 * If $extension is not handled return false 650 */ 651 function readArchive($extension, &$toConvert, $sourceOpened = false) 652 { 653 $source =& File_Archive::_convertToReader($toConvert); 654 if (PEAR::isError($source)) { 655 return $source; 656 } 657 658 switch($extension) { 659 case 'tgz': 660 return File_Archive::readArchive('tar', 661 File_Archive::readArchive('gz', $source, $sourceOpened) 662 ); 663 case 'tbz': 664 return File_Archive::readArchive('tar', 665 File_Archive::readArchive('bz2', $source, $sourceOpened) 666 ); 667 case 'tar': 668 require_once 'File/Archive/Reader/Tar.php'; 669 return new File_Archive_Reader_Tar($source, $sourceOpened); 670 671 case 'gz': 672 case 'gzip': 673 require_once 'File/Archive/Reader/Gzip.php'; 674 return new File_Archive_Reader_Gzip($source, $sourceOpened); 675 676 case 'zip': 677 case 'jar': 678 require_once 'File/Archive/Reader/Zip.php'; 679 return new File_Archive_Reader_Zip($source, $sourceOpened); 680 681 case 'bz2': 682 case 'bzip2': 683 require_once 'File/Archive/Reader/Bzip2.php'; 684 return new File_Archive_Reader_Bzip2($source, $sourceOpened); 685 686 case 'deb': 687 case 'ar': 688 require_once 'File/Archive/Reader/Ar.php'; 689 return new File_Archive_Reader_Ar($source, $sourceOpened); 690 691/* case 'cab': 692 require_once 'File/Archive/Reader/Cab.php'; 693 return new File_Archive_Reader_Cab($source, $sourceOpened); 694 695 696 case 'rar': 697 require_once "File/Archive/Reader/Rar.php"; 698 return new File_Archive_Reader_Rar($source, $sourceOpened); */ 699 700 default: 701 return false; 702 } 703 } 704 705 /** 706 * Contains only one file with data read from a memory buffer 707 * 708 * @param string $memory content of the file 709 * @param string $filename public name of the file 710 * @param array $stat statistics of the file. Index 7 (size) will be 711 * overwritten to match the size of $memory 712 * @param string $mime mime type of the file. Default will determine the 713 * mime type thanks to the extension of $filename 714 * @see File_Archive_Reader_Memory 715 */ 716 function readMemory($memory, $filename, $stat=array(), $mime=null) 717 { 718 require_once "File/Archive/Reader/Memory.php"; 719 return new File_Archive_Reader_Memory($memory, $filename, $stat, $mime); 720 } 721 722 /** 723 * Contains several other sources. Take care the sources don't have several 724 * files with the same filename. The sources are given as a parameter, or 725 * can be added thanks to the reader addSource method 726 * 727 * @param array $sources Array of strings or readers that will be added to 728 * the multi reader. If the parameter is a string, a reader will be 729 * built thanks to the read function 730 * @see File_Archive_Reader_Multi, File_Archive::read() 731 */ 732 function readMulti($sources = array()) 733 { 734 require_once "File/Archive/Reader/Multi.php"; 735 $result = new File_Archive_Reader_Multi(); 736 foreach ($sources as $index => $foo) { 737 $s =& File_Archive::_convertToReader($sources[$index]); 738 if (PEAR::isError($s)) { 739 return $s; 740 } else { 741 $result->addSource($s); 742 } 743 } 744 return $result; 745 } 746 /** 747 * Make the files of a source appear as one large file whose content is the 748 * concatenation of the content of all the files 749 * 750 * @param File_Archive_Reader $toConvert The source whose files must be 751 * concatened 752 * @param string $filename name of the only file of the created reader 753 * @param array $stat statistics of the file. Index 7 (size) will be 754 * overwritten to match the total size of the files 755 * @param string $mime mime type of the file. Default will determine the 756 * mime type thanks to the extension of $filename 757 * @see File_Archive_Reader_Concat 758 */ 759 function readConcat(&$toConvert, $filename, $stat=array(), $mime=null) 760 { 761 $source =& File_Archive::_convertToReader($toConvert); 762 if (PEAR::isError($source)) { 763 return $source; 764 } 765 766 require_once "File/Archive/Reader/Concat.php"; 767 return new File_Archive_Reader_Concat($source, $filename, $stat, $mime); 768 } 769 770 /** 771 * Changes the name of each file in a reader by applying a custom function 772 * The function must return false if the file is to be discarded, or the new 773 * name of the file else 774 * 775 * @param Callable $function Function called to modify the name of the file 776 * $function takes the name of the file as a parameter and returns the 777 * new name, or false if the file must be discarded 778 * @param File_Archive_Reader $toConvert The files of this source will be 779 * modified 780 * @return File_Archive_Reader a new reader that contains the same files 781 * as $toConvert but with a different name 782 */ 783 function changeName($function, &$toConvert) 784 { 785 $source =& File_Archive::_convertToReader($toConvert); 786 if (PEAR::isError($source)) { 787 return $source; 788 } 789 790 require_once "File/Archive/Reader/ChangeName.php"; 791 return new File_Archive_Reader_RemoveDirectory($source); 792 } 793 794 /** 795 * Removes from a source the files that do not follow a given predicat 796 * 797 * @param File_Archive_Predicate $predicate Only the files for which 798 * $predicate->isTrue() will be kept 799 * @param File_Archive_Reader $source Source that will be filtered 800 * @see File_Archive_Reader_Filter 801 */ 802 function filter($predicate, &$toConvert) 803 { 804 $source =& File_Archive::_convertToReader($toConvert); 805 if (PEAR::isError($source)) { 806 return $source; 807 } 808 809 require_once "File/Archive/Reader/Filter.php"; 810 return new File_Archive_Reader_Filter($predicate, $source); 811 } 812 /** 813 * Predicate that always evaluate to true 814 * 815 * @see File_Archive_Predicate_True 816 */ 817 function predTrue() 818 { 819 require_once "File/Archive/Predicate/True.php"; 820 return new File_Archive_Predicate_True(); 821 } 822 /** 823 * Predicate that always evaluate to false 824 * 825 * @see File_Archive_Predicate_False 826 */ 827 function predFalse() 828 { 829 require_once "File/Archive/Predicate/False.php"; 830 return new File_Archive_Predicate_False(); 831 } 832 /** 833 * Predicate that evaluates to the logical AND of the parameters 834 * You can add other predicates thanks to the 835 * File_Archive_Predicate_And::addPredicate() function 836 * 837 * @param File_Archive_Predicate (any number of them) 838 * @see File_Archive_Predicate_And 839 */ 840 function predAnd() 841 { 842 require_once "File/Archive/Predicate/And.php"; 843 $pred = new File_Archive_Predicate_And(); 844 $args = func_get_args(); 845 foreach ($args as $p) { 846 $pred->addPredicate($p); 847 } 848 return $pred; 849 } 850 /** 851 * Predicate that evaluates to the logical OR of the parameters 852 * You can add other predicates thanks to the 853 * File_Archive_Predicate_Or::addPredicate() function 854 * 855 * @param File_Archive_Predicate (any number of them) 856 * @see File_Archive_Predicate_Or 857 */ 858 function predOr() 859 { 860 require_once "File/Archive/Predicate/Or.php"; 861 $pred = new File_Archive_Predicate_Or(); 862 $args = func_get_args(); 863 foreach ($args as $p) { 864 $pred->addPredicate($p); 865 } 866 return $pred; 867 } 868 /** 869 * Negate a predicate 870 * 871 * @param File_Archive_Predicate $pred Predicate to negate 872 * @see File_Archive_Predicate_Not 873 */ 874 function predNot($pred) 875 { 876 require_once "File/Archive/Predicate/Not.php"; 877 return new File_Archive_Predicate_Not($pred); 878 } 879 /** 880 * Evaluates to true iif the file is larger than a given size 881 * 882 * @param int $size the minimal size of the files (in Bytes) 883 * @see File_Archive_Predicate_MinSize 884 */ 885 function predMinSize($size) 886 { 887 require_once "File/Archive/Predicate/MinSize.php"; 888 return new File_Archive_Predicate_MinSize($size); 889 } 890 /** 891 * Evaluates to true iif the file has been modified after a given time 892 * 893 * @param int $time Unix timestamp of the minimal modification time of the 894 * files 895 * @see File_Archive_Predicate_MinTime 896 */ 897 function predMinTime($time) 898 { 899 require_once "File/Archive/Predicate/MinTime.php"; 900 return new File_Archive_Predicate_MinTime($time); 901 } 902 /** 903 * Evaluates to true iif the file has less that a given number of 904 * directories in its path 905 * 906 * @param int $depth Maximal number of directories in path of the files 907 * @see File_Archive_Predicate_MaxDepth 908 */ 909 function predMaxDepth($depth) 910 { 911 require_once "File/Archive/Predicate/MaxDepth.php"; 912 return new File_Archive_Predicate_MaxDepth($depth); 913 } 914 /** 915 * Evaluates to true iif the extension of the file is in a given list 916 * 917 * @param array or string $list List or comma separated string of possible 918 * extension of the files 919 * @see File_Archive_Predicate_Extension 920 */ 921 function predExtension($list) 922 { 923 require_once "File/Archive/Predicate/Extension.php"; 924 return new File_Archive_Predicate_Extension($list); 925 } 926 /** 927 * Evaluates to true iif the MIME type of the file is in a given list 928 * 929 * @param array or string $list List or comma separated string of possible 930 * MIME types of the files. You may enter wildcards like "image/*" to 931 * select all the MIME in class image 932 * @see File_Archive_Predicate_MIME, MIME_Type::isWildcard() 933 */ 934 function predMIME($list) 935 { 936 require_once "File/Archive/Predicate/MIME.php"; 937 return new File_Archive_Predicate_MIME($list); 938 } 939 940 /** 941 * Evaluates to true iif the name of the file follow a given regular 942 * expression 943 * 944 * @param string $preg regular expression that the filename must follow 945 * @see File_Archive_Predicate_Preg, preg_match() 946 */ 947 function predPreg($preg) 948 { 949 require_once "File/Archive/Predicate/Preg.php"; 950 return new File_Archive_Predicate_Preg($preg); 951 } 952 953 /** 954 * Evaluates to true iif the name of the file follow a given regular 955 * expression 956 * 957 * @param string $ereg regular expression that the filename must follow 958 * @see File_Archive_Predicate_Ereg, ereg() 959 * @deprecated Make use of predPreg instead for PHP 5.3+ compatability 960 */ 961 function predEreg($ereg) 962 { 963 require_once "File/Archive/Predicate/Ereg.php"; 964 return new File_Archive_Predicate_Ereg($ereg); 965 } 966 /** 967 * Evaluates to true iif the name of the file follow a given regular 968 * expression (case insensitive version) 969 * 970 * @param string $ereg regular expression that the filename must follow 971 * @see File_Archive_Predicate_Eregi, eregi 972 * @deprecated Make use of predPreg instead for PHP 5.3+ compatability 973 */ 974 function predEregi($ereg) 975 { 976 require_once "File/Archive/Predicate/Eregi.php"; 977 return new File_Archive_Predicate_Eregi($ereg); 978 } 979 /** 980 * Evaluates to true only after a given number of evaluations 981 * This can be used to select files by index since the evaluation is done 982 * once per file 983 * 984 * @param array The indexes for which the returned predicate will return true 985 * are the keys of the array 986 * The predicate will return true if isset($indexes[$pos]) 987 */ 988 function predIndex($indexes) 989 { 990 require_once "File/Archive/Predicate/Index.php"; 991 return new File_Archive_Predicate_Index($indexes); 992 } 993 /** 994 * Custom predicate built by supplying a string expression 995 * 996 * Here are different ways to create a predicate that keeps only files 997 * with names shorter than 100 chars 998 * <sample> 999 * File_Archive::predCustom("return strlen($name)<100;") 1000 * File_Archive::predCustom("strlen($name)<100;") 1001 * File_Archive::predCustom("strlen($name)<100") 1002 * File_Archive::predCustom("strlen($source->getFilename())<100") 1003 * </sample> 1004 * 1005 * @param string $expression String containing an expression that evaluates 1006 * to a boolean. If the expression doesn't contain a return 1007 * statement, it will be added at the begining of the expression 1008 * A ';' will be added at the end of the expression so that you don't 1009 * have to write it. You may use the $name variable to refer to the 1010 * current filename (with path...), $time for the modification time 1011 * (unix timestamp), $size for the size of the file in bytes, $mime 1012 * for the MIME type of the file 1013 * @see File_Archive_Predicate_Custom 1014 */ 1015 function predCustom($expression) 1016 { 1017 require_once "File/Archive/Predicate/Custom.php"; 1018 return new File_Archive_Predicate_Custom($expression); 1019 } 1020 1021 /** 1022 * Send the files as a mail attachment 1023 * 1024 * @param Mail $mail Object used to send mail (see Mail::factory) 1025 * @param array or String $to An array or a string with comma separated 1026 * recipients 1027 * @param array $headers The headers that will be passed to the Mail_mime 1028 * object 1029 * @param string $message Text body of the mail 1030 * @see File_Archive_Writer_Mail 1031 */ 1032 function toMail($to, $headers, $message, $mail = null) 1033 { 1034 require_once "File/Archive/Writer/Mail.php"; 1035 return new File_Archive_Writer_Mail($to, $headers, $message, $mail); 1036 } 1037 /** 1038 * Write the files on the hard drive 1039 * 1040 * @param string $baseDir if specified, the files will be created in that 1041 * directory. If they don't exist, the directories will automatically 1042 * be created 1043 * @see File_Archive_Writer_Files 1044 */ 1045 function toFiles($baseDir = "") 1046 { 1047 require_once "File/Archive/Writer/Files.php"; 1048 return new File_Archive_Writer_Files($baseDir); 1049 } 1050 /** 1051 * Send the content of the files to a memory buffer 1052 * 1053 * toMemory returns a writer where the data will be written. 1054 * In this case, the data is accessible using the getData member 1055 * 1056 * toVariable returns a writer that will write into the given 1057 * variable 1058 * 1059 * @param out $data if specified, the data will be written to this buffer 1060 * Else, you can retrieve the buffer with the 1061 * File_Archive_Writer_Memory::getData() function 1062 * @see File_Archive_Writer_Memory 1063 */ 1064 function toMemory() 1065 { 1066 $v = ''; 1067 return File_Archive::toVariable($v); 1068 } 1069 function toVariable(&$v) 1070 { 1071 require_once "File/Archive/Writer/Memory.php"; 1072 return new File_Archive_Writer_Memory($v); 1073 } 1074 /** 1075 * Duplicate the writing operation on two writers 1076 * 1077 * @param File_Archive_Writer $a, $b writers where data will be duplicated 1078 * @see File_Archive_Writer_Multi 1079 */ 1080 function toMulti(&$aC, &$bC) 1081 { 1082 $a =& File_Archive::_convertToWriter($aC); 1083 $b =& File_Archive::_convertToWriter($bC); 1084 1085 if (PEAR::isError($a)) { 1086 return $a; 1087 } 1088 if (PEAR::isError($b)) { 1089 return $b; 1090 } 1091 1092 require_once "File/Archive/Writer/Multi.php"; 1093 $writer = new File_Archive_Writer_Multi(); 1094 $writer->addWriter($a); 1095 $writer->addWriter($b); 1096 return $writer; 1097 } 1098 /** 1099 * Send the content of the files to the standard output (so to the client 1100 * for a website) 1101 * 1102 * @param bool $sendHeaders If true some headers will be sent to force the 1103 * download of the file. Default value is true 1104 * @see File_Archive_Writer_Output 1105 */ 1106 function toOutput($sendHeaders = true) 1107 { 1108 require_once "File/Archive/Writer/Output.php"; 1109 return new File_Archive_Writer_Output($sendHeaders); 1110 } 1111 /** 1112 * Compress the data to a tar, gz, tar/gz or zip format 1113 * 1114 * @param string $filename name of the archive file 1115 * @param File_Archive_Writer $innerWriter writer where the archive will be 1116 * written 1117 * @param string $type can be one of tgz, tbz, tar, zip, gz, gzip, bz2, 1118 * bzip2 (default is the extension of $filename) or any composition 1119 * of them (for example tar.gz or tar.bz2). The case of this 1120 * parameter is not important. 1121 * @param array $stat Statistics of the archive (see stat function) 1122 * @param bool $autoClose If set to true, $innerWriter will be closed when 1123 * the returned archive is close. Default value is true. 1124 */ 1125 function toArchive($filename, &$toConvert, $type = null, 1126 $stat = array(), $autoClose = true) 1127 { 1128 $innerWriter =& File_Archive::_convertToWriter($toConvert); 1129 if (PEAR::isError($innerWriter)) { 1130 return $innerWriter; 1131 } 1132 $shortcuts = array("tgz" , "tbz" ); 1133 $reals = array("tar.gz", "tar.bz2"); 1134 1135 if ($type === null) { 1136 $extensions = strtolower($filename); 1137 } else { 1138 $extensions = strtolower($type); 1139 } 1140 $extensions = explode('.', str_replace($shortcuts, $reals, $extensions)); 1141 if ($innerWriter !== null) { 1142 $writer =& $innerWriter; 1143 } else { 1144 $writer = File_Archive::toFiles(); 1145 } 1146 $nbCompressions = 0; 1147 $currentFilename = $filename; 1148 while (($extension = array_pop($extensions)) !== null) { 1149 unset($next); 1150 switch($extension) { 1151 case "tar": 1152 require_once "File/Archive/Writer/Tar.php"; 1153 $next = new File_Archive_Writer_Tar( 1154 $currentFilename, $writer, $stat, $autoClose 1155 ); 1156 unset($writer); $writer =& $next; 1157 break; 1158 case "zip": 1159 require_once "File/Archive/Writer/Zip.php"; 1160 $next = new File_Archive_Writer_Zip( 1161 $currentFilename, $writer, $stat, $autoClose 1162 ); 1163 unset($writer); $writer =& $next; 1164 break; 1165 case "gz": 1166 case "gzip": 1167 require_once "File/Archive/Writer/Gzip.php"; 1168 $next = new File_Archive_Writer_Gzip( 1169 $currentFilename, $writer, $stat, $autoClose 1170 ); 1171 unset($writer); $writer =& $next; 1172 break; 1173 case "bz2": 1174 case "bzip2": 1175 require_once "File/Archive/Writer/Bzip2.php"; 1176 $next = new File_Archive_Writer_Bzip2( 1177 $currentFilename, $writer, $stat, $autoClose 1178 ); 1179 unset($writer); $writer =& $next; 1180 break; 1181 case "deb": 1182 case "ar": 1183 require_once "File/Archive/Writer/Ar.php"; 1184 $next = new File_Archive_Writer_Ar( 1185 $currentFilename, $writer, $stat, $autoClose 1186 ); 1187 unset($writer); $writer =& $next; 1188 break; 1189 default: 1190 if ($type !== null || $nbCompressions == 0) { 1191 return PEAR::raiseError("Archive $extension unknown"); 1192 } 1193 break; 1194 } 1195 $nbCompressions ++; 1196 $autoClose = true; 1197 $currentFilename = implode(".", $extensions); 1198 } 1199 return $writer; 1200 } 1201 1202 1203 /** 1204 * File_Archive::extract($source, $dest) is equivalent to $source->extract($dest) 1205 * If $source is a PEAR error, the error will be returned 1206 * It is thus easier to use this function than $source->extract, since it reduces the number of 1207 * error checking and doesn't force you to define a variable $source 1208 * 1209 * You may use strings as source and dest. In that case the source is automatically 1210 * converted to a reader using File_Archive::read and the dest is converted to a 1211 * writer using File_Archive::appender 1212 * Since PHP doesn't allow to pass literal strings by ref, you will have to use temporary 1213 * variables. 1214 * File_Archive::extract($src = 'archive.zip/', $dest = 'dir') will extract the archive to 'dir' 1215 * It is the same as 1216 * File_Archive::extract( 1217 * File_Archive::read('archive.zip/'), 1218 * File_Archive::appender('dir') 1219 * ); 1220 * You may use any variable in the extract function ($from/$to, $a/$b...). 1221 * 1222 * @param File_Archive_Reader $source The source that will be read 1223 * @param File_Archive_Writer $dest Where to copy $source files 1224 * @param bool $autoClose if true (default), $dest will be closed after the extraction 1225 * @param int $bufferSize Size of the buffer to use to move data from the reader to the buffer 1226 * If $bufferSize <= 0 (default), the blockSize option is used 1227 * You shouldn't need to change that 1228 * @return null or a PEAR error if an error occured 1229 */ 1230 function extract(&$sourceToConvert, &$destToConvert, $autoClose = true, $bufferSize = 0) 1231 { 1232 $source =& File_Archive::_convertToReader($sourceToConvert); 1233 if (PEAR::isError($source)) { 1234 return $source; 1235 } 1236 $dest =& File_Archive::_convertToWriter($destToConvert); 1237 return $source->extract($dest, $autoClose, $bufferSize); 1238 } 1239 1240 /** 1241 * Create a writer that can be used to append files to an archive inside a source 1242 * If the archive can't be found in the source, it will be created 1243 * If source is set to null, File_Archive::toFiles will be assumed 1244 * If type is set to null, the type of the archive will be determined looking at 1245 * the extension in the URL 1246 * stat is the array of stat (returned by stat() PHP function of Reader getStat()) 1247 * to use if the archive must be created 1248 * 1249 * This function allows to create or append data to nested archives. Only one 1250 * archive will be created and if your creation requires creating several nested 1251 * archives, a PEAR error will be returned 1252 * 1253 * After this call, $source will be closed and should not be used until the 1254 * returned writer is closed. 1255 * 1256 * @param File_Archive_Reader $source A reader where some files will be appended 1257 * @param string $URL URL to reach the archive in the source. 1258 * if $URL is null, a writer to append files to the $source reader will 1259 * be returned 1260 * @param bool $unique If true, the duplicate files will be deleted on close 1261 * Default is false (and setting it to true may have some performance 1262 * consequences) 1263 * @param string $type Extension of the archive (or null to use the one in the URL) 1264 * @param array $stat Used only if archive is created, array of stat as returned 1265 * by PHP stat function or Reader getStat function: stats of the archive) 1266 * Time (index 9) will be overwritten to current time 1267 * @return File_Archive_Writer a writer that you can use to append files to the reader 1268 */ 1269 function appenderFromSource(&$toConvert, $URL = null, $unique = null, 1270 $type = null, $stat = array()) 1271 { 1272 $source =& File_Archive::_convertToReader($toConvert); 1273 if (PEAR::isError($source)) { 1274 return $source; 1275 } 1276 if ($unique == null) { 1277 $unique = File_Archive::getOption("appendRemoveDuplicates"); 1278 } 1279 1280 //Do not report the fact that the archive does not exist as an error 1281 PEAR::pushErrorHandling(PEAR_ERROR_RETURN); 1282 1283 if ($URL === null) { 1284 $result =& $source; 1285 } else { 1286 if ($type === null) { 1287 $result = File_Archive::_readSource($source, $URL.'/', $reachable, $baseDir); 1288 } else { 1289 $result = File_Archive::readArchive( 1290 $type, 1291 File_Archive::_readSource($source, $URL, $reachable, $baseDir) 1292 ); 1293 } 1294 } 1295 1296 PEAR::popErrorHandling(); 1297 1298 if (!PEAR::isError($result)) { 1299 if ($unique) { 1300 require_once "File/Archive/Writer/UniqueAppender.php"; 1301 return new File_Archive_Writer_UniqueAppender($result); 1302 } else { 1303 return $result->makeAppendWriter(); 1304 } 1305 } 1306 1307 //The source can't be found and has to be created 1308 $stat[9] = $stat['mtime'] = time(); 1309 1310 if (empty($baseDir)) { 1311 if ($source !== null) { 1312 $writer =& $source->makeWriter(); 1313 } else { 1314 $writer =& File_Archive::toFiles(); 1315 } 1316 if (PEAR::isError($writer)) { 1317 return $writer; 1318 } 1319 1320 PEAR::pushErrorHandling(PEAR_ERROR_RETURN); 1321 $result = File_Archive::toArchive($reachable, $writer, $type); 1322 PEAR::popErrorHandling(); 1323 1324 if (PEAR::isError($result)) { 1325 $result = File_Archive::toFiles($reachable); 1326 } 1327 } else { 1328 $reachedSource = File_Archive::readSource($source, $reachable); 1329 if (PEAR::isError($reachedSource)) { 1330 return $reachedSource; 1331 } 1332 $writer = $reachedSource->makeWriter(); 1333 if (PEAR::isError($writer)) { 1334 return $writer; 1335 } 1336 1337 PEAR::pushErrorHandling(PEAR_ERROR_RETURN); 1338 $result = File_Archive::toArchive($baseDir, $writer, $type); 1339 PEAR::popErrorHandling(); 1340 1341 if (PEAR::isError($result)) { 1342 require_once "File/Archive/Writer/AddBaseName.php"; 1343 $result = new File_Archive_Writer_AddBaseName( 1344 $baseDir, $writer); 1345 if (PEAR::isError($result)) { 1346 return $result; 1347 } 1348 } 1349 } 1350 return $result; 1351 } 1352 1353 /** 1354 * Create a writer that allows appending new files to an existing archive 1355 * This function actes as appendToSource with source being the system files 1356 * $URL can't be null here 1357 * 1358 * @param File_Archive_Reader $source A reader where some files will be appended 1359 * @return File_Archive_Writer a writer that you can use to append files to the reader 1360 */ 1361 function appender($URL, $unique = null, $type = null, $stat = array()) 1362 { 1363 $source = null; 1364 return File_Archive::appenderFromSource($source, $URL, $unique, $type, $stat); 1365 } 1366 1367 /** 1368 * Remove the files that follow a given predicate from the source 1369 * If URL is null, the files will be removed from the source directly 1370 * Else, URL must link to a source from which the files will be removed 1371 * 1372 * @param File_Archive_Predicate $pred The files that follow the predicate 1373 * (for which $pred->isTrue($source) is true) will be erased 1374 * @param File_Archive_Reader $source A reader that contains the files to remove 1375 */ 1376 function removeFromSource(&$pred, &$toConvert, $URL = null) 1377 { 1378 $source =& File_Archive::_convertToReader($toConvert); 1379 if (PEAR::isError($source)) { 1380 return $source; 1381 } 1382 if ($URL === null) { 1383 $result = &$source; 1384 } else { 1385 if (substr($URL, -1) !== '/') { 1386 $URL .= '/'; 1387 } 1388 $result = File_Archive::readSource($source, $URL); 1389 } 1390 1391 $writer = $result->makeWriterRemoveFiles($pred); 1392 if (PEAR::isError($writer)) { 1393 return $writer; 1394 } 1395 $writer->close(); 1396 } 1397 1398 /** 1399 * Remove the files that follow a given predicate from the archive specified 1400 * in $URL 1401 * 1402 * @param $URL URL of the archive where some files must be removed 1403 */ 1404 function remove($pred, $URL) 1405 { 1406 $source = null; 1407 return File_Archive::removeFromSource($pred, $source, $URL); 1408 } 1409 1410 /** 1411 * Remove duplicates from a source, keeping the most recent one (or the one that has highest pos in 1412 * the archive if the files have same date or no date specified) 1413 * 1414 * @param File_Archive_Reader a reader that may contain duplicates 1415 */ 1416 function removeDuplicatesFromSource(&$toConvert, $URL = null) 1417 { 1418 $source =& File_Archive::_convertToReader($toConvert); 1419 if (PEAR::isError($source)) { 1420 return $source; 1421 } 1422 if ($URL !== null && substr($URL, -1) != '/') { 1423 $URL .= '/'; 1424 } 1425 1426 if ($source === null) { 1427 $source = File_Archive::read($URL); 1428 } 1429 1430 require_once "File/Archive/Predicate/Duplicate.php"; 1431 $pred = new File_Archive_Predicate_Duplicate($source); 1432 $source->close(); 1433 return File_Archive::removeFromSource( 1434 $pred, 1435 $source, 1436 null 1437 ); 1438 } 1439 1440 /** 1441 * Remove duplicates from the archive specified in the URL 1442 */ 1443 function removeDuplicates($URL) 1444 { 1445 $source = null; 1446 return File_Archive::removeDuplicatesFromSource($source, $URL); 1447 } 1448} 1449 1450?> 1451