3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
20 * Implementation of zip packer.
23 * @subpackage filestorage
24 * @copyright 2008 Petr Skoda (http://skodak.org)
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 defined('MOODLE_INTERNAL') ||
die();
30 require_once("$CFG->libdir/filestorage/file_packer.php");
31 require_once("$CFG->libdir/filestorage/zip_archive.php");
34 * Utility class - handles all zipping and unzipping operations.
37 * @subpackage filestorage
38 * @copyright 2008 Petr Skoda (http://skodak.org)
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 class zip_packer
extends file_packer
{
44 * Zip files and store the result in file storage
45 * @param array $files array with full zip paths (including directory information)
46 * as keys (archivepath=>ospathname or archivepath/subdir=>stored_file or archivepath=>array('content_as_string'))
47 * @param int $contextid
48 * @param string $component
49 * @param string $filearea
51 * @param string $filepath
52 * @param string $filename
53 * @return mixed false if error stored file instance if ok
55 public function archive_to_storage($files, $contextid, $component, $filearea, $itemid, $filepath, $filename, $userid = NULL) {
58 $fs = get_file_storage();
60 check_dir_exists($CFG->tempdir
.'/zip');
61 $tmpfile = tempnam($CFG->tempdir
.'/zip', 'zipstor');
63 if ($result = $this->archive_to_pathname($files, $tmpfile)) {
64 if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
65 if (!$file->delete()) {
70 $file_record = new stdClass();
71 $file_record->contextid
= $contextid;
72 $file_record->component
= $component;
73 $file_record->filearea
= $filearea;
74 $file_record->itemid
= $itemid;
75 $file_record->filepath
= $filepath;
76 $file_record->filename
= $filename;
77 $file_record->userid
= $userid;
78 $file_record->mimetype
= 'application/zip';
80 $result = $fs->create_file_from_pathname($file_record, $tmpfile);
87 * Zip files and store the result in os file
88 * @param array $files array with zip paths as keys (archivepath=>ospathname or archivepath=>stored_file or archivepath=>array('content_as_string'))
89 * @param string $archivefile path to target zip file
90 * @return bool success
92 public function archive_to_pathname($files, $archivefile) {
93 if (!is_array($files)) {
97 $ziparch = new zip_archive();
98 if (!$ziparch->open($archivefile, file_archive
::OVERWRITE
)) {
102 foreach ($files as $archivepath => $file) {
103 $archivepath = trim($archivepath, '/');
105 if (is_null($file)) {
106 // empty directories have null as content
107 $ziparch->add_directory($archivepath.'/');
109 } else if (is_string($file)) {
110 $this->archive_pathname($ziparch, $archivepath, $file);
112 } else if (is_array($file)) {
113 $content = reset($file);
114 $ziparch->add_file_from_string($archivepath, $content);
117 $this->archive_stored($ziparch, $archivepath, $file);
121 return $ziparch->close();
124 private function archive_stored($ziparch, $archivepath, $file) {
125 $file->archive_file($ziparch, $archivepath);
127 if (!$file->is_directory()) {
131 $baselength = strlen($file->get_filepath());
132 $fs = get_file_storage();
133 $files = $fs->get_directory_files($file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(),
134 $file->get_filepath(), true, true);
135 foreach ($files as $file) {
136 $path = $file->get_filepath();
137 $path = substr($path, $baselength);
138 $path = $archivepath.'/'.$path;
139 if (!$file->is_directory()) {
140 $path = $path.$file->get_filename();
142 $file->archive_file($ziparch, $path);
146 private function archive_pathname($ziparch, $archivepath, $file) {
147 if (!file_exists($file)) {
151 if (is_file($file)) {
152 if (!is_readable($file)) {
155 $ziparch->add_file_from_pathname($archivepath, $file);
159 if ($archivepath !== '') {
160 $ziparch->add_directory($archivepath);
162 $files = new DirectoryIterator($file);
163 foreach ($files as $file) {
164 if ($file->isDot()) {
167 $newpath = $archivepath.'/'.$file->getFilename();
168 $this->archive_pathname($ziparch, $newpath, $file->getPathname());
170 unset($files); //release file handles
176 * Unzip file to given file path (real OS filesystem), existing files are overwrited
177 * @param mixed $archivefile full pathname of zip file or stored_file instance
178 * @param string $pathname target directory
179 * @return mixed list of processed files; false if error
181 public function extract_to_pathname($archivefile, $pathname) {
184 if (!is_string($archivefile)) {
185 return $archivefile->extract_to_pathname($this, $pathname);
188 $processed = array();
190 $pathname = rtrim($pathname, '/');
191 if (!is_readable($archivefile)) {
194 $ziparch = new zip_archive();
195 if (!$ziparch->open($archivefile, file_archive
::OPEN
)) {
199 foreach ($ziparch as $info) {
201 $name = $info->pathname
;
203 if ($name === '' or array_key_exists($name, $processed)) {
204 //probably filename collisions caused by filename cleaning/conversion
208 if ($info->is_directory
) {
209 $newdir = "$pathname/$name";
211 if (is_file($newdir) and !unlink($newdir)) {
212 $processed[$name] = 'Can not create directory, file already exists'; // TODO: localise
215 if (is_dir($newdir)) {
217 $processed[$name] = true;
219 if (mkdir($newdir, $CFG->directorypermissions
, true)) {
220 $processed[$name] = true;
222 $processed[$name] = 'Can not create directory'; // TODO: localise
228 $parts = explode('/', trim($name, '/'));
229 $filename = array_pop($parts);
230 $newdir = rtrim($pathname.'/'.implode('/', $parts), '/');
232 if (!is_dir($newdir)) {
233 if (!mkdir($newdir, $CFG->directorypermissions
, true)) {
234 $processed[$name] = 'Can not create directory'; // TODO: localise
239 $newfile = "$newdir/$filename";
240 if (!$fp = fopen($newfile, 'wb')) {
241 $processed[$name] = 'Can not write target file'; // TODO: localise
244 if (!$fz = $ziparch->get_stream($info->index
)) {
245 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
251 $content = fread($fz, 262143);
252 fwrite($fp, $content);
256 if (filesize($newfile) !== $size) {
257 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
258 // something went wrong :-(
262 $processed[$name] = true;
269 * Unzip file to given file path (real OS filesystem), existing files are overwrited
270 * @param mixed $archivefile full pathname of zip file or stored_file instance
271 * @param int $contextid
272 * @param string $component
273 * @param string $filearea
275 * @param string $filepath
276 * @return mixed list of processed files; false if error
278 public function extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
281 if (!is_string($archivefile)) {
282 return $archivefile->extract_to_storage($this, $contextid, $component, $filearea, $itemid, $pathbase, $userid);
285 check_dir_exists($CFG->tempdir
.'/zip');
287 $pathbase = trim($pathbase, '/');
288 $pathbase = ($pathbase === '') ?
'/' : '/'.$pathbase.'/';
289 $fs = get_file_storage();
291 $processed = array();
293 $ziparch = new zip_archive();
294 if (!$ziparch->open($archivefile, file_archive
::OPEN
)) {
298 foreach ($ziparch as $info) {
300 $name = $info->pathname
;
302 if ($name === '' or array_key_exists($name, $processed)) {
303 //probably filename collisions caused by filename cleaning/conversion
307 if ($info->is_directory
) {
308 $newfilepath = $pathbase.$name.'/';
309 $fs->create_directory($contextid, $component, $filearea, $itemid, $newfilepath, $userid);
310 $processed[$name] = true;
314 $parts = explode('/', trim($name, '/'));
315 $filename = array_pop($parts);
316 $filepath = $pathbase;
318 $filepath .= implode('/', $parts).'/';
321 if ($size < 2097151) {
323 if (!$fz = $ziparch->get_stream($info->index
)) {
324 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
329 $content .= fread($fz, 262143);
332 if (strlen($content) !== $size) {
333 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
334 // something went wrong :-(
339 if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
340 if (!$file->delete()) {
341 $processed[$name] = 'Can not delete existing file'; // TODO: localise
345 $file_record = new stdClass();
346 $file_record->contextid
= $contextid;
347 $file_record->component
= $component;
348 $file_record->filearea
= $filearea;
349 $file_record->itemid
= $itemid;
350 $file_record->filepath
= $filepath;
351 $file_record->filename
= $filename;
352 $file_record->userid
= $userid;
353 if ($fs->create_file_from_string($file_record, $content)) {
354 $processed[$name] = true;
356 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
362 // large file, would not fit into memory :-(
363 $tmpfile = tempnam($CFG->tempdir
.'/zip', 'unzip');
364 if (!$fp = fopen($tmpfile, 'wb')) {
366 $processed[$name] = 'Can not write temp file'; // TODO: localise
369 if (!$fz = $ziparch->get_stream($info->index
)) {
371 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
375 $content = fread($fz, 262143);
376 fwrite($fp, $content);
380 if (filesize($tmpfile) !== $size) {
381 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
382 // something went wrong :-(
387 if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
388 if (!$file->delete()) {
390 $processed[$name] = 'Can not delete existing file'; // TODO: localise
394 $file_record = new stdClass();
395 $file_record->contextid
= $contextid;
396 $file_record->component
= $component;
397 $file_record->filearea
= $filearea;
398 $file_record->itemid
= $itemid;
399 $file_record->filepath
= $filepath;
400 $file_record->filename
= $filename;
401 $file_record->userid
= $userid;
402 if ($fs->create_file_from_pathname($file_record, $tmpfile)) {
403 $processed[$name] = true;
405 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
416 * Returns array of info about all files in archive
417 * @return array of file infos
419 public function list_files($archivefile) {
420 if (!is_string($archivefile)) {
421 return $archivefile->list_files();
424 $ziparch = new zip_archive();
425 if (!$ziparch->open($archivefile, file_archive
::OPEN
)) {
428 $list = $ziparch->list_files();