[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Implementation of .tar.gz packer. 19 * 20 * A limited subset of the .tar format is supported. This packer can open files 21 * that it wrote, but may not be able to open files from other sources, 22 * especially if they use extensions. There are restrictions on file 23 * length and character set of filenames. 24 * 25 * We generate POSIX-compliant ustar files. As a result, the following 26 * restrictions apply to archive paths: 27 * 28 * - Filename may not be more than 100 characters. 29 * - Total of path + filename may not be more than 256 characters. 30 * - For path more than 155 characters it may or may not work. 31 * - May not contain non-ASCII characters. 32 * 33 * @package core_files 34 * @copyright 2013 The Open University 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 38 defined('MOODLE_INTERNAL') || die(); 39 40 require_once("$CFG->libdir/filestorage/file_packer.php"); 41 require_once("$CFG->libdir/filestorage/tgz_extractor.php"); 42 43 /** 44 * Utility class - handles all packing/unpacking of .tar.gz files. 45 * 46 * @package core_files 47 * @category files 48 * @copyright 2013 The Open University 49 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 50 */ 51 class tgz_packer extends file_packer { 52 /** 53 * @var int Default timestamp used where unknown (Jan 1st 2013 00:00) 54 */ 55 const DEFAULT_TIMESTAMP = 1356998400; 56 57 /** 58 * @var string Name of special archive index file added by Moodle. 59 */ 60 const ARCHIVE_INDEX_FILE = '.ARCHIVE_INDEX'; 61 62 /** 63 * @var string Required text at start of archive index file before file count. 64 */ 65 const ARCHIVE_INDEX_COUNT_PREFIX = 'Moodle archive file index. Count: '; 66 67 /** 68 * @var bool If true, includes .ARCHIVE_INDEX file in root of tar file. 69 */ 70 protected $includeindex = true; 71 72 /** 73 * @var int Max value for total progress. 74 */ 75 const PROGRESS_MAX = 1000000; 76 77 /** 78 * @var int Tar files have a fixed block size of 512 bytes. 79 */ 80 const TAR_BLOCK_SIZE = 512; 81 82 /** 83 * Archive files and store the result in file storage. 84 * 85 * Any existing file at that location will be overwritten. 86 * 87 * @param array $files array from archive path => pathname or stored_file 88 * @param int $contextid context ID 89 * @param string $component component 90 * @param string $filearea file area 91 * @param int $itemid item ID 92 * @param string $filepath file path 93 * @param string $filename file name 94 * @param int $userid user ID 95 * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error 96 * @param file_progress $progress Progress indicator callback or null if not required 97 * @return stored_file|bool false if error stored_file instance if ok 98 * @throws file_exception If file operations fail 99 * @throws coding_exception If any archive paths do not meet the restrictions 100 */ 101 public function archive_to_storage(array $files, $contextid, 102 $component, $filearea, $itemid, $filepath, $filename, 103 $userid = null, $ignoreinvalidfiles = true, file_progress $progress = null) { 104 global $CFG; 105 106 // Set up a temporary location for the file. 107 $tempfolder = $CFG->tempdir . '/core_files'; 108 check_dir_exists($tempfolder); 109 $tempfile = tempnam($tempfolder, '.tgz'); 110 111 // Archive to the given path. 112 if ($result = $this->archive_to_pathname($files, $tempfile, $ignoreinvalidfiles, $progress)) { 113 // If there is an existing file, delete it. 114 $fs = get_file_storage(); 115 if ($existing = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) { 116 $existing->delete(); 117 } 118 $filerecord = array('contextid' => $contextid, 'component' => $component, 119 'filearea' => $filearea, 'itemid' => $itemid, 'filepath' => $filepath, 120 'filename' => $filename, 'userid' => $userid, 'mimetype' => 'application/x-tgz'); 121 self::delete_existing_file_record($fs, $filerecord); 122 $result = $fs->create_file_from_pathname($filerecord, $tempfile); 123 } 124 125 // Delete the temporary file (if created) and return. 126 @unlink($tempfile); 127 return $result; 128 } 129 130 /** 131 * Wrapper function useful for deleting an existing file (if present) just 132 * before creating a new one. 133 * 134 * @param file_storage $fs File storage 135 * @param array $filerecord File record in same format used to create file 136 */ 137 public static function delete_existing_file_record(file_storage $fs, array $filerecord) { 138 if ($existing = $fs->get_file($filerecord['contextid'], $filerecord['component'], 139 $filerecord['filearea'], $filerecord['itemid'], $filerecord['filepath'], 140 $filerecord['filename'])) { 141 $existing->delete(); 142 } 143 } 144 145 /** 146 * By default, the .tar file includes a .ARCHIVE_INDEX file as its first 147 * entry. This makes list_files much faster and allows for better progress 148 * reporting. 149 * 150 * If you need to disable the inclusion of this file, use this function 151 * before calling one of the archive_xx functions. 152 * 153 * @param bool $includeindex If true, includes index 154 */ 155 public function set_include_index($includeindex) { 156 $this->includeindex = $includeindex; 157 } 158 159 /** 160 * Archive files and store the result in an OS file. 161 * 162 * @param array $files array from archive path => pathname or stored_file 163 * @param string $archivefile path to target zip file 164 * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error 165 * @param file_progress $progress Progress indicator callback or null if not required 166 * @return bool true if file created, false if not 167 * @throws coding_exception If any archive paths do not meet the restrictions 168 */ 169 public function archive_to_pathname(array $files, $archivefile, 170 $ignoreinvalidfiles=true, file_progress $progress = null) { 171 // Open .gz file. 172 if (!($gz = gzopen($archivefile, 'wb'))) { 173 return false; 174 } 175 try { 176 // Because we update how we calculate progress after we already 177 // analyse the directory list, we can't just use a number of files 178 // as progress. Instead, progress always goes to PROGRESS_MAX 179 // and we do estimates as a proportion of that. To begin with, 180 // assume that counting files will be 10% of the work, so allocate 181 // one-tenth of PROGRESS_MAX to the total of all files. 182 if ($files) { 183 $progressperfile = (int)(self::PROGRESS_MAX / (count($files) * 10)); 184 } else { 185 // If there are no files, avoid divide by zero. 186 $progressperfile = 1; 187 } 188 $done = 0; 189 190 // Expand the provided files into a complete list of single files. 191 $expandedfiles = array(); 192 foreach ($files as $archivepath => $file) { 193 // Update progress if required. 194 if ($progress) { 195 $progress->progress($done, self::PROGRESS_MAX); 196 } 197 $done += $progressperfile; 198 199 if (is_null($file)) { 200 // Empty directory record. Ensure it ends in a /. 201 if (!preg_match('~/$~', $archivepath)) { 202 $archivepath .= '/'; 203 } 204 $expandedfiles[$archivepath] = null; 205 } else if (is_string($file)) { 206 // File specified as path on disk. 207 if (!$this->list_files_path($expandedfiles, $archivepath, $file, 208 $progress, $done)) { 209 gzclose($gz); 210 unlink($archivefile); 211 return false; 212 } 213 } else if (is_array($file)) { 214 // File specified as raw content in array. 215 $expandedfiles[$archivepath] = $file; 216 } else { 217 // File specified as stored_file object. 218 $this->list_files_stored($expandedfiles, $archivepath, $file); 219 } 220 } 221 222 // Store the list of files as a special file that is first in the 223 // archive. This contains enough information to implement list_files 224 // if required later. 225 $list = self::ARCHIVE_INDEX_COUNT_PREFIX . count($expandedfiles) . "\n"; 226 $sizes = array(); 227 $mtimes = array(); 228 foreach ($expandedfiles as $archivepath => $file) { 229 // Check archivepath doesn't contain any non-ASCII characters. 230 if (!preg_match('~^[\x00-\xff]*$~', $archivepath)) { 231 throw new coding_exception( 232 'Non-ASCII paths not supported: ' . $archivepath); 233 } 234 235 // Build up the details. 236 $type = 'f'; 237 $mtime = '?'; 238 if (is_null($file)) { 239 $type = 'd'; 240 $size = 0; 241 } else if (is_string($file)) { 242 $stat = stat($file); 243 $mtime = (int)$stat['mtime']; 244 $size = (int)$stat['size']; 245 } else if (is_array($file)) { 246 $size = (int)strlen(reset($file)); 247 } else { 248 $mtime = (int)$file->get_timemodified(); 249 $size = (int)$file->get_filesize(); 250 } 251 $sizes[$archivepath] = $size; 252 $mtimes[$archivepath] = $mtime; 253 254 // Write a line in the index. 255 $list .= "$archivepath\t$type\t$size\t$mtime\n"; 256 } 257 258 // The index file is optional; only write into archive if needed. 259 if ($this->includeindex) { 260 // Put the index file into the archive. 261 $this->write_tar_entry($gz, self::ARCHIVE_INDEX_FILE, null, strlen($list), '?', $list); 262 } 263 264 // Update progress ready for main stage. 265 $done = (int)(self::PROGRESS_MAX / 10); 266 if ($progress) { 267 $progress->progress($done, self::PROGRESS_MAX); 268 } 269 if ($expandedfiles) { 270 // The remaining 9/10ths of progress represents these files. 271 $progressperfile = (int)((9 * self::PROGRESS_MAX) / (10 * count($expandedfiles))); 272 } else { 273 $progressperfile = 1; 274 } 275 276 // Actually write entries for each file/directory. 277 foreach ($expandedfiles as $archivepath => $file) { 278 if (is_null($file)) { 279 // Null entry indicates a directory. 280 $this->write_tar_entry($gz, $archivepath, null, 281 $sizes[$archivepath], $mtimes[$archivepath]); 282 } else if (is_string($file)) { 283 // String indicates an OS file. 284 $this->write_tar_entry($gz, $archivepath, $file, 285 $sizes[$archivepath], $mtimes[$archivepath], null, $progress, $done); 286 } else if (is_array($file)) { 287 // Array indicates in-memory data. 288 $data = reset($file); 289 $this->write_tar_entry($gz, $archivepath, null, 290 $sizes[$archivepath], $mtimes[$archivepath], $data, $progress, $done); 291 } else { 292 // Stored_file object. 293 $this->write_tar_entry($gz, $archivepath, $file->get_content_file_handle(), 294 $sizes[$archivepath], $mtimes[$archivepath], null, $progress, $done); 295 } 296 $done += $progressperfile; 297 if ($progress) { 298 $progress->progress($done, self::PROGRESS_MAX); 299 } 300 } 301 302 // Finish tar file with two empty 512-byte records. 303 gzwrite($gz, str_pad('', 2 * self::TAR_BLOCK_SIZE, "\x00")); 304 gzclose($gz); 305 return true; 306 } catch (Exception $e) { 307 // If there is an exception, delete the in-progress file. 308 gzclose($gz); 309 unlink($archivefile); 310 throw $e; 311 } 312 } 313 314 /** 315 * Writes a single tar file to the archive, including its header record and 316 * then the file contents. 317 * 318 * @param resource $gz Gzip file 319 * @param string $archivepath Full path of file within archive 320 * @param string|resource $file Full path of file on disk or file handle or null if none 321 * @param int $size Size or 0 for directories 322 * @param int|string $mtime Time or ? if unknown 323 * @param string $content Actual content of file to write (null if using $filepath) 324 * @param file_progress $progress Progress indicator or null if none 325 * @param int $done Value for progress indicator 326 * @return bool True if OK 327 * @throws coding_exception If names aren't valid 328 */ 329 protected function write_tar_entry($gz, $archivepath, $file, $size, $mtime, $content = null, 330 file_progress $progress = null, $done = 0) { 331 // Header based on documentation of POSIX ustar format from: 332 // http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current . 333 334 // For directories, ensure name ends in a slash. 335 $directory = false; 336 if ($size === 0 && is_null($file)) { 337 $directory = true; 338 if (!preg_match('~/$~', $archivepath)) { 339 $archivepath .= '/'; 340 } 341 $mode = '755'; 342 } else { 343 $mode = '644'; 344 } 345 346 // Split archivepath into name and prefix. 347 $name = $archivepath; 348 $prefix = ''; 349 while (strlen($name) > 100) { 350 $slash = strpos($name, '/'); 351 if ($slash === false) { 352 throw new coding_exception( 353 'Name cannot fit length restrictions (> 100 characters): ' . $archivepath); 354 } 355 356 if ($prefix !== '') { 357 $prefix .= '/'; 358 } 359 $prefix .= substr($name, 0, $slash); 360 $name = substr($name, $slash + 1); 361 if (strlen($prefix) > 155) { 362 throw new coding_exception( 363 'Name cannot fit length restrictions (path too long): ' . $archivepath); 364 } 365 } 366 367 // Checksum performance is a bit slow because of having to call 'ord' 368 // lots of times (it takes about 1/3 the time of the actual gzwrite 369 // call). To improve performance of checksum calculation, we will 370 // store all the non-zero, non-fixed bytes that need adding to the 371 // checksum, and checksum only those bytes. 372 $forchecksum = $name; 373 374 // struct header_posix_ustar { 375 // char name[100]; 376 $header = str_pad($name, 100, "\x00"); 377 378 // char mode[8]; 379 // char uid[8]; 380 // char gid[8]; 381 $header .= '0000' . $mode . "\x000000000\x000000000\x00"; 382 $forchecksum .= $mode; 383 384 // char size[12]; 385 $octalsize = decoct($size); 386 if (strlen($octalsize) > 11) { 387 throw new coding_exception( 388 'File too large for .tar file: ' . $archivepath . ' (' . $size . ' bytes)'); 389 } 390 $paddedsize = str_pad($octalsize, 11, '0', STR_PAD_LEFT); 391 $forchecksum .= $paddedsize; 392 $header .= $paddedsize . "\x00"; 393 394 // char mtime[12]; 395 if ($mtime === '?') { 396 // Use a default timestamp rather than zero; GNU tar outputs 397 // warnings about zeroes here. 398 $mtime = self::DEFAULT_TIMESTAMP; 399 } 400 $octaltime = decoct($mtime); 401 $paddedtime = str_pad($octaltime, 11, '0', STR_PAD_LEFT); 402 $forchecksum .= $paddedtime; 403 $header .= $paddedtime . "\x00"; 404 405 // char checksum[8]; 406 // Checksum needs to be completed later. 407 $header .= ' '; 408 409 // char typeflag[1]; 410 $typeflag = $directory ? '5' : '0'; 411 $forchecksum .= $typeflag; 412 $header .= $typeflag; 413 414 // char linkname[100]; 415 $header .= str_pad('', 100, "\x00"); 416 417 // char magic[6]; 418 // char version[2]; 419 $header .= "ustar\x0000"; 420 421 // char uname[32]; 422 // char gname[32]; 423 // char devmajor[8]; 424 // char devminor[8]; 425 $header .= str_pad('', 80, "\x00"); 426 427 // char prefix[155]; 428 // char pad[12]; 429 $header .= str_pad($prefix, 167, "\x00"); 430 $forchecksum .= $prefix; 431 432 // }; 433 434 // We have now calculated the header, but without the checksum. To work 435 // out the checksum, sum all the bytes that aren't fixed or zero, and add 436 // to a standard value that contains all the fixed bytes. 437 438 // The fixed non-zero bytes are: 439 // 440 // '000000000000000000 ustar00' 441 // mode (except 3 digits), uid, gid, checksum space, magic number, version 442 // 443 // To calculate the number, call the calculate_checksum function on the 444 // above string. The result is 1775. 445 $checksum = 1775 + self::calculate_checksum($forchecksum); 446 447 $octalchecksum = str_pad(decoct($checksum), 6, '0', STR_PAD_LEFT) . "\x00 "; 448 449 // Slot it into place in the header. 450 $header = substr($header, 0, 148) . $octalchecksum . substr($header, 156); 451 452 if (strlen($header) != self::TAR_BLOCK_SIZE) { 453 throw new coding_exception('Header block wrong size!!!!!'); 454 } 455 456 // Awesome, now write out the header. 457 gzwrite($gz, $header); 458 459 // Special pre-handler for OS filename. 460 if (is_string($file)) { 461 $file = fopen($file, 'rb'); 462 if (!$file) { 463 return false; 464 } 465 } 466 467 if ($content !== null) { 468 // Write in-memory content if any. 469 if (strlen($content) !== $size) { 470 throw new coding_exception('Mismatch between provided sizes: ' . $archivepath); 471 } 472 gzwrite($gz, $content); 473 } else if ($file !== null) { 474 // Write file content if any, using a 64KB buffer. 475 $written = 0; 476 $chunks = 0; 477 while (true) { 478 $data = fread($file, 65536); 479 if ($data === false || strlen($data) == 0) { 480 break; 481 } 482 $written += gzwrite($gz, $data); 483 484 // After every megabyte of large files, update the progress 485 // tracker (so there are no long gaps without progress). 486 $chunks++; 487 if ($chunks == 16) { 488 $chunks = 0; 489 if ($progress) { 490 // This call always has the same values, but that gives 491 // the tracker a chance to indicate indeterminate 492 // progress and output something to avoid timeouts. 493 $progress->progress($done, self::PROGRESS_MAX); 494 } 495 } 496 } 497 fclose($file); 498 499 if ($written !== $size) { 500 throw new coding_exception('Mismatch between provided sizes: ' . $archivepath . 501 ' (was ' . $written . ', expected ' . $size . ')'); 502 } 503 } else if ($size != 0) { 504 throw new coding_exception('Missing data file handle for non-empty file'); 505 } 506 507 // Pad out final 512-byte block in file, if applicable. 508 $leftover = self::TAR_BLOCK_SIZE - ($size % self::TAR_BLOCK_SIZE); 509 if ($leftover == 512) { 510 $leftover = 0; 511 } else { 512 gzwrite($gz, str_pad('', $leftover, "\x00")); 513 } 514 515 return true; 516 } 517 518 /** 519 * Calculates a checksum by summing all characters of the binary string 520 * (treating them as unsigned numbers). 521 * 522 * @param string $str Input string 523 * @return int Checksum 524 */ 525 protected static function calculate_checksum($str) { 526 $checksum = 0; 527 $checklength = strlen($str); 528 for ($i = 0; $i < $checklength; $i++) { 529 $checksum += ord($str[$i]); 530 } 531 return $checksum; 532 } 533 534 /** 535 * Based on an OS path, adds either that path (if it's a file) or 536 * all its children (if it's a directory) into the list of files to 537 * archive. 538 * 539 * If a progress indicator is supplied and if this corresponds to a 540 * directory, then it will be repeatedly called with the same values. This 541 * allows the progress handler to respond in some way to avoid timeouts 542 * if required. 543 * 544 * @param array $expandedfiles List of all files to archive (output) 545 * @param string $archivepath Current path within archive 546 * @param string $path OS path on disk 547 * @param file_progress $progress Progress indicator or null if none 548 * @param int $done Value for progress indicator 549 * @return bool True if successful 550 */ 551 protected function list_files_path(array &$expandedfiles, $archivepath, $path, 552 file_progress $progress = null, $done) { 553 if (is_dir($path)) { 554 // Unless we're using this directory as archive root, add a 555 // directory entry. 556 if ($archivepath != '') { 557 // Add directory-creation record. 558 $expandedfiles[$archivepath . '/'] = null; 559 } 560 561 // Loop through directory contents and recurse. 562 if (!$handle = opendir($path)) { 563 return false; 564 } 565 while (false !== ($entry = readdir($handle))) { 566 if ($entry === '.' || $entry === '..') { 567 continue; 568 } 569 $result = $this->list_files_path($expandedfiles, 570 $archivepath . '/' . $entry, $path . '/' . $entry, 571 $progress, $done); 572 if (!$result) { 573 return false; 574 } 575 if ($progress) { 576 $progress->progress($done, self::PROGRESS_MAX); 577 } 578 } 579 closedir($handle); 580 } else { 581 // Just add it to list. 582 $expandedfiles[$archivepath] = $path; 583 } 584 return true; 585 } 586 587 /** 588 * Based on a stored_file objects, adds either that file (if it's a file) or 589 * all its children (if it's a directory) into the list of files to 590 * archive. 591 * 592 * If a progress indicator is supplied and if this corresponds to a 593 * directory, then it will be repeatedly called with the same values. This 594 * allows the progress handler to respond in some way to avoid timeouts 595 * if required. 596 * 597 * @param array $expandedfiles List of all files to archive (output) 598 * @param string $archivepath Current path within archive 599 * @param stored_file $file File object 600 */ 601 protected function list_files_stored(array &$expandedfiles, $archivepath, stored_file $file) { 602 if ($file->is_directory()) { 603 // Add a directory-creation record. 604 $expandedfiles[$archivepath . '/'] = null; 605 606 // Loop through directory contents (this is a recursive collection 607 // of all children not just one directory). 608 $fs = get_file_storage(); 609 $baselength = strlen($file->get_filepath()); 610 $files = $fs->get_directory_files( 611 $file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(), 612 $file->get_filepath(), true, true); 613 foreach ($files as $childfile) { 614 // Get full pathname after original part. 615 $path = $childfile->get_filepath(); 616 $path = substr($path, $baselength); 617 $path = $archivepath . '/' . $path; 618 if ($childfile->is_directory()) { 619 $childfile = null; 620 } else { 621 $path .= $childfile->get_filename(); 622 } 623 $expandedfiles[$path] = $childfile; 624 } 625 } else { 626 // Just add it to list. 627 $expandedfiles[$archivepath] = $file; 628 } 629 } 630 631 /** 632 * Extract file to given file path (real OS filesystem), existing files are overwritten. 633 * 634 * @param stored_file|string $archivefile full pathname of zip file or stored_file instance 635 * @param string $pathname target directory 636 * @param array $onlyfiles only extract files present in the array 637 * @param file_progress $progress Progress indicator callback or null if not required 638 * @return array list of processed files (name=>true) 639 * @throws moodle_exception If error 640 */ 641 public function extract_to_pathname($archivefile, $pathname, 642 array $onlyfiles = null, file_progress $progress = null) { 643 $extractor = new tgz_extractor($archivefile); 644 return $extractor->extract( 645 new tgz_packer_extract_to_pathname($pathname, $onlyfiles), $progress); 646 } 647 648 /** 649 * Extract file to given file path (real OS filesystem), existing files are overwritten. 650 * 651 * @param string|stored_file $archivefile full pathname of zip file or stored_file instance 652 * @param int $contextid context ID 653 * @param string $component component 654 * @param string $filearea file area 655 * @param int $itemid item ID 656 * @param string $pathbase file path 657 * @param int $userid user ID 658 * @param file_progress $progress Progress indicator callback or null if not required 659 * @return array list of processed files (name=>true) 660 * @throws moodle_exception If error 661 */ 662 public function extract_to_storage($archivefile, $contextid, 663 $component, $filearea, $itemid, $pathbase, $userid = null, 664 file_progress $progress = null) { 665 $extractor = new tgz_extractor($archivefile); 666 return $extractor->extract( 667 new tgz_packer_extract_to_storage($contextid, $component, 668 $filearea, $itemid, $pathbase, $userid), $progress); 669 } 670 671 /** 672 * Returns array of info about all files in archive. 673 * 674 * @param string|stored_file $archivefile 675 * @return array of file infos 676 */ 677 public function list_files($archivefile) { 678 $extractor = new tgz_extractor($archivefile); 679 return $extractor->list_files(); 680 } 681 682 /** 683 * Checks whether a file appears to be a .tar.gz file. 684 * 685 * @param string|stored_file $archivefile 686 * @return bool True if file contains the gzip magic number 687 */ 688 public static function is_tgz_file($archivefile) { 689 if (is_a($archivefile, 'stored_file')) { 690 $fp = $archivefile->get_content_file_handle(); 691 } else { 692 $fp = fopen($archivefile, 'rb'); 693 } 694 $firstbytes = fread($fp, 2); 695 fclose($fp); 696 return ($firstbytes[0] == "\x1f" && $firstbytes[1] == "\x8b"); 697 } 698 699 /** 700 * The zlib extension is required for this packer to work. This is a single 701 * location for the code to check whether the extension is available. 702 * 703 * @deprecated since 2.7 Always true because zlib extension is now required. 704 * 705 * @return bool True if the zlib extension is available OK 706 */ 707 public static function has_required_extension() { 708 return extension_loaded('zlib'); 709 } 710 } 711 712 713 /** 714 * Handles extraction to pathname. 715 */ 716 class tgz_packer_extract_to_pathname implements tgz_extractor_handler { 717 /** 718 * @var string Target directory for extract. 719 */ 720 protected $pathname; 721 /** 722 * @var array Array of files to extract (other files are skipped). 723 */ 724 protected $onlyfiles; 725 726 /** 727 * Constructor. 728 * 729 * @param string $pathname target directory 730 * @param array $onlyfiles only extract files present in the array 731 */ 732 public function __construct($pathname, array $onlyfiles = null) { 733 $this->pathname = $pathname; 734 $this->onlyfiles = $onlyfiles; 735 } 736 737 /** 738 * @see tgz_extractor_handler::tgz_start_file() 739 */ 740 public function tgz_start_file($archivepath) { 741 // Check file restriction. 742 if ($this->onlyfiles !== null && !in_array($archivepath, $this->onlyfiles)) { 743 return null; 744 } 745 // Ensure directory exists and prepare filename. 746 $fullpath = $this->pathname . '/' . $archivepath; 747 check_dir_exists(dirname($fullpath)); 748 return $fullpath; 749 } 750 751 /** 752 * @see tgz_extractor_handler::tgz_end_file() 753 */ 754 public function tgz_end_file($archivepath, $realpath) { 755 // Do nothing. 756 } 757 758 /** 759 * @see tgz_extractor_handler::tgz_directory() 760 */ 761 public function tgz_directory($archivepath, $mtime) { 762 // Check file restriction. 763 if ($this->onlyfiles !== null && !in_array($archivepath, $this->onlyfiles)) { 764 return false; 765 } 766 // Ensure directory exists. 767 $fullpath = $this->pathname . '/' . $archivepath; 768 check_dir_exists($fullpath); 769 return true; 770 } 771 } 772 773 774 /** 775 * Handles extraction to file storage. 776 */ 777 class tgz_packer_extract_to_storage implements tgz_extractor_handler { 778 /** 779 * @var string Path to temp file. 780 */ 781 protected $tempfile; 782 783 /** 784 * @var int Context id for files. 785 */ 786 protected $contextid; 787 /** 788 * @var string Component name for files. 789 */ 790 protected $component; 791 /** 792 * @var string File area for files. 793 */ 794 protected $filearea; 795 /** 796 * @var int Item ID for files. 797 */ 798 protected $itemid; 799 /** 800 * @var string Base path for files (subfolders will go inside this). 801 */ 802 protected $pathbase; 803 /** 804 * @var int User id for files or null if none. 805 */ 806 protected $userid; 807 808 /** 809 * Constructor. 810 * 811 * @param int $contextid Context id for files. 812 * @param string $component Component name for files. 813 * @param string $filearea File area for files. 814 * @param int $itemid Item ID for files. 815 * @param string $pathbase Base path for files (subfolders will go inside this). 816 * @param int $userid User id for files or null if none. 817 */ 818 public function __construct($contextid, $component, $filearea, $itemid, $pathbase, $userid) { 819 global $CFG; 820 821 // Store all data. 822 $this->contextid = $contextid; 823 $this->component = $component; 824 $this->filearea = $filearea; 825 $this->itemid = $itemid; 826 $this->pathbase = $pathbase; 827 $this->userid = $userid; 828 829 // Obtain temp filename. 830 $tempfolder = $CFG->tempdir . '/core_files'; 831 check_dir_exists($tempfolder); 832 $this->tempfile = tempnam($tempfolder, '.dat'); 833 } 834 835 /** 836 * @see tgz_extractor_handler::tgz_start_file() 837 */ 838 public function tgz_start_file($archivepath) { 839 // All files are stored in the same filename. 840 return $this->tempfile; 841 } 842 843 /** 844 * @see tgz_extractor_handler::tgz_end_file() 845 */ 846 public function tgz_end_file($archivepath, $realpath) { 847 // Place temp file into storage. 848 $fs = get_file_storage(); 849 $filerecord = array('contextid' => $this->contextid, 'component' => $this->component, 850 'filearea' => $this->filearea, 'itemid' => $this->itemid); 851 $filerecord['filepath'] = $this->pathbase . dirname($archivepath) . '/'; 852 $filerecord['filename'] = basename($archivepath); 853 if ($this->userid) { 854 $filerecord['userid'] = $this->userid; 855 } 856 // Delete existing file (if any) and create new one. 857 tgz_packer::delete_existing_file_record($fs, $filerecord); 858 $fs->create_file_from_pathname($filerecord, $this->tempfile); 859 unlink($this->tempfile); 860 } 861 862 /** 863 * @see tgz_extractor_handler::tgz_directory() 864 */ 865 public function tgz_directory($archivepath, $mtime) { 866 // Standardise path. 867 if (!preg_match('~/$~', $archivepath)) { 868 $archivepath .= '/'; 869 } 870 // Create directory if it doesn't already exist. 871 $fs = get_file_storage(); 872 if (!$fs->file_exists($this->contextid, $this->component, $this->filearea, $this->itemid, 873 $this->pathbase . $archivepath, '.')) { 874 $fs->create_directory($this->contextid, $this->component, $this->filearea, $this->itemid, 875 $this->pathbase . $archivepath); 876 } 877 return true; 878 } 879 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |