MDL-33733 fix broken zip_packer->extract_to_storage() when extracting from stored_file
[moodle.git] / lib / filestorage / zip_packer.php
CommitLineData
33488ad6 1<?php
33488ad6 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/**
19 * Implementation of zip packer.
20 *
d2b7803e
DC
21 * @package core_files
22 * @copyright 2008 Petr Skoda (http://skodak.org)
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33488ad6 24 */
172dd12c 25
64f93798
PS
26defined('MOODLE_INTERNAL') || die();
27
28require_once("$CFG->libdir/filestorage/file_packer.php");
29require_once("$CFG->libdir/filestorage/zip_archive.php");
0b0bfa93 30
17d9269f 31/**
32 * Utility class - handles all zipping and unzipping operations.
64f93798 33 *
d2b7803e
DC
34 * @package core_files
35 * @category files
36 * @copyright 2008 Petr Skoda (http://skodak.org)
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
17d9269f 38 */
0b0bfa93 39class zip_packer extends file_packer {
172dd12c 40
b1897a6d 41 /**
42 * Zip files and store the result in file storage
d2b7803e 43 *
59333bc9
PS
44 * @param array $files array with full zip paths (including directory information)
45 * as keys (archivepath=>ospathname or archivepath/subdir=>stored_file or archivepath=>array('content_as_string'))
d2b7803e
DC
46 * @param int $contextid context ID
47 * @param string $component component
48 * @param string $filearea file area
49 * @param int $itemid item ID
50 * @param string $filepath file path
51 * @param string $filename file name
52 * @param int $userid user ID
53 * @return stored_file|bool false if error stored file instance if ok
b1897a6d 54 */
64f93798 55 public function archive_to_storage($files, $contextid, $component, $filearea, $itemid, $filepath, $filename, $userid = NULL) {
b1897a6d 56 global $CFG;
57
58 $fs = get_file_storage();
59
7aa06e6d
TL
60 check_dir_exists($CFG->tempdir.'/zip');
61 $tmpfile = tempnam($CFG->tempdir.'/zip', 'zipstor');
b1897a6d 62
0b0bfa93 63 if ($result = $this->archive_to_pathname($files, $tmpfile)) {
64f93798 64 if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
b1897a6d 65 if (!$file->delete()) {
66 @unlink($tmpfile);
67 return false;
68 }
69 }
ac6f1a82 70 $file_record = new stdClass();
b1897a6d 71 $file_record->contextid = $contextid;
64f93798 72 $file_record->component = $component;
b1897a6d 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;
b27c4855 78 $file_record->mimetype = 'application/zip';
0b0bfa93 79
b1897a6d 80 $result = $fs->create_file_from_pathname($file_record, $tmpfile);
81 }
82 @unlink($tmpfile);
83 return $result;
84 }
85
86 /**
87 * Zip files and store the result in os file
d2b7803e 88 *
59333bc9 89 * @param array $files array with zip paths as keys (archivepath=>ospathname or archivepath=>stored_file or archivepath=>array('content_as_string'))
593bb2eb 90 * @param string $archivefile path to target zip file
b1897a6d 91 * @return bool success
92 */
0b0bfa93 93 public function archive_to_pathname($files, $archivefile) {
b1897a6d 94 if (!is_array($files)) {
95 return false;
96 }
97
98 $ziparch = new zip_archive();
0b0bfa93 99 if (!$ziparch->open($archivefile, file_archive::OVERWRITE)) {
b1897a6d 100 return false;
101 }
102
103 foreach ($files as $archivepath => $file) {
104 $archivepath = trim($archivepath, '/');
105
106 if (is_null($file)) {
107 // empty directories have null as content
0b0bfa93 108 $ziparch->add_directory($archivepath.'/');
b1897a6d 109
110 } else if (is_string($file)) {
0b0bfa93 111 $this->archive_pathname($ziparch, $archivepath, $file);
b1897a6d 112
59333bc9
PS
113 } else if (is_array($file)) {
114 $content = reset($file);
115 $ziparch->add_file_from_string($archivepath, $content);
116
b1897a6d 117 } else {
0b0bfa93 118 $this->archive_stored($ziparch, $archivepath, $file);
b1897a6d 119 }
120 }
121
122 return $ziparch->close();
17d9269f 123 }
124
d2b7803e
DC
125 /**
126 * Perform archiving file from stored file
127 *
128 * @param zip_archive $ziparch zip archive instance
129 * @param string $archivepath file path to archive
130 * @param stored_file $file stored_file object
131 */
0b0bfa93 132 private function archive_stored($ziparch, $archivepath, $file) {
133 $file->archive_file($ziparch, $archivepath);
b1897a6d 134
135 if (!$file->is_directory()) {
136 return;
137 }
138
139 $baselength = strlen($file->get_filepath());
140 $fs = get_file_storage();
64f93798 141 $files = $fs->get_directory_files($file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(),
b1897a6d 142 $file->get_filepath(), true, true);
143 foreach ($files as $file) {
144 $path = $file->get_filepath();
145 $path = substr($path, $baselength);
146 $path = $archivepath.'/'.$path;
147 if (!$file->is_directory()) {
148 $path = $path.$file->get_filename();
149 }
0b0bfa93 150 $file->archive_file($ziparch, $path);
b1897a6d 151 }
152 }
153
d2b7803e
DC
154 /**
155 * Perform archiving file from file path
156 *
157 * @param zip_archive $ziparch zip archive instance
158 * @param string $archivepath file path to archive
159 * @param string $file path name of the file
160 */
0b0bfa93 161 private function archive_pathname($ziparch, $archivepath, $file) {
b1897a6d 162 if (!file_exists($file)) {
163 return;
164 }
165
166 if (is_file($file)) {
167 if (!is_readable($file)) {
168 return;
169 }
0b0bfa93 170 $ziparch->add_file_from_pathname($archivepath, $file);
b1897a6d 171 return;
172 }
173 if (is_dir($file)) {
174 if ($archivepath !== '') {
0b0bfa93 175 $ziparch->add_directory($archivepath);
b1897a6d 176 }
177 $files = new DirectoryIterator($file);
178 foreach ($files as $file) {
179 if ($file->isDot()) {
180 continue;
181 }
a14e283a 182 $newpath = $archivepath.'/'.$file->getFilename();
0b0bfa93 183 $this->archive_pathname($ziparch, $newpath, $file->getPathname());
b1897a6d 184 }
185 unset($files); //release file handles
186 return;
187 }
17d9269f 188 }
189
190 /**
191 * Unzip file to given file path (real OS filesystem), existing files are overwrited
d2b7803e
DC
192 *
193 * @todo MDL-31048 localise messages
194 * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
b1897a6d 195 * @param string $pathname target directory
d2b7803e 196 * @return bool|array list of processed files; false if error
17d9269f 197 */
0b0bfa93 198 public function extract_to_pathname($archivefile, $pathname) {
17d9269f 199 global $CFG;
200
0b0bfa93 201 if (!is_string($archivefile)) {
202 return $archivefile->extract_to_pathname($this, $pathname);
17d9269f 203 }
204
205 $processed = array();
206
207 $pathname = rtrim($pathname, '/');
0b0bfa93 208 if (!is_readable($archivefile)) {
17d9269f 209 return false;
210 }
0b0bfa93 211 $ziparch = new zip_archive();
212 if (!$ziparch->open($archivefile, file_archive::OPEN)) {
17d9269f 213 return false;
214 }
215
0b0bfa93 216 foreach ($ziparch as $info) {
217 $size = $info->size;
218 $name = $info->pathname;
17d9269f 219
17d9269f 220 if ($name === '' or array_key_exists($name, $processed)) {
221 //probably filename collisions caused by filename cleaning/conversion
222 continue;
223 }
224
0b0bfa93 225 if ($info->is_directory) {
17d9269f 226 $newdir = "$pathname/$name";
227 // directory
228 if (is_file($newdir) and !unlink($newdir)) {
229 $processed[$name] = 'Can not create directory, file already exists'; // TODO: localise
230 continue;
231 }
232 if (is_dir($newdir)) {
233 //dir already there
234 $processed[$name] = true;
235 } else {
236 if (mkdir($newdir, $CFG->directorypermissions, true)) {
237 $processed[$name] = true;
238 } else {
239 $processed[$name] = 'Can not create directory'; // TODO: localise
240 }
241 }
242 continue;
243 }
244
245 $parts = explode('/', trim($name, '/'));
246 $filename = array_pop($parts);
247 $newdir = rtrim($pathname.'/'.implode('/', $parts), '/');
248
249 if (!is_dir($newdir)) {
250 if (!mkdir($newdir, $CFG->directorypermissions, true)) {
251 $processed[$name] = 'Can not create directory'; // TODO: localise
252 continue;
253 }
254 }
255
256 $newfile = "$newdir/$filename";
257 if (!$fp = fopen($newfile, 'wb')) {
258 $processed[$name] = 'Can not write target file'; // TODO: localise
259 continue;
260 }
0b0bfa93 261 if (!$fz = $ziparch->get_stream($info->index)) {
17d9269f 262 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
263 fclose($fp);
264 continue;
265 }
266
267 while (!feof($fz)) {
268 $content = fread($fz, 262143);
269 fwrite($fp, $content);
270 }
271 fclose($fz);
272 fclose($fp);
273 if (filesize($newfile) !== $size) {
274 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
275 // something went wrong :-(
276 @unlink($newfile);
277 continue;
278 }
279 $processed[$name] = true;
280 }
b1897a6d 281 $ziparch->close();
17d9269f 282 return $processed;
283 }
284
285 /**
286 * Unzip file to given file path (real OS filesystem), existing files are overwrited
d2b7803e
DC
287 *
288 * @todo MDL-31048 localise messages
289 * @param string|stored_file $archivefile full pathname of zip file or stored_file instance
290 * @param int $contextid context ID
291 * @param string $component component
292 * @param string $filearea file area
293 * @param int $itemid item ID
294 * @param string $pathbase file path
295 * @param int $userid user ID
296 * @return array|bool list of processed files; false if error
17d9269f 297 */
64f93798 298 public function extract_to_storage($archivefile, $contextid, $component, $filearea, $itemid, $pathbase, $userid = NULL) {
17d9269f 299 global $CFG;
300
0b0bfa93 301 if (!is_string($archivefile)) {
aefe9734 302 return $archivefile->extract_to_storage($this, $contextid, $component, $filearea, $itemid, $pathbase, $userid);
17d9269f 303 }
304
7aa06e6d 305 check_dir_exists($CFG->tempdir.'/zip');
17d9269f 306
307 $pathbase = trim($pathbase, '/');
308 $pathbase = ($pathbase === '') ? '/' : '/'.$pathbase.'/';
309 $fs = get_file_storage();
310
311 $processed = array();
312
b1897a6d 313 $ziparch = new zip_archive();
0b0bfa93 314 if (!$ziparch->open($archivefile, file_archive::OPEN)) {
17d9269f 315 return false;
316 }
317
0b0bfa93 318 foreach ($ziparch as $info) {
319 $size = $info->size;
320 $name = $info->pathname;
17d9269f 321
322 if ($name === '' or array_key_exists($name, $processed)) {
323 //probably filename collisions caused by filename cleaning/conversion
324 continue;
325 }
326
0b0bfa93 327 if ($info->is_directory) {
17d9269f 328 $newfilepath = $pathbase.$name.'/';
64f93798 329 $fs->create_directory($contextid, $component, $filearea, $itemid, $newfilepath, $userid);
17d9269f 330 $processed[$name] = true;
331 continue;
332 }
333
334 $parts = explode('/', trim($name, '/'));
335 $filename = array_pop($parts);
336 $filepath = $pathbase;
337 if ($parts) {
338 $filepath .= implode('/', $parts).'/';
339 }
340
341 if ($size < 2097151) {
342 // small file
0b0bfa93 343 if (!$fz = $ziparch->get_stream($info->index)) {
17d9269f 344 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
345 continue;
346 }
347 $content = '';
348 while (!feof($fz)) {
349 $content .= fread($fz, 262143);
350 }
351 fclose($fz);
352 if (strlen($content) !== $size) {
353 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
354 // something went wrong :-(
355 unset($content);
356 continue;
357 }
358
64f93798 359 if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
17d9269f 360 if (!$file->delete()) {
361 $processed[$name] = 'Can not delete existing file'; // TODO: localise
362 continue;
363 }
364 }
ac6f1a82 365 $file_record = new stdClass();
17d9269f 366 $file_record->contextid = $contextid;
64f93798 367 $file_record->component = $component;
17d9269f 368 $file_record->filearea = $filearea;
369 $file_record->itemid = $itemid;
370 $file_record->filepath = $filepath;
371 $file_record->filename = $filename;
372 $file_record->userid = $userid;
373 if ($fs->create_file_from_string($file_record, $content)) {
374 $processed[$name] = true;
375 } else {
376 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
377 }
378 unset($content);
379 continue;
380
381 } else {
382 // large file, would not fit into memory :-(
7aa06e6d 383 $tmpfile = tempnam($CFG->tempdir.'/zip', 'unzip');
17d9269f 384 if (!$fp = fopen($tmpfile, 'wb')) {
d4858e5c 385 @unlink($tmpfile);
17d9269f 386 $processed[$name] = 'Can not write temp file'; // TODO: localise
387 continue;
388 }
0b0bfa93 389 if (!$fz = $ziparch->get_stream($info->index)) {
d4858e5c 390 @unlink($tmpfile);
17d9269f 391 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
392 continue;
393 }
394 while (!feof($fz)) {
395 $content = fread($fz, 262143);
396 fwrite($fp, $content);
397 }
398 fclose($fz);
399 fclose($fp);
7c53e3e5 400 if (filesize($tmpfile) !== $size) {
17d9269f 401 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
402 // something went wrong :-(
403 @unlink($tmpfile);
404 continue;
405 }
406
64f93798 407 if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
17d9269f 408 if (!$file->delete()) {
d4858e5c 409 @unlink($tmpfile);
17d9269f 410 $processed[$name] = 'Can not delete existing file'; // TODO: localise
411 continue;
412 }
413 }
ac6f1a82 414 $file_record = new stdClass();
17d9269f 415 $file_record->contextid = $contextid;
64f93798 416 $file_record->component = $component;
17d9269f 417 $file_record->filearea = $filearea;
418 $file_record->itemid = $itemid;
419 $file_record->filepath = $filepath;
420 $file_record->filename = $filename;
421 $file_record->userid = $userid;
422 if ($fs->create_file_from_pathname($file_record, $tmpfile)) {
423 $processed[$name] = true;
424 } else {
425 $processed[$name] = 'Unknown error during zip extraction'; // TODO: localise
426 }
427 @unlink($tmpfile);
428 continue;
429 }
430 }
b1897a6d 431 $ziparch->close();
17d9269f 432 return $processed;
433 }
c78a0558 434
435 /**
436 * Returns array of info about all files in archive
d2b7803e
DC
437 *
438 * @param file_archive $archivefile
c78a0558 439 * @return array of file infos
440 */
441 public function list_files($archivefile) {
442 if (!is_string($archivefile)) {
443 return $archivefile->list_files();
444 }
445
446 $ziparch = new zip_archive();
447 if (!$ziparch->open($archivefile, file_archive::OPEN)) {
448 return false;
449 }
450 $list = $ziparch->list_files();
451 $ziparch->close();
452 return $list;
453 }
454
f8c532ea 455}