Merge branch 'MDL-29847_22' of git://github.com/timhunt/moodle into MOODLE_22_STABLE
[moodle.git] / lib / filestorage / zip_packer.php
blobec008e7f0258790501d81920f789a3525fdaafb0
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
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.
9 //
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/>.
19 /**
20 * Implementation of zip packer.
22 * @package core
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");
33 /**
34 * Utility class - handles all zipping and unzipping operations.
36 * @package core
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 {
43 /**
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
50 * @param int $itemid
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) {
56 global $CFG;
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()) {
66 @unlink($tmpfile);
67 return false;
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);
82 @unlink($tmpfile);
83 return $result;
86 /**
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)) {
94 return false;
97 $ziparch = new zip_archive();
98 if (!$ziparch->open($archivefile, file_archive::OVERWRITE)) {
99 return false;
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);
116 } else {
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()) {
128 return;
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)) {
148 return;
151 if (is_file($file)) {
152 if (!is_readable($file)) {
153 return;
155 $ziparch->add_file_from_pathname($archivepath, $file);
156 return;
158 if (is_dir($file)) {
159 if ($archivepath !== '') {
160 $ziparch->add_directory($archivepath);
162 $files = new DirectoryIterator($file);
163 foreach ($files as $file) {
164 if ($file->isDot()) {
165 continue;
167 $newpath = $archivepath.'/'.$file->getFilename();
168 $this->archive_pathname($ziparch, $newpath, $file->getPathname());
170 unset($files); //release file handles
171 return;
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) {
182 global $CFG;
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)) {
192 return false;
194 $ziparch = new zip_archive();
195 if (!$ziparch->open($archivefile, file_archive::OPEN)) {
196 return false;
199 foreach ($ziparch as $info) {
200 $size = $info->size;
201 $name = $info->pathname;
203 if ($name === '' or array_key_exists($name, $processed)) {
204 //probably filename collisions caused by filename cleaning/conversion
205 continue;
208 if ($info->is_directory) {
209 $newdir = "$pathname/$name";
210 // directory
211 if (is_file($newdir) and !unlink($newdir)) {
212 $processed[$name] = 'Can not create directory, file already exists'; // TODO: localise
213 continue;
215 if (is_dir($newdir)) {
216 //dir already there
217 $processed[$name] = true;
218 } else {
219 if (mkdir($newdir, $CFG->directorypermissions, true)) {
220 $processed[$name] = true;
221 } else {
222 $processed[$name] = 'Can not create directory'; // TODO: localise
225 continue;
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
235 continue;
239 $newfile = "$newdir/$filename";
240 if (!$fp = fopen($newfile, 'wb')) {
241 $processed[$name] = 'Can not write target file'; // TODO: localise
242 continue;
244 if (!$fz = $ziparch->get_stream($info->index)) {
245 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
246 fclose($fp);
247 continue;
250 while (!feof($fz)) {
251 $content = fread($fz, 262143);
252 fwrite($fp, $content);
254 fclose($fz);
255 fclose($fp);
256 if (filesize($newfile) !== $size) {
257 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
258 // something went wrong :-(
259 @unlink($newfile);
260 continue;
262 $processed[$name] = true;
264 $ziparch->close();
265 return $processed;
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
274 * @param int $itemid
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) {
279 global $CFG;
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)) {
295 return false;
298 foreach ($ziparch as $info) {
299 $size = $info->size;
300 $name = $info->pathname;
302 if ($name === '' or array_key_exists($name, $processed)) {
303 //probably filename collisions caused by filename cleaning/conversion
304 continue;
307 if ($info->is_directory) {
308 $newfilepath = $pathbase.$name.'/';
309 $fs->create_directory($contextid, $component, $filearea, $itemid, $newfilepath, $userid);
310 $processed[$name] = true;
311 continue;
314 $parts = explode('/', trim($name, '/'));
315 $filename = array_pop($parts);
316 $filepath = $pathbase;
317 if ($parts) {
318 $filepath .= implode('/', $parts).'/';
321 if ($size < 2097151) {
322 // small file
323 if (!$fz = $ziparch->get_stream($info->index)) {
324 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
325 continue;
327 $content = '';
328 while (!feof($fz)) {
329 $content .= fread($fz, 262143);
331 fclose($fz);
332 if (strlen($content) !== $size) {
333 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
334 // something went wrong :-(
335 unset($content);
336 continue;
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
342 continue;
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;
355 } else {
356 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
358 unset($content);
359 continue;
361 } else {
362 // large file, would not fit into memory :-(
363 $tmpfile = tempnam($CFG->tempdir.'/zip', 'unzip');
364 if (!$fp = fopen($tmpfile, 'wb')) {
365 @unlink($tmpfile);
366 $processed[$name] = 'Can not write temp file'; // TODO: localise
367 continue;
369 if (!$fz = $ziparch->get_stream($info->index)) {
370 @unlink($tmpfile);
371 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
372 continue;
374 while (!feof($fz)) {
375 $content = fread($fz, 262143);
376 fwrite($fp, $content);
378 fclose($fz);
379 fclose($fp);
380 if (filesize($tmpfile) !== $size) {
381 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
382 // something went wrong :-(
383 @unlink($tmpfile);
384 continue;
387 if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
388 if (!$file->delete()) {
389 @unlink($tmpfile);
390 $processed[$name] = 'Can not delete existing file'; // TODO: localise
391 continue;
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;
404 } else {
405 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
407 @unlink($tmpfile);
408 continue;
411 $ziparch->close();
412 return $processed;
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)) {
426 return false;
428 $list = $ziparch->list_files();
429 $ziparch->close();
430 return $list;