MDL-15920 initial zipping support
authorskodak <skodak>
Sun, 3 Aug 2008 21:37:50 +0000 (21:37 +0000)
committerskodak <skodak>
Sun, 3 Aug 2008 21:37:50 +0000 (21:37 +0000)
lib/file/file_packer.php
lib/file/stored_file.php
lib/file/zip_archive.php [new file with mode: 0644]

index f5ef80df5f2a1e97af088e1ff063eb7b18f47476..7007b8b08277ff11e6e8f9ecca4672667814f393 100644 (file)
 <?php  //$Id$
 
+require_once("$CFG->libdir/file/zip_archive.php");
+
 /**
  * Utility class - handles all zipping and unzipping operations.
  */
 class file_packer {
 
+    /**
+     * Zip files and store the result in file storage
+     * @param array $archivepath=>$pathanme or stored file instance
+     * @param int $contextid
+     * @param string $filearea
+     * @param int $itemid
+     * @param string $filepath
+     * @param string $filename
+     * @return mixed false if error stored file instance if ok
+     */
+    public function zip_files_to_storage($files, $contextid, $filearea, $itemid, $filepath, $filename, $userid=null) {
+        global $CFG;
+
+        $fs = get_file_storage();
+
+        check_dir_exists($CFG->dataroot.'/temp/zip', true, true);
+        $tmpfile = tempnam($CFG->dataroot.'/temp/zip', 'zipstor');
+
+        if ($result = $this->zip_files_to_pathname($files, $tmpfile)) {
+            if ($file = $fs->get_file($contextid, $filearea, $itemid, $filepath, $filename)) {
+                if (!$file->delete()) {
+                    @unlink($tmpfile);
+                    return false;
+                }
+            }
+            $file_record = new object();
+            $file_record->contextid = $contextid;
+            $file_record->filearea  = $filearea;
+            $file_record->itemid    = $itemid;
+            $file_record->filepath  = $filepath;
+            $file_record->filename  = $filename;
+            $file_record->userid    = $userid;
+            $result = $fs->create_file_from_pathname($file_record, $tmpfile);
+        }
+        @unlink($tmpfile);
+        return $result;
+    }
+
+    /**
+     * Zip files and store the result in os file
+     * @param array $archivepath=>$pathanme or stored file instance
+     * @param string $zipfile
+     * @return bool success
+     */
     public function zip_files_to_pathname($files, $zipfile) {
-        error('todo');
+        if (!is_array($files)) {
+            return false;
+        }
+
+        $ziparch = new zip_archive();
+        if (!$ziparch->open($zipfile, ZIPARCHIVE::OVERWRITE)) {
+            return false;
+        }
+
+        foreach ($files as $archivepath => $file) {
+            $archivepath = trim($archivepath, '/');
+
+            if (is_null($file)) {
+                // empty directories have null as content
+                $ziparch->addEmptyDir($archivepath.'/');
+
+            } else if (is_string($file)) {
+                $this->add_os_file_to_zip($ziparch, $archivepath, $file);
+
+            } else {
+                $this->add_stored_file_to_zip($ziparch, $archivepath, $file);
+            }
+        }
+
+        return $ziparch->close();
     }
 
-    public function zip_files_to_storage($files, $contextid, $filearea, $itemid, $filepath, $filename) {
-        error('todo');
+    protected function add_stored_file_to_zip($ziparch, $archivepath, $file) {
+        $file->add_to_ziparchive($ziparch, $archivepath);
+
+        if (!$file->is_directory()) {
+            return;
+        }
+
+        $baselength = strlen($file->get_filepath());
+        $fs = get_file_storage();
+        $files = $fs->get_directory_files($file->get_contextid(), $file->get_filearea(), $file->get_itemid(),
+                                          $file->get_filepath(), true, true);
+        foreach ($files as $file) {
+            $path = $file->get_filepath();
+            $path = substr($path, $baselength);
+            $path = $archivepath.'/'.$path;
+            if (!$file->is_directory()) {
+                $path = $path.$file->get_filename();
+            }
+            $file->add_to_ziparchive($ziparch, $path);
+        }
+    }
+
+    protected function add_os_file_to_zip( $ziparch, $archivepath, $file) {
+        if (!file_exists($file)) {
+            return;
+        }
+
+        if (is_file($file)) {
+            if (!is_readable($file)) {
+                return;
+            }
+            $ziparch->addFile($file, $archivepath);
+            return;
+        }
+        if (is_dir($file)) {
+            if ($archivepath !== '') {
+                $archivepath = $archivepath.'/';
+                $ziparch->addEmptyDir($archivepath);
+            }
+            $files = new DirectoryIterator($file);
+            foreach ($files as $file) {
+                if ($file->isDot()) {
+                    continue;
+                }
+                $newpath = $archivepath.$file->getFilename();
+                $this->add_os_file_to_zip($ziparch, $newpath, $file->getPathname());
+            }
+            unset($files); //release file handles
+            return;
+        }
     }
 
     /**
      * Unzip file to given file path (real OS filesystem), existing files are overwrited
      * @param mixed $zipfile full pathname of zip file or stored_file instance
-     * @param string $path target directory
+     * @param string $pathname target directory
      * @return mixed list of processed files; false if error
      */
     public function unzip_files_to_pathname($zipfile, $pathname) {
@@ -32,20 +150,18 @@ class file_packer {
         if (!is_readable($zipfile)) {
             return false;
         }
-        $zip = new ZipArchive();
-        if (!$zip->open($zipfile, ZIPARCHIVE::FL_NOCASE)) {
+        $ziparch = new zip_archive();
+        if (!$ziparch->open($zipfile, ZIPARCHIVE::FL_NOCASE)) {
             return false;
         }
 
-        for ($i=0; $i<$zip->numFiles; $i++) {
-            $index = $zip->statIndex($i);
+        for ($i=0; $i<$ziparch->numFiles; $i++) {
+            $index = $ziparch->statIndex($i);
 
             $size = clean_param($index['size'], PARAM_INT);
             $name = clean_param($index['name'], PARAM_PATH);
             $name = ltrim($name, '/');
 
-            //TODO: add $name encoding conversions magic here
-
             if ($name === '' or array_key_exists($name, $processed)) {
                 //probably filename collisions caused by filename cleaning/conversion
                 continue;
@@ -87,7 +203,7 @@ class file_packer {
                 $processed[$name] = 'Can not write target file'; // TODO: localise
                 continue;
             }
-            if (!$fz = $zip->getStream($index['name'])) {
+            if (!$fz = $ziparch->getStream($index['name'])) {
                 $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
                 fclose($fp);
                 continue;
@@ -107,7 +223,7 @@ class file_packer {
             }
             $processed[$name] = true;
         }
-        $zip->close();
+        $ziparch->close();
         return $processed;
     }
 
@@ -127,7 +243,7 @@ class file_packer {
             return $zipfile->unzip_files_to_pathname($contextid, $filearea, $itemid, $pathbase, $userid);
         }
 
-        check_dir_exists($CFG->dataroot.'/temp/unzip', true, true);
+        check_dir_exists($CFG->dataroot.'/temp/zip', true, true);
 
         $pathbase = trim($pathbase, '/');
         $pathbase = ($pathbase === '') ? '/' : '/'.$pathbase.'/';
@@ -135,19 +251,18 @@ class file_packer {
 
         $processed = array();
 
-        $zip = new ZipArchive();
-        if (!$zip->open($zipfile, ZIPARCHIVE::FL_NOCASE)) {
+        $ziparch = new zip_archive();
+        if (!$ziparch->open($zipfile, ZIPARCHIVE::FL_NOCASE)) {
             return false;
         }
 
-        for ($i=0; $i<$zip->numFiles; $i++) {
-            $index = $zip->statIndex($i);
+        for ($i=0; $i<$ziparch->numFiles; $i++) {
+            $index = $ziparch->statIndex($i);
 
             $size = clean_param($index['size'], PARAM_INT);
             $name = clean_param($index['name'], PARAM_PATH);
             $name = ltrim($name, '/');
 
-            //TODO: add $name encoding conversions magic here
 
             if ($name === '' or array_key_exists($name, $processed)) {
                 //probably filename collisions caused by filename cleaning/conversion
@@ -170,7 +285,7 @@ class file_packer {
 
             if ($size < 2097151) {
                 // small file
-                if (!$fz = $zip->getStream($index['name'])) {
+                if (!$fz = $ziparch->getStream($index['name'])) {
                     $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
                     continue;
                 }
@@ -209,13 +324,13 @@ class file_packer {
 
             } else {
                 // large file, would not fit into memory :-(
-                $tmpfile = tempnam($CFG->dataroot.'/temp/unzip', 'largefile');
+                $tmpfile = tempnam($CFG->dataroot.'/temp/zip', 'unzip');
                 if (!$fp = fopen($tmpfile, 'wb')) {
                     @unlink($tmpfile);
                     $processed[$name] = 'Can not write temp file'; // TODO: localise
                     continue;
                 }
-                if (!$fz = $zip->getStream($index['name'])) {
+                if (!$fz = $ziparch->getStream($index['name'])) {
                     @unlink($tmpfile);
                     $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
                     continue;
@@ -256,6 +371,7 @@ class file_packer {
                 continue;
             }
         }
+        $ziparch->close();
         return $processed;
     }
 }
\ No newline at end of file
index 8e536247cd5098d28360f248839c62a13c3abe4b..b7bf489faed924a5093776c77923d0ecf9ade39d 100644 (file)
@@ -129,6 +129,24 @@ class stored_file {
         return $packer->unzip_files_to_storage($zipfile, $contextid, $filearea, $itemid, $pathbase);
     }
 
+    /**
+     * Add file/directory into zip archive
+     * @param object $ziparchive
+     * @param string $archivepath pathname in zip archive
+     * @return bool success
+     */
+    public function add_to_ziparchive(zip_archive $ziparch, $archivepath) {
+        if ($this->is_directory()) {
+            return $ziparch->addEmptyDir($archivepath);
+        } else {
+            $path = $this->get_content_file_location();
+            if (!is_readable($path)) {
+                return false;
+            }
+            return $ziparch->addFile($path, $archivepath);
+        }
+    }
+
     public function get_contextid() {
         return $this->file_record->contextid;
     }
diff --git a/lib/file/zip_archive.php b/lib/file/zip_archive.php
new file mode 100644 (file)
index 0000000..d8b7867
--- /dev/null
@@ -0,0 +1,11 @@
+<?php  //$Id$
+
+
+class zip_archive extends ZipArchive {
+
+//TODO: limit number of open file handles by fetching small files into memory and closing/reopening archive for large files
+//TODO: add file name encoding conversions
+//TODO: prevent adding of target zip into archive
+
+
+}
\ No newline at end of file