You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
566 lines
21 KiB
566 lines
21 KiB
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Implementation of zip packer.
|
|
*
|
|
* @package core_files
|
|
* @copyright 2008 Petr Skoda (http://skodak.org)
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
require_once("$CFG->libdir/filestorage/file_packer.php");
|
|
require_once("$CFG->libdir/filestorage/zip_archive.php");
|
|
|
|
/**
|
|
* Utility class - handles all zipping and unzipping operations.
|
|
*
|
|
* @package core_files
|
|
* @category files
|
|
* @copyright 2008 Petr Skoda (http://skodak.org)
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class zip_packer extends file_packer {
|
|
|
|
/**
|
|
* Zip files and store the result in file storage.
|
|
*
|
|
* @param array $files array with full zip paths (including directory information)
|
|
* as keys (archivepath=>ospathname or archivepath/subdir=>stored_file or archivepath=>array('content_as_string'))
|
|
* @param int $contextid context ID
|
|
* @param string $component component
|
|
* @param string $filearea file area
|
|
* @param int $itemid item ID
|
|
* @param string $filepath file path
|
|
* @param string $filename file name
|
|
* @param int $userid user ID
|
|
* @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @return stored_file|bool false if error stored_file instance if ok
|
|
*/
|
|
public function archive_to_storage(array $files, $contextid,
|
|
$component, $filearea, $itemid, $filepath, $filename,
|
|
$userid = NULL, $ignoreinvalidfiles=true, file_progress $progress = null) {
|
|
global $CFG;
|
|
|
|
$fs = get_file_storage();
|
|
|
|
check_dir_exists($CFG->tempdir.'/zip');
|
|
$tmpfile = tempnam($CFG->tempdir.'/zip', 'zipstor');
|
|
|
|
if ($result = $this->archive_to_pathname($files, $tmpfile, $ignoreinvalidfiles, $progress)) {
|
|
if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
|
|
if (!$file->delete()) {
|
|
@unlink($tmpfile);
|
|
return false;
|
|
}
|
|
}
|
|
$file_record = new stdClass();
|
|
$file_record->contextid = $contextid;
|
|
$file_record->component = $component;
|
|
$file_record->filearea = $filearea;
|
|
$file_record->itemid = $itemid;
|
|
$file_record->filepath = $filepath;
|
|
$file_record->filename = $filename;
|
|
$file_record->userid = $userid;
|
|
$file_record->mimetype = 'application/zip';
|
|
|
|
$result = $fs->create_file_from_pathname($file_record, $tmpfile);
|
|
}
|
|
@unlink($tmpfile);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Zip files and store the result in os file.
|
|
*
|
|
* @param array $files array with zip paths as keys (archivepath=>ospathname or archivepath=>stored_file or archivepath=>array('content_as_string'))
|
|
* @param string $archivefile path to target zip file
|
|
* @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @return bool true if file created, false if not
|
|
*/
|
|
public function archive_to_pathname(array $files, $archivefile,
|
|
$ignoreinvalidfiles=true, file_progress $progress = null) {
|
|
$ziparch = new zip_archive();
|
|
if (!$ziparch->open($archivefile, file_archive::OVERWRITE)) {
|
|
return false;
|
|
}
|
|
|
|
$abort = false;
|
|
foreach ($files as $archivepath => $file) {
|
|
$archivepath = trim($archivepath, '/');
|
|
|
|
// Record progress each time around this loop.
|
|
if ($progress) {
|
|
$progress->progress();
|
|
}
|
|
|
|
if (is_null($file)) {
|
|
// Directories have null as content.
|
|
if (!$ziparch->add_directory($archivepath.'/')) {
|
|
debugging("Can not zip '$archivepath' directory", DEBUG_DEVELOPER);
|
|
if (!$ignoreinvalidfiles) {
|
|
$abort = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else if (is_string($file)) {
|
|
if (!$this->archive_pathname($ziparch, $archivepath, $file, $progress)) {
|
|
debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
|
|
if (!$ignoreinvalidfiles) {
|
|
$abort = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else if (is_array($file)) {
|
|
$content = reset($file);
|
|
if (!$ziparch->add_file_from_string($archivepath, $content)) {
|
|
debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
|
|
if (!$ignoreinvalidfiles) {
|
|
$abort = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (!$this->archive_stored($ziparch, $archivepath, $file, $progress)) {
|
|
debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
|
|
if (!$ignoreinvalidfiles) {
|
|
$abort = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$ziparch->close()) {
|
|
@unlink($archivefile);
|
|
return false;
|
|
}
|
|
|
|
if ($abort) {
|
|
@unlink($archivefile);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Perform archiving file from stored file.
|
|
*
|
|
* @param zip_archive $ziparch zip archive instance
|
|
* @param string $archivepath file path to archive
|
|
* @param stored_file $file stored_file object
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @return bool success
|
|
*/
|
|
private function archive_stored($ziparch, $archivepath, $file, file_progress $progress = null) {
|
|
$result = $file->archive_file($ziparch, $archivepath);
|
|
if (!$result) {
|
|
return false;
|
|
}
|
|
|
|
if (!$file->is_directory()) {
|
|
return true;
|
|
}
|
|
|
|
$baselength = strlen($file->get_filepath());
|
|
$fs = get_file_storage();
|
|
$files = $fs->get_directory_files($file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(),
|
|
$file->get_filepath(), true, true);
|
|
foreach ($files as $file) {
|
|
// Record progress for each file.
|
|
if ($progress) {
|
|
$progress->progress();
|
|
}
|
|
|
|
$path = $file->get_filepath();
|
|
$path = substr($path, $baselength);
|
|
$path = $archivepath.'/'.$path;
|
|
if (!$file->is_directory()) {
|
|
$path = $path.$file->get_filename();
|
|
}
|
|
// Ignore result here, partial zipping is ok for now.
|
|
$file->archive_file($ziparch, $path);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Perform archiving file from file path.
|
|
*
|
|
* @param zip_archive $ziparch zip archive instance
|
|
* @param string $archivepath file path to archive
|
|
* @param string $file path name of the file
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @return bool success
|
|
*/
|
|
private function archive_pathname($ziparch, $archivepath, $file,
|
|
file_progress $progress = null) {
|
|
// Record progress each time this function is called.
|
|
if ($progress) {
|
|
$progress->progress();
|
|
}
|
|
|
|
if (!file_exists($file)) {
|
|
return false;
|
|
}
|
|
|
|
if (is_file($file)) {
|
|
if (!is_readable($file)) {
|
|
return false;
|
|
}
|
|
return $ziparch->add_file_from_pathname($archivepath, $file);
|
|
}
|
|
if (is_dir($file)) {
|
|
if ($archivepath !== '') {
|
|
$ziparch->add_directory($archivepath);
|
|
}
|
|
$files = new DirectoryIterator($file);
|
|
foreach ($files as $file) {
|
|
if ($file->isDot()) {
|
|
continue;
|
|
}
|
|
$newpath = $archivepath.'/'.$file->getFilename();
|
|
$this->archive_pathname($ziparch, $newpath, $file->getPathname(), $progress);
|
|
}
|
|
unset($files); // Release file handles.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unzip file to given file path (real OS filesystem), existing files are overwritten.
|
|
*
|
|
* @todo MDL-31048 localise messages
|
|
* @param string|stored_file $archivefile full pathname of zip file or stored_file instance
|
|
* @param string $pathname target directory
|
|
* @param array $onlyfiles only extract files present in the array. The path to files MUST NOT
|
|
* start with a /. Example: array('myfile.txt', 'directory/anotherfile.txt')
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @param bool $returnbool Whether to return a basic true/false indicating error state, or full per-file error
|
|
* details.
|
|
* @return bool|array list of processed files; false if error
|
|
*/
|
|
public function extract_to_pathname($archivefile, $pathname,
|
|
array $onlyfiles = null, file_progress $progress = null, $returnbool = false) {
|
|
global $CFG;
|
|
|
|
if (!is_string($archivefile)) {
|
|
return $archivefile->extract_to_pathname($this, $pathname, $progress);
|
|
}
|
|
|
|
$processed = array();
|
|
$success = true;
|
|
|
|
$pathname = rtrim($pathname, '/');
|
|
if (!is_readable($archivefile)) {
|
|
return false;
|
|
}
|
|
$ziparch = new zip_archive();
|
|
if (!$ziparch->open($archivefile, file_archive::OPEN)) {
|
|
return false;
|
|
}
|
|
|
|
// Get the number of files (approx).
|
|
if ($progress) {
|
|
$approxmax = $ziparch->estimated_count();
|
|
$done = 0;
|
|
}
|
|
|
|
foreach ($ziparch as $info) {
|
|
// Notify progress.
|
|
if ($progress) {
|
|
$progress->progress($done, $approxmax);
|
|
$done++;
|
|
}
|
|
|
|
$size = $info->size;
|
|
$name = $info->pathname;
|
|
|
|
if ($name === '' or array_key_exists($name, $processed)) {
|
|
// Probably filename collisions caused by filename cleaning/conversion.
|
|
continue;
|
|
} else if (is_array($onlyfiles) && !in_array($name, $onlyfiles)) {
|
|
// Skipping files which are not in the list.
|
|
continue;
|
|
}
|
|
|
|
if ($info->is_directory) {
|
|
$newdir = "$pathname/$name";
|
|
// directory
|
|
if (is_file($newdir) and !unlink($newdir)) {
|
|
$processed[$name] = 'Can not create directory, file already exists'; // TODO: localise
|
|
$success = false;
|
|
continue;
|
|
}
|
|
if (is_dir($newdir)) {
|
|
//dir already there
|
|
$processed[$name] = true;
|
|
} else {
|
|
if (mkdir($newdir, $CFG->directorypermissions, true)) {
|
|
$processed[$name] = true;
|
|
} else {
|
|
$processed[$name] = 'Can not create directory'; // TODO: localise
|
|
$success = false;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$parts = explode('/', trim($name, '/'));
|
|
$filename = array_pop($parts);
|
|
$newdir = rtrim($pathname.'/'.implode('/', $parts), '/');
|
|
|
|
if (!is_dir($newdir)) {
|
|
if (!mkdir($newdir, $CFG->directorypermissions, true)) {
|
|
$processed[$name] = 'Can not create directory'; // TODO: localise
|
|
$success = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$newfile = "$newdir/$filename";
|
|
if (!$fp = fopen($newfile, 'wb')) {
|
|
$processed[$name] = 'Can not write target file'; // TODO: localise
|
|
$success = false;
|
|
continue;
|
|
}
|
|
if (!$fz = $ziparch->get_stream($info->index)) {
|
|
$processed[$name] = 'Can not read file from zip archive'; // TODO: localise
|
|
$success = false;
|
|
fclose($fp);
|
|
continue;
|
|
}
|
|
|
|
while (!feof($fz)) {
|
|
$content = fread($fz, 262143);
|
|
fwrite($fp, $content);
|
|
}
|
|
fclose($fz);
|
|
fclose($fp);
|
|
if (filesize($newfile) !== $size) {
|
|
$processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
|
|
$success = false;
|
|
// something went wrong :-(
|
|
@unlink($newfile);
|
|
continue;
|
|
}
|
|
$processed[$name] = true;
|
|
}
|
|
$ziparch->close();
|
|
|
|
if ($returnbool) {
|
|
return $success;
|
|
} else {
|
|
return $processed;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unzip file to given file path (real OS filesystem), existing files are overwritten.
|
|
*
|
|
* @todo MDL-31048 localise messages
|
|
* @param string|stored_file $archivefile full pathname of zip file or stored_file instance
|
|
* @param int $contextid context ID
|
|
* @param string $component component
|
|
* @param string $filearea file area
|
|
* @param int $itemid item ID
|
|
* @param string $pathbase file path
|
|
* @param int $userid user ID
|
|
* @param file_progress $progress Progress indicator callback or null if not required
|
|
* @return array|bool list of processed files; false if error
|
|
*/
|
|
public function extract_to_storage($archivefile, $contextid,
|
|
$component, $filearea, $itemid, $pathbase, $userid = NULL,
|
|
file_progress $progress = null) {
|
|
global $CFG;
|
|
|
|
if (!is_string($archivefile)) {
|
|
return $archivefile->extract_to_storage($this, $contextid, $component,
|
|
$filearea, $itemid, $pathbase, $userid, $progress);
|
|
}
|
|
|
|
check_dir_exists($CFG->tempdir.'/zip');
|
|
|
|
$pathbase = trim($pathbase, '/');
|
|
$pathbase = ($pathbase === '') ? '/' : '/'.$pathbase.'/';
|
|
$fs = get_file_storage();
|
|
|
|
$processed = array();
|
|
|
|
$ziparch = new zip_archive();
|
|
if (!$ziparch->open($archivefile, file_archive::OPEN)) {
|
|
return false;
|
|
}
|
|
|
|
// Get the number of files (approx).
|
|
if ($progress) {
|
|
$approxmax = $ziparch->estimated_count();
|
|
$done = 0;
|
|
}
|
|
|
|
foreach ($ziparch as $info) {
|
|
// Notify progress.
|
|
if ($progress) {
|
|
$progress->progress($done, $approxmax);
|
|
$done++;
|
|
}
|
|
|
|
$size = $info->size;
|
|
$name = $info->pathname;
|
|
|
|
if ($name === '' or array_key_exists($name, $processed)) {
|
|
//probably filename collisions caused by filename cleaning/conversion
|
|
continue;
|
|
}
|
|
|
|
if ($info->is_directory) {
|
|
$newfilepath = $pathbase.$name.'/';
|
|
$fs->create_directory($contextid, $component, $filearea, $itemid, $newfilepath, $userid);
|
|
$processed[$name] = true;
|
|
continue;
|
|
}
|
|
|
|
$parts = explode('/', trim($name, '/'));
|
|
$filename = array_pop($parts);
|
|
$filepath = $pathbase;
|
|
if ($parts) {
|
|
$filepath .= implode('/', $parts).'/';
|
|
}
|
|
|
|
if ($size < 2097151) {
|
|
// Small file.
|
|
if (!$fz = $ziparch->get_stream($info->index)) {
|
|
$processed[$name] = 'Can not read file from zip archive'; // TODO: localise
|
|
continue;
|
|
}
|
|
$content = '';
|
|
while (!feof($fz)) {
|
|
$content .= fread($fz, 262143);
|
|
}
|
|
fclose($fz);
|
|
if (strlen($content) !== $size) {
|
|
$processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
|
|
// something went wrong :-(
|
|
unset($content);
|
|
continue;
|
|
}
|
|
|
|
if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
|
|
if (!$file->delete()) {
|
|
$processed[$name] = 'Can not delete existing file'; // TODO: localise
|
|
continue;
|
|
}
|
|
}
|
|
$file_record = new stdClass();
|
|
$file_record->contextid = $contextid;
|
|
$file_record->component = $component;
|
|
$file_record->filearea = $filearea;
|
|
$file_record->itemid = $itemid;
|
|
$file_record->filepath = $filepath;
|
|
$file_record->filename = $filename;
|
|
$file_record->userid = $userid;
|
|
if ($fs->create_file_from_string($file_record, $content)) {
|
|
$processed[$name] = true;
|
|
} else {
|
|
$processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
|
|
}
|
|
unset($content);
|
|
continue;
|
|
|
|
} else {
|
|
// large file, would not fit into memory :-(
|
|
$tmpfile = tempnam($CFG->tempdir.'/zip', 'unzip');
|
|
if (!$fp = fopen($tmpfile, 'wb')) {
|
|
@unlink($tmpfile);
|
|
$processed[$name] = 'Can not write temp file'; // TODO: localise
|
|
continue;
|
|
}
|
|
if (!$fz = $ziparch->get_stream($info->index)) {
|
|
@unlink($tmpfile);
|
|
$processed[$name] = 'Can not read file from zip archive'; // TODO: localise
|
|
continue;
|
|
}
|
|
while (!feof($fz)) {
|
|
$content = fread($fz, 262143);
|
|
fwrite($fp, $content);
|
|
}
|
|
fclose($fz);
|
|
fclose($fp);
|
|
if (filesize($tmpfile) !== $size) {
|
|
$processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
|
|
// something went wrong :-(
|
|
@unlink($tmpfile);
|
|
continue;
|
|
}
|
|
|
|
if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
|
|
if (!$file->delete()) {
|
|
@unlink($tmpfile);
|
|
$processed[$name] = 'Can not delete existing file'; // TODO: localise
|
|
continue;
|
|
}
|
|
}
|
|
$file_record = new stdClass();
|
|
$file_record->contextid = $contextid;
|
|
$file_record->component = $component;
|
|
$file_record->filearea = $filearea;
|
|
$file_record->itemid = $itemid;
|
|
$file_record->filepath = $filepath;
|
|
$file_record->filename = $filename;
|
|
$file_record->userid = $userid;
|
|
if ($fs->create_file_from_pathname($file_record, $tmpfile)) {
|
|
$processed[$name] = true;
|
|
} else {
|
|
$processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
|
|
}
|
|
@unlink($tmpfile);
|
|
continue;
|
|
}
|
|
}
|
|
$ziparch->close();
|
|
return $processed;
|
|
}
|
|
|
|
/**
|
|
* Returns array of info about all files in archive.
|
|
*
|
|
* @param string|file_archive $archivefile
|
|
* @return array of file infos
|
|
*/
|
|
public function list_files($archivefile) {
|
|
if (!is_string($archivefile)) {
|
|
return $archivefile->list_files();
|
|
}
|
|
|
|
$ziparch = new zip_archive();
|
|
if (!$ziparch->open($archivefile, file_archive::OPEN)) {
|
|
return false;
|
|
}
|
|
$list = $ziparch->list_files();
|
|
$ziparch->close();
|
|
return $list;
|
|
}
|
|
|
|
}
|
|
|