diff options
| author | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
|---|---|---|
| committer | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
| commit | 4459dd7917f4d1c34f40bb68f0e991e9c3d53e4c (patch) | |
| tree | 5c07151ae61276d334e88f6309c30d439a85c12e /lib/filesystem/FileArchiveManager.php | |
| parent | da0022e5c1abbf9825ae76debaabdff7e8623bb4 (diff) | |
| parent | 97a188592c679890a25c37ab78463add76a52ff7 (diff) | |
Merge branch 'main' into issue-3911issue-3911
Diffstat (limited to 'lib/filesystem/FileArchiveManager.php')
| -rw-r--r-- | lib/filesystem/FileArchiveManager.php | 998 |
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; + } +} |
