aboutsummaryrefslogtreecommitdiff
path: root/lib/filesystem/FileArchiveManager.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/filesystem/FileArchiveManager.php')
-rw-r--r--lib/filesystem/FileArchiveManager.php998
1 files changed, 998 insertions, 0 deletions
diff --git a/lib/filesystem/FileArchiveManager.php b/lib/filesystem/FileArchiveManager.php
new file mode 100644
index 0000000..aded83c
--- /dev/null
+++ b/lib/filesystem/FileArchiveManager.php
@@ -0,0 +1,998 @@
+<?php
+/**
+ * FileArchiveManager.php
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Moritz Strohm <strohm@data-quest.de>
+ * @copyright 2016 data-quest
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+
+/**
+ * The FileArchiveManager class gives programmers a simple way to handle
+ * file archives by providing different methods for packing and unpacking
+ * file archives in a simple manner.
+ */
+class FileArchiveManager
+{
+
+ //ARCHIVE HELPER METHODS
+
+
+ /**
+ * Adds a file to an archive using its FileType object.
+ *
+ * @param ZipArchive $archive The Zip archive where the FileRef shall be added to.
+ * @param FileType $file_type The FileType which shall be added to the zip archive.
+ * @param string $user_id The user who wishes to add the FileRef to the archive.
+ * @param string $archive_fs_path The path of the file inside the archive's file system.
+ * @param bool $do_user_permission_checks Set to true if reading/downloading permissions
+ * shall be checked. False otherwise. Default is true.
+ * @param bool $ignore_user Set to true, if a file
+ * which has no download restrictions shall be included
+ * and the user-specific download condition check shall be ignored.
+ * If this parameter is set to true, the user_id parameter is irrelevant.
+ * The default for this parameter is false.
+ * @return bool True on success, false on failure.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more
+ * special exception is thrown.
+ */
+ public static function addFileTypeToArchive(
+ ZipArchive $archive,
+ FileType $file_type,
+ $user_id = null,
+ $archive_fs_path = '',
+ $do_user_permission_checks = true,
+ $ignore_user = false,
+ &$file_list = null
+ )
+ {
+ $archive_max_size = Config::get()->ZIP_DOWNLOAD_MAX_SIZE * 1024 * 1024;
+ //For FileRef objects we first have to do permission checks
+ //using the FileRef's folder object.
+ $adding_allowed = false;
+
+ if ($do_user_permission_checks) {
+ $folder = $file_type->getFolderType();
+ if (!$folder) {
+ return false;
+ }
+
+ if ($folder->isReadable($user_id) && $file_type->isDownloadable($user_id)) {
+ //FileRef is readable and downloadable for the user (identified by $user_id).
+ $adding_allowed = true;
+ }
+ } elseif ($ignore_user) {
+ //we have to check the download condition by looking at the
+ //terms of use object of the FileType:
+ $terms_of_use = $file_type->getTermsOfUse();
+ if ($terms_of_use && $terms_of_use->download_condition == 0) {
+ $adding_allowed = true;
+ }
+ } else {
+ //Totally skip permission checks:
+ $adding_allowed = true;
+ }
+
+ if ($adding_allowed) {
+ //Adding the FileType is allowed:
+ $file_contains_link = false;
+ if ($file_type instanceof LibraryFile) {
+ $file_contains_link = !$file_type->hasFileAttached();
+ } else {
+ $file_contains_link = $file_type instanceof URLFile;
+ }
+
+ // Increase download counter
+ if ($file_type instanceof StandardFile) {
+ $file_ref = $file_type->getFileRef();
+ $file_ref->incrementDownloadCounter();
+ }
+
+ if ($file_contains_link) {
+ //The FileType references a link:
+ //Put the URL into a file ending with .url:
+ $url = $file_type->getDownloadURL();
+ if ($url) {
+ //The URL has been fetched and we can put it
+ //in a file in the archive:
+ $archive->addFromString(
+ $archive_fs_path . FileManager::cleanFileName($file_type->getFilename()) . '.url',
+ "[InternetShortcut]\nURL={$url}\n"
+ );
+ //Check the file size of the archive:
+ if (file_exists($archive->filename) && filesize($archive->filename) > $archive_max_size) {
+ throw new FileArchiveManagerException(
+ sprintf(
+ _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'),
+ relsize($archive_max_size)
+ )
+ );
+ }
+ if (is_array($file_list)) {
+ $user = $file_type->getUser();
+ $file_list[] = [
+ 'name' => FileManager::cleanFileName($file_type->getFilename()),
+ 'size' => $file_type->getSize(),
+ 'first_name' => ($user instanceof User) ? $user->vorname : '',
+ 'last_name' => ($user instanceof User) ? $user->nachname : '',
+ 'downloads' => $file_type->getDownloads(),
+ 'mkdate' => date('d.m.Y H:i', $file_type->getMakeDate()),
+ 'path' => ($archive_fs_path . $file_type->getFilename())
+ ];
+ }
+ return true;
+ }
+ } else {
+ if (!($file_type instanceof StandardFile)) {
+ $file_type = $file_type->convertToStandardFile();
+ }
+ if (!($file_type instanceof StandardFile)) {
+ //The file type could not be converted to a standard file.
+ //We cannot continue.
+ return false;
+ }
+ //Get the file's path (if the file exists) and add the file to the archive:
+ $path = $file_type->getPath();
+ if ($path) {
+ //It is a file in the file system:
+ if (file_exists($path)) {
+ $archive->addFile($path, $archive_fs_path . FileManager::cleanFileName($file_type->getFilename()));
+ //Check the file size of the archive:
+ if (file_exists($archive->filename) && filesize($archive->filename) > $archive_max_size) {
+ throw new FileArchiveManagerException(
+ sprintf(
+ _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'),
+ relsize($archive_max_size)
+ )
+ );
+ }
+ //Add the file to the file list (if available):
+ if (is_array($file_list)) {
+ $archive_max_num_files = Config::get()->ZIP_DOWNLOAD_MAX_FILES;
+ $archive_max_size = Config::get()->ZIP_DOWNLOAD_MAX_SIZE * 1024 * 1024; //1048576 bytes = 1 Mebibyte
+ $user = $file_type->getUser();
+ $file_list[] = [
+ 'name' => FileManager::cleanFileName($file_type->getFilename()),
+ 'size' => $file_type->getSize(),
+ 'first_name' => ($user instanceof User) ? $user->vorname : '',
+ 'last_name' => ($user instanceof User) ? $user->nachname : '',
+ 'downloads' => $file_type->getDownloads(),
+ 'mkdate' => date('d.m.Y H:i', $file_type->getMakeDate()),
+ 'path' => ($archive_fs_path . FileManager::cleanFileName($file_type->getFilename()))
+ ];
+ if (count($file_list) > $archive_max_num_files) {
+ $archive->unchangeAll();
+ unlink($archive->filename);
+ throw new FileArchiveManagerException(
+ sprintf(
+ _('Das Archiv beinhaltet zu viele Dateibereich-Objekte! Die Obergrenze liegt bei %d Objekten!'),
+ $archive_max_num_files
+ )
+ );
+ }
+ if (array_sum(array_column($file_list, 'size')) > $archive_max_size) {
+ $archive->unchangeAll();
+ unlink($archive->filename);
+ throw new FileArchiveManagerException(
+ sprintf(
+ _('Das ZIP-Archiv ist zu groß! Die maximal erlaubte Größe ist %s!'),
+ relsize($archive_max_size)
+ )
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //Something must have gone wrong:
+ return false;
+ }
+
+
+ /**
+ * Adds a FileRef to a Zip archive.
+ * This is only a wrapper to addFileTypeToArchive that exists only
+ * for compatibility reasons.
+ *
+ * @see addFileTypeToArchive
+ */
+ public static function addFileRefToArchive(
+ ZipArchive $archive,
+ FileRef $file_ref,
+ $user_id = null,
+ $archive_fs_path = '',
+ $do_user_permission_checks = true,
+ $ignore_user = false,
+ &$file_list = null
+ )
+ {
+ $file_type = $file_ref->getFileType();
+ if ($file_type instanceof FileType) {
+ return self::addFileTypeToArchive(
+ $archive,
+ $file_type,
+ $user_id,
+ $archive_fs_path,
+ $do_user_permission_checks,
+ $ignore_user,
+ $file_list
+ );
+ }
+ //The file type variable does not contain a FileType object.
+ return false;
+ }
+
+
+ /**
+ * Adds a FolderType instance to a Zip archive.
+ *
+ * @param ZipArchive $archive The Zip archive where the FileRef shall be added to.
+ * @param FileRef $file_ref The FileRef which shall be added to the zip archive.
+ * @param string $user_id The user who wishes to add the FileRef to the archive.
+ * @param string $archive_fs_path The path of the folder inside the archive's file system.
+ * @param bool $do_user_permission_checks Set to true if reading/downloading permissions
+ * shall be checked. False otherwise. Default is true.
+ * @param bool $keep_hierarchy True, if the folder hierarchy shall be kept.
+ * False, if the folder hierarchy shall be flattened.
+ * @param bool $ignore_user Set to true, if a folder
+ * of type StandardFolder shall be included without checking
+ * if the user (identified by user_id) can read it.
+ * @return bool True on success, false on failure.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more
+ * special exception is thrown.
+ */
+ public static function addFolderToArchive(
+ ZipArchive $archive,
+ FolderType $folder,
+ $user_id = null,
+ $archive_fs_path = '',
+ $do_user_permission_checks = true,
+ $keep_hierarchy = true,
+ $ignore_user = false,
+ &$file_list = null
+ ) {
+
+ if ($do_user_permission_checks) {
+ //Check if the folder is readable for the user (identified by $user_id):
+ if (!$folder->isReadable($user_id)) {
+ //Folder is not readable:
+ return false;
+ }
+ } elseif ($ignore_user
+ && !($folder instanceof StandardFolder)
+ && in_array($folder->range_type, ['course', 'institute']))
+ {
+ //If user permissions shall be skipped the folder must be
+ //an instance of StandardFolder and the folder's range type
+ //must be course or institute since we can only be sure
+ //that StandardFolder instances in courses or institutes
+ //are readable by everyone.
+ return false;
+ }
+
+ $folder_zip_path = $archive_fs_path;
+ if ($keep_hierarchy) {
+ $folder_zip_path .= FileManager::cleanFileName($folder->name);
+ $archive->addEmptyDir($folder_zip_path);
+ }
+ foreach ($folder->getFiles() as $file) {
+
+ /*if (!$file_ref instanceof FileRef) { TODO: OwnCloudPlugin is this ready?
+ $plugin = PluginManager::getInstance()->getPlugin($folder->range_id);
+ if (!$plugin) {
+ $plugin = PluginManager::getInstance()->getPlugin($folder->range_type);;
+ }
+ if ($plugin) {
+ $file_ref = $plugin->getPreparedFile($file_ref->id, true);
+ }
+ }*/
+
+ self::addFileTypeToArchive(
+ $archive,
+ $file,
+ $user_id,
+ //keep hierarchy in zip file (files and subdirectories)
+ $keep_hierarchy ? $folder_zip_path . '/' : '',
+ $do_user_permission_checks,
+ $ignore_user,
+ $file_list
+ );
+ }
+
+ foreach ($folder->getSubfolders() as $subfolder) {
+ self::addFolderToArchive(
+ $archive,
+ $subfolder,
+ $user_id,
+ //keep hierarchy in zip file (files and subdirectories)
+ $keep_hierarchy ? $folder_zip_path . '/' : '',
+ $do_user_permission_checks,
+ $keep_hierarchy,
+ $ignore_user,
+ $file_list
+ );
+ }
+
+ return true;
+ }
+
+
+ //ARCHIVE CREATION METHODS
+
+
+ /**
+ * General method for creating file archives.
+ *
+ * This method is a generalisation for all archive creation methods.
+ * For easier archive creation you may use the other archive creation
+ * methods which work with less arguments.
+ *
+ * @param Array $file_area_objects Array of FileRef, FileURL, Folder or FolderType objects.
+ * $file_area_objects may contain a mix between those object types.
+ * @param string $user_id The user who wishes to pack files.
+ * @param string $archive_file_path The path for the archive file.
+ * @param bool $do_user_permission_checks Set to true if individual
+ * reading/downloading permissions shall be checked. False otherwise.
+ * Default is true.
+ * @param bool $keep_hierarchy True, if the folder hierarchy shall be kept.
+ * False, if the folder hierarchy shall be flattened. Default is true.
+ * @param bool $ignore_user Set to true, if all files
+ * which have no download restrictions and all folders which are of type
+ * StandardFolder shall be included and the user-specific
+ * download condition check shall be ignored.
+ * If this parameter is set to true, the user_id parameter is irrelevant.
+ * The default for this parameter is false.
+ * @param string $zip_encoding encoding for filenames in zip
+ * @param bool $add_filelist_to_archive If this is set to true a file list
+ * in the CSV format will be added to the archive. Its name is hardcoded
+ * to archive_filelist.csv. The default value of $add_filelist_to_archive
+ * is false which means no file list is added.
+ *
+ * @return bool True, if the archive file was created and saved successfully
+ * at $archive_file_path, false otherwise.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs a general exception or a more
+ * special exception is thrown.
+ */
+ public static function createArchive(
+ $file_area_objects = [],
+ $user_id = null,
+ $archive_file_path = '',
+ $do_user_permission_checks = true,
+ $keep_hierarchy = true,
+ $ignore_user = false,
+ $zip_encoding = 'UTF-8',
+ $add_filelist_to_archive = false
+ )
+ {
+
+ // check if archive path is set:
+ if (!$archive_file_path) {
+ throw new FileArchiveManagerException(
+ _('Der Zielpfad für das Archiv wurde nicht angegeben!')
+ );
+ }
+
+ // $file_area_objects must be a non-empty array!
+ // Otherwise we would return an empty Zip archive.
+ if (!is_array($file_area_objects) || empty($file_area_objects)) {
+ throw new FileArchiveManagerException(
+ _('Es wurden keine Dateien ausgewählt!')
+ );
+ }
+
+ // We can create the Zip archive now since its path exists in the file system
+ // and furthermore there are file area objects available.
+ $archive = new Studip\ZipArchive();
+ if (!$archive->open($archive_file_path, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
+ throw new FileArchiveManagerException('Error opening new ZIP archive!');
+ }
+ $archive->setOutputEncoding($zip_encoding);
+
+ //If $file_list is not an array
+ //then no files are added to the file list.
+ $file_list = null;
+ if ($add_filelist_to_archive) {
+ $file_list = [];
+ }
+
+ foreach ($file_area_objects as $file_area_object) {
+ if ($file_area_object instanceof FileRef) {
+ self::addFileRefToArchive(
+ $archive,
+ $file_area_object,
+ $user_id,
+ '',
+ $do_user_permission_checks,
+ $ignore_user,
+ $file_list
+ );
+ } elseif ($file_area_object instanceof FileType) {
+ self::addFileTypeToArchive(
+ $archive,
+ $file_area_object,
+ $user_id,
+ '',
+ $do_user_permission_checks,
+ $ignore_user,
+ $file_list
+ );
+ } elseif ($file_area_object instanceof Folder || $file_area_object instanceof FolderType) {
+ $folder = $file_area_object;
+ if ($folder instanceof Folder) {
+ //We use FolderType instances here.
+ $folder = $folder->getTypedFolder();
+ }
+
+ self::addFolderToArchive(
+ $archive,
+ $folder,
+ $user_id,
+ '',
+ $do_user_permission_checks,
+ $keep_hierarchy,
+ $ignore_user,
+ $file_list
+ );
+ }
+ }
+
+ if ($archive->numFiles > 0) {
+ //At least one file is in the archive.
+
+ if ($add_filelist_to_archive) {
+ //If a file list shall be included in the ZIP archive
+ //we must now make a CSV file out of file_list:
+
+ $csv_data = array_merge(
+ [
+ [
+ _('Name'),
+ _('Größe'),
+ _('Vorname'),
+ _('Nachname'),
+ _('Downloads'),
+ _('Datum'),
+ _('Pfad')
+ ]
+ ],
+ $file_list
+ );
+
+ //The CSV file has been generated.
+ //Now we must add it to the archive:
+ $archive->addFromString('archive_filelist.csv', array_to_csv($csv_data));
+ }
+
+ //Now the ZIP file is really finished:
+ return $archive->close();
+ }
+
+ //empty archive
+ throw new FileArchiveManagerException(
+ _('Das ZIP Archiv enthält keine Dateien!')
+ );
+ }
+
+ /**
+ * Puts files (identified by their file refs) into one file archive.
+ *
+ * @param FileRef[] $file_refs Array of FileRef objects.
+ * @param User $user The user who wishes to pack files.
+ * @param string $archive_file_path The path for the archive file.
+ * @param bool $do_user_permission_checks Set to true if reading/downloading
+ * permissions shall be checked. False otherwise. Default is true.
+ *
+ * @return bool True, if the archive file was created and saved successfully
+ * at $archive_file_path, false otherwise.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs
+ * a general exception or a more special exception is thrown.
+ */
+ public static function createArchiveFromFileRefs(
+ $file_refs,
+ User $user,
+ $archive_file_path = '',
+ $do_user_permission_checks = true
+ )
+ {
+ if (!$archive_file_path) {
+ throw new FileArchiveManagerException(
+ _('Der Zielpfad für das Archiv wurde nicht angegeben!')
+ );
+ }
+
+ //We must now collect all the files from these FileRefs and copy them
+ //into the new archive file.
+
+ return self::createArchive(
+ $file_refs,
+ $user->id,
+ $archive_file_path,
+ $do_user_permission_checks,
+ false //do not keep the file hierarchy
+ );
+ }
+
+ /**
+ * Returns all children of a folder type.
+ *
+ * @param FolderType $folder
+ * @return array
+ */
+ private static function getFolderChildren(FolderType $folder)
+ {
+ $children = [];
+ foreach ($folder->subfolders as $folder) {
+ $children[] = $folder;
+ }
+ foreach ($folder->file_refs as $ref) {
+ $children[] = $ref;
+ }
+ return $children;
+ }
+
+ /**
+ * Creates an archive that contains all files of a course the given user
+ * is allowed to download.
+ *
+ * @param FolderType $folder The folder whose files shall be put inside an archive.
+ * @param string $user_id The ID of the user who wishes to put the course's files into an archive
+ * @param string $archive_file_path The path for the archive file.
+ * @param bool $do_user_permission_checks Set to true if reading/downloading permissions
+ * shall be checked. False otherwise. Default is true.
+ * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive.
+ * If $keep_hierarchy is set to false you will get an archive that contains only files
+ * and no subdirectories.
+ *
+ * @return bool True, if the archive file was created and saved successfully
+ * at $archive_file_path, false otherwise.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs
+ * a general exception or a more special exception is thrown.
+ */
+ public static function createArchiveFromFolder(
+ FolderType $folder,
+ $user_id = null,
+ $archive_file_path = '',
+ $do_user_permission_checks = true,
+ $keep_hierarchy = true
+ )
+ {
+ return self::createArchive(
+ self::getFolderChildren($folder),
+ $user_id,
+ $archive_file_path,
+ $do_user_permission_checks,
+ $keep_hierarchy
+ );
+ }
+
+ /**
+ * Creates an archive that contains all files of a course the given user
+ * is allowed to download.
+ *
+ * @param string $course_id The ID of the course whose files shall be put inside an archive.
+ * @param string $user_id The ID of the user who wishes to put the course's files into an archive
+ * @param string $archive_file_path The path for the archive file.
+ * @param bool $do_user_permission_checks Set to true if reading/downloading permissions
+ * shall be checked. False otherwise. Default is true.
+ * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive.
+ * If $keep_hierarchy is set to false you will get an archive that contains only files
+ * and no subdirectories.
+ *
+ * @return bool True, if the archive file was created and saved successfully
+ * at $archive_file_path, false otherwise.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs
+ * a general exception or a more special exception is thrown.
+ */
+ public static function createArchiveFromCourse(
+ $course_id,
+ $user_id = null,
+ $archive_file_path = '',
+ $do_user_permission_checks = true,
+ $keep_hierarchy = true
+ )
+ {
+ $folder = Folder::findTopFolder($course_id);
+ if (!$folder) {
+ return null;
+ }
+
+ $folder = $folder->getTypedFolder();
+ if (!$folder) {
+ return null;
+ }
+
+ return self::createArchive(
+ self::getFolderChildren($folder),
+ $user_id,
+ $archive_file_path,
+ $do_user_permission_checks,
+ $keep_hierarchy
+ );
+ }
+
+
+ /**
+ * Creates an archive that contains all files of an institute the given user
+ * is allowed to download.
+ *
+ * @param string $institute_id The ID of the institute whose files shall be put inside an archive.
+ * @param string $user_id The ID of the user who wishes to put the institute's files into an archive
+ * @param string $archive_file_path The path for the archive file.
+ * @param bool $do_user_permission_checks Set to true if reading/downloading permissions
+ * shall be checked. False otherwise. Default is true.
+ * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive.
+ * If $keep_hierarchy is set to false you will get an archive that contains only files
+ * and no subdirectories.
+ *
+ * @return bool True, if the archive file was created and saved successfully
+ * at $archive_file_path, false otherwise.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs
+ * a general exception or a more special exception is thrown.
+ */
+ public static function createArchiveFromInstitute(
+ $institute_id,
+ $user_id = null,
+ $archive_file_path = '',
+ $do_user_permission_checks = true,
+ $keep_hierarchy = true)
+ {
+ $folder = Folder::findTopFolder($institute_id);
+ if(!$folder) {
+ return null;
+ }
+
+ $folder = $folder->getTypedFolder();
+ if(!$folder) {
+ return null;
+ }
+
+ return self::createArchive(
+ self::getFolderChildren($folder),
+ $archive_file_path,
+ $do_user_permission_checks,
+ $keep_hierarchy
+ );
+ }
+
+ /**
+ * Creates an archive that contains all files of a user, if the current
+ * user has root permissions to do this.
+ *
+ * @param string $user_id The ID of the user whose files shall be put inside an archive.
+ * @param string $archive_file_path The path for the archive file.
+ * @param bool $do_user_permission_checks Set to true if reading/downloading permissions
+ * shall be checked. False otherwise. Default is true.
+ * @param bool $keep_hierarchy True, if the file hierarchy shall be kept inside the archive.
+ * If $keep_hierarchy is set to false you will get an archive that contains only files
+ * and no subdirectories.
+ *
+ * @return bool True, if the archive file was created and saved successfully
+ * at $archive_file_path, false otherwise.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs
+ * a general exception or a more special exception is thrown.
+ */
+ public static function createArchiveFromUser(
+ $user_id,
+ $archive_file_path = '',
+ $do_user_permission_checks = true,
+ $keep_hierarchy = true
+ )
+ {
+ $folder = Folder::findTopFolder($user_id);
+ if (!$folder) {
+ return null;
+ }
+
+ $folder = $folder->getTypedFolder();
+ if (!$folder) {
+ return null;
+ }
+
+ return self::createArchive(
+ self::getFolderChildren($folder),
+ $archive_file_path,
+ $do_user_permission_checks,
+ $keep_hierarchy
+ );
+ }
+
+
+ /**
+ * This method creates an archive with the content of a physical folder
+ * (A folder inside the operating system's file system).
+ *
+ * @param string $folder_path The path to the physical folder
+ * which content shall be added to a file archive.
+ * @param string $archive_file_path The path to the archive file which
+ * shall be created.
+ *
+ * @return True, if all files were added successfully, false otherwise.
+ *
+ * @throws Exception|FileArchiveManagerException If an error occurs
+ * a general exception or a more special exception is thrown.
+ */
+ public static function createArchiveFromPhysicalFolder($folder_path, $archive_file_path)
+ {
+ if (!$folder_path || !$archive_file_path) {
+ //we can't work with empty paths!
+ return false;
+ }
+
+ if (!file_exists($folder_path)) {
+ //path to physical folder does not exist!
+ throw new FileArchiveManagerException(
+ _('Der Ordner wurde im Dateisystem des Servers nicht gefunden!')
+ );
+ }
+
+ //Put all the content of the folder inside an archive:
+ $archive = Studip\ZipArchive::create($archive_file_path, true);
+ $result = $archive->addFromPath($folder_path);
+ $archive->close();
+ return $result;
+ }
+
+ //ARCHIVE EXTRACTION METHODS
+
+ /**
+ * This is a helper method that builds a subfolder hierarchy inside
+ * a folder by looking at a string representing a file system path.
+ *
+ * The variable $path contains a hierarchy of subfolders that shall be created
+ * inside the given folder. If $path contains "folder1/folder2/folder3" then
+ * the given folder will get a subfolder named "folder1". The folder
+ * "folder1" itself will get a subfolder named "folder2" and so on.
+ *
+ * @param FolderType $folder The folder where a subfolder path shall be created.
+ * @param User $user The user who wishes to create the path.
+ * @param string $path The path which shall be created inside $folder.
+ *
+ * @return FolderType[] An array with FolderType objects representing
+ * each element of $path.
+ */
+ public static function createFolderPath(FolderType $folder, User $user, $path = '')
+ {
+ if (!$path) {
+ return [];
+ }
+
+ // now we strip leading and trailing slashes, whitespaces and other characters:
+ // then we convert path into an array of strings:
+ $path = trim($path, ' /');
+ $path = explode('/', $path);
+
+ //now we loop through path and build subfolders:
+ $folder_path = [];
+
+ $current_folder = $folder;
+ foreach ($path as $new_folder_name) {
+ //first we check if the folder already exists:
+ foreach ($current_folder->getSubfolders() as $subfolder) {
+ if ($subfolder->name === $new_folder_name) {
+ //We have found a folder that has the name $new_folder_name:
+ //No need to create a new folder, we can use that folder
+ //and continue with it:
+ $current_folder = $subfolder;
+ $folder_path[] = $subfolder;
+
+ //start next iteration of the outer foreach loop:
+ continue 2;
+ }
+ }
+
+ //If code execution has reached this point we have looped
+ //throug all subfolders of the current folder and couldn't find
+ //any subfolder that matches the name given in $new_folder_name.
+ //Therefore we must create a new folder here, if possible:
+
+ //Check the user's permissions first:
+ if ($current_folder->isSubfolderAllowed($user->id)) {
+ //Create a subfolder:
+ $result = FileManager::createSubFolder(
+ $current_folder,
+ $user,
+ get_class($current_folder) === RootFolder::class ? StandardFolder::class : get_class($current_folder),
+ $new_folder_name
+ );
+
+ if ($result instanceof FolderType) {
+ $folder_path[] = $result;
+ }
+ }
+ }
+ return $folder_path;
+ }
+
+ /**
+ * Extracts one file from an opened archive and stores it in a folder.
+ *
+ * @param ZipArchive $archive The archive from which a file shall be extracted.
+ * @param string $archive_path The path of the file in the archive.
+ * @param FolderType $target_folder The folder where the file shall be stored.
+ * @param User $user The user who wishes to extract the file from the archive.
+ *
+ * @return FileType|null FileType instance on success, null otherwise.
+ */
+ public static function extractFileFromArchive(
+ Studip\ZipArchive $archive,
+ $archive_path,
+ FolderType $target_folder,
+ User $user
+ )
+ {
+ $file_resource = $archive->getStream($archive_path);
+ $file_info = $archive->statName($archive_path);
+
+ if (!$file_resource) {
+ return null;
+ }
+
+ $studip_file = new File();
+ $studip_file->user_id = $user->id;
+ $studip_file->name = $archive->convertArchiveFilename(basename($archive_path));
+ $studip_file->mime_type = get_mime_type($studip_file->name);
+ $studip_file->size = $file_info['size'];
+ $studip_file->id = $studip_file->getNewId();
+ //$file->store();
+
+ // Ok, we have a file object in the database. Now we must connect
+ // it with the data file by extracting the data file into
+ // the place, where the file's content has to be placed.
+ $file_dir = pathinfo($studip_file->getPath(), PATHINFO_DIRNAME);
+ $file_path = $file_dir . '/' . $studip_file->id;
+
+ // Create the directory for the file, if necessary:
+ if (!is_dir($file_dir)) {
+ mkdir($file_dir);
+ }
+
+ // Ok, now we read all data from $file_resource and put it into
+ // the file's path:
+ if (file_put_contents($file_path, $file_resource) === false) {
+ //Something went wrong: abort and clean up!
+ //$file->delete();
+ return null;
+ }
+
+ // Ok, we now must create a File:
+ $file_ref = new FileRef();
+ $file_ref->file_id = $studip_file->id;
+ $file_ref->folder_id = $target_folder->getId();
+ $file_ref->user_id = $user->id;
+ $file_ref->name = $studip_file->name;
+ $file_ref->file = $studip_file;
+ $file = new StandardFile($file_ref);
+ if ($saved_file = $target_folder->addFile($file, $user->id)) {
+ return $saved_file;
+ }
+
+ //Something went wrong:
+ return null;
+ }
+
+ /**
+ * Extracts an archive into a folder inside the Stud.IP file area.
+ *
+ * @param FileType $archive_file The archive file which shall be extracted.
+ * @param FolderType $folder The folder where the archive shall be extracted.
+ * @param string $user_id The ID of the user who wants to extract the archive.
+ *
+ * @return FileType[] Array with extracted files, represented as FileRef objects.
+ */
+ public static function extractArchiveFileToFolder(
+ FileType $archive_file,
+ FolderType $folder,
+ $user_id = null
+ )
+ {
+ $user = $user_id ? User::find($user_id) : User::findCurrent();
+ if (!$user) {
+ return [];
+ }
+
+ // Determine, if the folder is writable for the user identified by $user_id:
+ if (!$folder->isWritable($user->id)) {
+ return [];
+ }
+
+ // Determine if we can keep the zip archive's folder hierarchy:
+ $keep_hierarchy = $folder->isSubfolderAllowed($user->id);
+
+ $archive = new Studip\ZipArchive();
+ $standard_archive_file = $archive_file->convertToStandardFile();
+ if (!($standard_archive_file instanceof StandardFile)) {
+ //Error converting the archive file.
+ return [];
+ }
+ $archive->open($standard_archive_file->getPath());
+
+ // loop over all entries in the zip archive and put each entry
+ // in the current folder or one of its subfolders:
+ $files = [];
+
+ for ($i = 0; $i < $archive->numFiles; $i++) {
+ $entry_info = $archive->statIndex($i);
+ $entry_info_name = $archive->convertArchiveFilename($entry_info['name']);
+ // split the entry's path into its path and its name component:
+ $entry_path = ltrim(pathinfo($entry_info_name, PATHINFO_DIRNAME), '.');
+ $entry_name = pathinfo($entry_info_name, PATHINFO_BASENAME);
+
+ // check if $entry_info['name'] ends with a slash:
+ // In that case it is a directory entry:
+ $entry_is_directory = preg_match('/\/$/', $entry_info_name);
+
+ //The folder where the extracted file/folder shall be inserted:
+ $extracted_entry_destination_folder = $folder;
+
+ if ($keep_hierarchy) {
+ //Keep the archive's folder hierarchy:
+ //We may have to create subfolders.
+ if (basename($entry_path)) {
+ //The file/folder doesn't lie in the "top folder" of the archive:
+ //Pass the path to createFolderPath and let it generate
+ //a folder path before extracting the file:
+ $folder_path = self::createFolderPath(
+ $folder,
+ $user,
+ $entry_path
+ );
+
+ //Get the last element of $folder_path:
+ $last_folder_path_element = array_pop($folder_path);
+
+ //Compare $extracted_entry_destination_folder's name with the name of the
+ //last path item in $file_archive_path. Only if they are equal
+ //we can use that folder to store the file. Otherwise
+ //we must continue with the next file entry in the archive:
+ if ($last_folder_path_element
+ && $last_folder_path_element->name === basename($entry_path))
+ {
+ $extracted_entry_destination_folder = $last_folder_path_element;
+ }
+ }
+ }
+
+ if ($entry_is_directory) {
+ //We have to create a subfolder if it doesn't exist yet:
+ self::createFolderPath(
+ $extracted_entry_destination_folder,
+ $user,
+ $entry_name
+ );
+ } else {
+ //we extract one file:
+ //$entry_info['name'] is necessary because we need the full path
+ //to the entry inside the archive.
+ $file = self::extractFileFromArchive(
+ $archive,
+ $entry_info['name'],
+ $extracted_entry_destination_folder,
+ $user
+ );
+
+ if ($file instanceof FileType) {
+ $files[] = $file;
+ }
+ }
+ }
+
+ return $files;
+ }
+}