MDL-37429 zipping improvements
[moodle.git] / lib / filestorage / zip_packer.php
index c30b479..2f5bfc3 100644 (file)
@@ -14,7 +14,6 @@
 // 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.
  *
@@ -39,7 +38,7 @@ require_once("$CFG->libdir/filestorage/zip_archive.php");
 class zip_packer extends file_packer {
 
     /**
-     * Zip files and store the result in file storage
+     * 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'))
@@ -50,9 +49,10 @@ class zip_packer extends file_packer {
      * @param string $filepath file path
      * @param string $filename file name
      * @param int $userid user ID
-     * @return stored_file|bool false if error stored file instance if ok
+     * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
+     * @return stored_file|bool false if error stored_file instance if ok
      */
-    public function archive_to_storage($files, $contextid, $component, $filearea, $itemid, $filepath, $filename, $userid = NULL) {
+    public function archive_to_storage(array $files, $contextid, $component, $filearea, $itemid, $filepath, $filename, $userid = NULL, $ignoreinvalidfiles=true) {
         global $CFG;
 
         $fs = get_file_storage();
@@ -60,7 +60,7 @@ class zip_packer extends file_packer {
         check_dir_exists($CFG->tempdir.'/zip');
         $tmpfile = tempnam($CFG->tempdir.'/zip', 'zipstor');
 
-        if ($result = $this->archive_to_pathname($files, $tmpfile)) {
+        if ($result = $this->archive_to_pathname($files, $tmpfile, $ignoreinvalidfiles)) {
             if ($file = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
                 if (!$file->delete()) {
                     @unlink($tmpfile);
@@ -84,70 +84,78 @@ class zip_packer extends file_packer {
     }
 
     /**
-     * Zip files and store the result in os file
+     * 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
-     * @return bool success
+     * @param bool $ignoreinvalidfiles true means ignore missing or invalid files, false means abort on any error
+     * @return bool true if file created, false if not
      */
-    public function archive_to_pathname($files, $archivefile) {
-        if (!is_array($files)) {
-            return false;
-        }
-
+    public function archive_to_pathname(array $files, $archivefile, $ignoreinvalidfiles=true) {
         $ziparch = new zip_archive();
         if (!$ziparch->open($archivefile, file_archive::OVERWRITE)) {
             return false;
         }
 
-        $result = false; // One processed file or dir means success here.
-
+        $abort = false;
         foreach ($files as $archivepath => $file) {
             $archivepath = trim($archivepath, '/');
 
             if (is_null($file)) {
-                // empty directories have null as content
-                if ($ziparch->add_directory($archivepath.'/')) {
-                    $result = true;
-                } else {
+                // 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)) {
-                    $result = true;
-                } else {
+                if (!$this->archive_pathname($ziparch, $archivepath, $file)) {
                     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)) {
-                    $result = true;
-                } else {
+                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)) {
-                    $result = true;
-                } else {
+                if (!$this->archive_stored($ziparch, $archivepath, $file)) {
                     debugging("Can not zip '$archivepath' file", DEBUG_DEVELOPER);
+                    if (!$ignoreinvalidfiles) {
+                        $abort = true;
+                        break;
+                    }
                 }
             }
         }
 
-        // We can consider that there was an error if the file generated does not contain anything.
-        if ($ziparch->count() == 0) {
-            $result = false;
-            debugging("Nothing was added to the zip file", DEBUG_DEVELOPER);
+        if (!$ziparch->close()) {
+            @unlink($archivefile);
+            return false;
         }
 
-        return ($ziparch->close() && $result);
+        if ($abort) {
+            @unlink($archivefile);
+            return false;
+        }
+
+        return true;
     }
 
     /**
-     * Perform archiving file from stored file
+     * Perform archiving file from stored file.
      *
      * @param zip_archive $ziparch zip archive instance
      * @param string $archivepath file path to archive
@@ -183,7 +191,7 @@ class zip_packer extends file_packer {
     }
 
     /**
-     * Perform archiving file from file path
+     * Perform archiving file from file path.
      *
      * @param zip_archive $ziparch zip archive instance
      * @param string $archivepath file path to archive
@@ -213,13 +221,13 @@ class zip_packer extends file_packer {
                 $newpath = $archivepath.'/'.$file->getFilename();
                 $this->archive_pathname($ziparch, $newpath, $file->getPathname());
             }
-            unset($files); //release file handles
+            unset($files); // Release file handles.
             return true;
         }
     }
 
     /**
-     * Unzip file to given file path (real OS filesystem), existing files are overwrited
+     * 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
@@ -319,7 +327,7 @@ class zip_packer extends file_packer {
     }
 
     /**
-     * Unzip file to given file path (real OS filesystem), existing files are overwrited
+     * 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
@@ -375,7 +383,7 @@ class zip_packer extends file_packer {
             }
 
             if ($size < 2097151) {
-                // small file
+                // Small file.
                 if (!$fz = $ziparch->get_stream($info->index)) {
                     $processed[$name] = 'Can not read file from zip archive'; // TODO: localise
                     continue;
@@ -469,7 +477,7 @@ class zip_packer extends file_packer {
     }
 
     /**
-     * Returns array of info about all files in archive
+     * Returns array of info about all files in archive.
      *
      * @param string|file_archive $archivefile
      * @return array of file infos