[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/filestorage/ -> zip_packer.php (source)

   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 zip packer.
  19   *
  20   * @package   core_files
  21   * @copyright 2008 Petr Skoda (http://skodak.org)
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once("$CFG->libdir/filestorage/file_packer.php");
  28  require_once("$CFG->libdir/filestorage/zip_archive.php");
  29  
  30  /**
  31   * Utility class - handles all zipping and unzipping operations.
  32   *
  33   * @package   core_files
  34   * @category  files
  35   * @copyright 2008 Petr Skoda (http://skodak.org)
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class zip_packer extends file_packer {
  39  
  40      /**
  41       * Zip files and store the result in file storage.
  42       *
  43       * @param array $files array with full zip paths (including directory information)
  44       *              as keys (archivepath=>ospathname or archivepath/subdir=>stored_file or archivepath=>array('content_as_string'))
  45       * @param int $contextid context ID
  46       * @param string $component component
  47       * @param string $filearea file area
  48       * @param int $itemid item ID
  49       * @param string $filepath file path
  50       * @param string $filename file name
  51       * @param int $userid user ID
  52       * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
  53       * @param file_progress $progress Progress indicator callback or null if not required
  54       * @return stored_file|bool false if error stored_file instance if ok
  55       */
  56      public function archive_to_storage(array $files, $contextid,
  57              $component, $filearea, $itemid, $filepath, $filename,
  58              $userid = NULL, $ignoreinvalidfiles=true, file_progress $progress = null) {
  59          global $CFG;
  60  
  61          $fs = get_file_storage();
  62  
  63          check_dir_exists($CFG->tempdir.'/zip');
  64          $tmpfile = tempnam($CFG->tempdir.'/zip', 'zipstor');
  65  
  66          if ($result = $this->archive_to_pathname($files, $tmpfile, $ignoreinvalidfiles, $progress)) {
  67              if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
  68                  if (!$file->delete()) {
  69                      @unlink($tmpfile);
  70                      return false;
  71                  }
  72              }
  73              $file_record = new stdClass();
  74              $file_record->contextid = $contextid;
  75              $file_record->component = $component;
  76              $file_record->filearea  = $filearea;
  77              $file_record->itemid    = $itemid;
  78              $file_record->filepath  = $filepath;
  79              $file_record->filename  = $filename;
  80              $file_record->userid    = $userid;
  81              $file_record->mimetype  = 'application/zip';
  82  
  83              $result = $fs->create_file_from_pathname($file_record, $tmpfile);
  84          }
  85          @unlink($tmpfile);
  86          return $result;
  87      }
  88  
  89      /**
  90       * Zip files and store the result in os file.
  91       *
  92       * @param array $files array with zip paths as keys (archivepath=>ospathname or archivepath=>stored_file or archivepath=>array('content_as_string'))
  93       * @param string $archivefile path to target zip file
  94       * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
  95       * @param file_progress $progress Progress indicator callback or null if not required
  96       * @return bool true if file created, false if not
  97       */
  98      public function archive_to_pathname(array $files, $archivefile,
  99              $ignoreinvalidfiles=true, file_progress $progress = null) {
 100          $ziparch = new zip_archive();
 101          if (!$ziparch->open($archivefile, file_archive::OVERWRITE)) {
 102              return false;
 103          }
 104  
 105          $abort = false;
 106          foreach ($files as $archivepath => $file) {
 107              $archivepath = trim($archivepath, '/');
 108  
 109              // Record progress each time around this loop.
 110              if ($progress) {
 111                  $progress->progress();
 112              }
 113  
 114              if (is_null($file)) {
 115                  // Directories have null as content.
 116                  if (!$ziparch->add_directory($archivepath.'/')) {
 117                      debugging("Can not zip '$archivepath' directory", DEBUG_DEVELOPER);
 118                      if (!$ignoreinvalidfiles) {
 119                          $abort = true;
 120                          break;
 121                      }
 122                  }
 123  
 124              } else if (is_string($file)) {
 125                  if (!$this->archive_pathname($ziparch, $archivepath, $file, $progress)) {
 126                      debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
 127                      if (!$ignoreinvalidfiles) {
 128                          $abort = true;
 129                          break;
 130                      }
 131                  }
 132  
 133              } else if (is_array($file)) {
 134                  $content = reset($file);
 135                  if (!$ziparch->add_file_from_string($archivepath, $content)) {
 136                      debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
 137                      if (!$ignoreinvalidfiles) {
 138                          $abort = true;
 139                          break;
 140                      }
 141                  }
 142  
 143              } else {
 144                  if (!$this->archive_stored($ziparch, $archivepath, $file, $progress)) {
 145                      debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
 146                      if (!$ignoreinvalidfiles) {
 147                          $abort = true;
 148                          break;
 149                      }
 150                  }
 151              }
 152          }
 153  
 154          if (!$ziparch->close()) {
 155              @unlink($archivefile);
 156              return false;
 157          }
 158  
 159          if ($abort) {
 160              @unlink($archivefile);
 161              return false;
 162          }
 163  
 164          return true;
 165      }
 166  
 167      /**
 168       * Perform archiving file from stored file.
 169       *
 170       * @param zip_archive $ziparch zip archive instance
 171       * @param string $archivepath file path to archive
 172       * @param stored_file $file stored_file object
 173       * @param file_progress $progress Progress indicator callback or null if not required
 174       * @return bool success
 175       */
 176      private function archive_stored($ziparch, $archivepath, $file, file_progress $progress = null) {
 177          $result = $file->archive_file($ziparch, $archivepath);
 178          if (!$result) {
 179              return false;
 180          }
 181  
 182          if (!$file->is_directory()) {
 183              return true;
 184          }
 185  
 186          $baselength = strlen($file->get_filepath());
 187          $fs = get_file_storage();
 188          $files = $fs->get_directory_files($file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(),
 189                                            $file->get_filepath(), true, true);
 190          foreach ($files as $file) {
 191              // Record progress for each file.
 192              if ($progress) {
 193                  $progress->progress();
 194              }
 195  
 196              $path = $file->get_filepath();
 197              $path = substr($path, $baselength);
 198              $path = $archivepath.'/'.$path;
 199              if (!$file->is_directory()) {
 200                  $path = $path.$file->get_filename();
 201              }
 202              // Ignore result here, partial zipping is ok for now.
 203              $file->archive_file($ziparch, $path);
 204          }
 205  
 206          return true;
 207      }
 208  
 209      /**
 210       * Perform archiving file from file path.
 211       *
 212       * @param zip_archive $ziparch zip archive instance
 213       * @param string $archivepath file path to archive
 214       * @param string $file path name of the file
 215       * @param file_progress $progress Progress indicator callback or null if not required
 216       * @return bool success
 217       */
 218      private function archive_pathname($ziparch, $archivepath, $file,
 219              file_progress $progress = null) {
 220          // Record progress each time this function is called.
 221          if ($progress) {
 222              $progress->progress();
 223          }
 224  
 225          if (!file_exists($file)) {
 226              return false;
 227          }
 228  
 229          if (is_file($file)) {
 230              if (!is_readable($file)) {
 231                  return false;
 232              }
 233              return $ziparch->add_file_from_pathname($archivepath, $file);
 234          }
 235          if (is_dir($file)) {
 236              if ($archivepath !== '') {
 237                  $ziparch->add_directory($archivepath);
 238              }
 239              $files = new DirectoryIterator($file);
 240              foreach ($files as $file) {
 241                  if ($file->isDot()) {
 242                      continue;
 243                  }
 244                  $newpath = $archivepath.'/'.$file->getFilename();
 245                  $this->archive_pathname($ziparch, $newpath, $file->getPathname(), $progress);
 246              }
 247              unset($files); // Release file handles.
 248              return true;
 249          }
 250      }
 251  
 252      /**
 253       * Unzip file to given file path (real OS filesystem), existing files are overwritten.
 254       *
 255       * @todo MDL-31048 localise messages
 256       * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
 257       * @param string $pathname target directory
 258       * @param array $onlyfiles only extract files present in the array. The path to files MUST NOT
 259       *              start with a /. Example: array('myfile.txt', 'directory/anotherfile.txt')
 260       * @param file_progress $progress Progress indicator callback or null if not required
 261       * @return bool|array list of processed files; false if error
 262       */
 263      public function extract_to_pathname($archivefile, $pathname,
 264              array $onlyfiles = null, file_progress $progress = null) {
 265          global $CFG;
 266  
 267          if (!is_string($archivefile)) {
 268              return $archivefile->extract_to_pathname($this, $pathname, $progress);
 269          }
 270  
 271          $processed = array();
 272  
 273          $pathname = rtrim($pathname, '/');
 274          if (!is_readable($archivefile)) {
 275              return false;
 276          }
 277          $ziparch = new zip_archive();
 278          if (!$ziparch->open($archivefile, file_archive::OPEN)) {
 279              return false;
 280          }
 281  
 282          // Get the number of files (approx).
 283          if ($progress) {
 284              $approxmax = $ziparch->estimated_count();
 285              $done = 0;
 286          }
 287  
 288          foreach ($ziparch as $info) {
 289              // Notify progress.
 290              if ($progress) {
 291                  $progress->progress($done, $approxmax);
 292                  $done++;
 293              }
 294  
 295              $size = $info->size;
 296              $name = $info->pathname;
 297  
 298              if ($name === '' or array_key_exists($name, $processed)) {
 299                  // Probably filename collisions caused by filename cleaning/conversion.
 300                  continue;
 301              } else if (is_array($onlyfiles) && !in_array($name, $onlyfiles)) {
 302                  // Skipping files which are not in the list.
 303                  continue;
 304              }
 305  
 306              if ($info->is_directory) {
 307                  $newdir = "$pathname/$name";
 308                  // directory
 309                  if (is_file($newdir) and !unlink($newdir)) {
 310                      $processed[$name] = 'Can not create directory, file already exists'; // TODO: localise
 311                      continue;
 312                  }
 313                  if (is_dir($newdir)) {
 314                      //dir already there
 315                      $processed[$name] = true;
 316                  } else {
 317                      if (mkdir($newdir, $CFG->directorypermissions, true)) {
 318                          $processed[$name] = true;
 319                      } else {
 320                          $processed[$name] = 'Can not create directory'; // TODO: localise
 321                      }
 322                  }
 323                  continue;
 324              }
 325  
 326              $parts = explode('/', trim($name, '/'));
 327              $filename = array_pop($parts);
 328              $newdir = rtrim($pathname.'/'.implode('/', $parts), '/');
 329  
 330              if (!is_dir($newdir)) {
 331                  if (!mkdir($newdir, $CFG->directorypermissions, true)) {
 332                      $processed[$name] = 'Can not create directory'; // TODO: localise
 333                      continue;
 334                  }
 335              }
 336  
 337              $newfile = "$newdir/$filename";
 338              if (!$fp = fopen($newfile, 'wb')) {
 339                  $processed[$name] = 'Can not write target file'; // TODO: localise
 340                  continue;
 341              }
 342              if (!$fz = $ziparch->get_stream($info->index)) {
 343                  $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
 344                  fclose($fp);
 345                  continue;
 346              }
 347  
 348              while (!feof($fz)) {
 349                  $content = fread($fz, 262143);
 350                  fwrite($fp, $content);
 351              }
 352              fclose($fz);
 353              fclose($fp);
 354              if (filesize($newfile) !== $size) {
 355                  $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
 356                  // something went wrong :-(
 357                  @unlink($newfile);
 358                  continue;
 359              }
 360              $processed[$name] = true;
 361          }
 362          $ziparch->close();
 363          return $processed;
 364      }
 365  
 366      /**
 367       * Unzip file to given file path (real OS filesystem), existing files are overwritten.
 368       *
 369       * @todo MDL-31048 localise messages
 370       * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
 371       * @param int $contextid context ID
 372       * @param string $component component
 373       * @param string $filearea file area
 374       * @param int $itemid item ID
 375       * @param string $pathbase file path
 376       * @param int $userid user ID
 377       * @param file_progress $progress Progress indicator callback or null if not required
 378       * @return array|bool list of processed files; false if error
 379       */
 380      public function extract_to_storage($archivefile, $contextid,
 381              $component, $filearea, $itemid, $pathbase, $userid = NULL,
 382              file_progress $progress = null) {
 383          global $CFG;
 384  
 385          if (!is_string($archivefile)) {
 386              return $archivefile->extract_to_storage($this, $contextid, $component,
 387                      $filearea, $itemid, $pathbase, $userid, $progress);
 388          }
 389  
 390          check_dir_exists($CFG->tempdir.'/zip');
 391  
 392          $pathbase = trim($pathbase, '/');
 393          $pathbase = ($pathbase === '') ? '/' : '/'.$pathbase.'/';
 394          $fs = get_file_storage();
 395  
 396          $processed = array();
 397  
 398          $ziparch = new zip_archive();
 399          if (!$ziparch->open($archivefile, file_archive::OPEN)) {
 400              return false;
 401          }
 402  
 403          // Get the number of files (approx).
 404          if ($progress) {
 405              $approxmax = $ziparch->estimated_count();
 406              $done = 0;
 407          }
 408  
 409          foreach ($ziparch as $info) {
 410              // Notify progress.
 411              if ($progress) {
 412                  $progress->progress($done, $approxmax);
 413                  $done++;
 414              }
 415  
 416              $size = $info->size;
 417              $name = $info->pathname;
 418  
 419              if ($name === '' or array_key_exists($name, $processed)) {
 420                  //probably filename collisions caused by filename cleaning/conversion
 421                  continue;
 422              }
 423  
 424              if ($info->is_directory) {
 425                  $newfilepath = $pathbase.$name.'/';
 426                  $fs->create_directory($contextid, $component, $filearea, $itemid, $newfilepath, $userid);
 427                  $processed[$name] = true;
 428                  continue;
 429              }
 430  
 431              $parts = explode('/', trim($name, '/'));
 432              $filename = array_pop($parts);
 433              $filepath = $pathbase;
 434              if ($parts) {
 435                  $filepath .= implode('/', $parts).'/';
 436              }
 437  
 438              if ($size < 2097151) {
 439                  // Small file.
 440                  if (!$fz = $ziparch->get_stream($info->index)) {
 441                      $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
 442                      continue;
 443                  }
 444                  $content = '';
 445                  while (!feof($fz)) {
 446                      $content .= fread($fz, 262143);
 447                  }
 448                  fclose($fz);
 449                  if (strlen($content) !== $size) {
 450                      $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
 451                      // something went wrong :-(
 452                      unset($content);
 453                      continue;
 454                  }
 455  
 456                  if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
 457                      if (!$file->delete()) {
 458                          $processed[$name] = 'Can not delete existing file'; // TODO: localise
 459                          continue;
 460                      }
 461                  }
 462                  $file_record = new stdClass();
 463                  $file_record->contextid = $contextid;
 464                  $file_record->component = $component;
 465                  $file_record->filearea  = $filearea;
 466                  $file_record->itemid    = $itemid;
 467                  $file_record->filepath  = $filepath;
 468                  $file_record->filename  = $filename;
 469                  $file_record->userid    = $userid;
 470                  if ($fs->create_file_from_string($file_record, $content)) {
 471                      $processed[$name] = true;
 472                  } else {
 473                      $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
 474                  }
 475                  unset($content);
 476                  continue;
 477  
 478              } else {
 479                  // large file, would not fit into memory :-(
 480                  $tmpfile = tempnam($CFG->tempdir.'/zip', 'unzip');
 481                  if (!$fp = fopen($tmpfile, 'wb')) {
 482                      @unlink($tmpfile);
 483                      $processed[$name] = 'Can not write temp file'; // TODO: localise
 484                      continue;
 485                  }
 486                  if (!$fz = $ziparch->get_stream($info->index)) {
 487                      @unlink($tmpfile);
 488                      $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
 489                      continue;
 490                  }
 491                  while (!feof($fz)) {
 492                      $content = fread($fz, 262143);
 493                      fwrite($fp, $content);
 494                  }
 495                  fclose($fz);
 496                  fclose($fp);
 497                  if (filesize($tmpfile) !== $size) {
 498                      $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
 499                      // something went wrong :-(
 500                      @unlink($tmpfile);
 501                      continue;
 502                  }
 503  
 504                  if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
 505                      if (!$file->delete()) {
 506                          @unlink($tmpfile);
 507                          $processed[$name] = 'Can not delete existing file'; // TODO: localise
 508                          continue;
 509                      }
 510                  }
 511                  $file_record = new stdClass();
 512                  $file_record->contextid = $contextid;
 513                  $file_record->component = $component;
 514                  $file_record->filearea  = $filearea;
 515                  $file_record->itemid    = $itemid;
 516                  $file_record->filepath  = $filepath;
 517                  $file_record->filename  = $filename;
 518                  $file_record->userid    = $userid;
 519                  if ($fs->create_file_from_pathname($file_record, $tmpfile)) {
 520                      $processed[$name] = true;
 521                  } else {
 522                      $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
 523                  }
 524                  @unlink($tmpfile);
 525                  continue;
 526              }
 527          }
 528          $ziparch->close();
 529          return $processed;
 530      }
 531  
 532      /**
 533       * Returns array of info about all files in archive.
 534       *
 535       * @param string|file_archive $archivefile
 536       * @return array of file infos
 537       */
 538      public function list_files($archivefile) {
 539          if (!is_string($archivefile)) {
 540              return $archivefile->list_files();
 541          }
 542  
 543          $ziparch = new zip_archive();
 544          if (!$ziparch->open($archivefile, file_archive::OPEN)) {
 545              return false;
 546          }
 547          $list = $ziparch->list_files();
 548          $ziparch->close();
 549          return $list;
 550      }
 551  
 552  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1