<?php /** * FileArchiveManager.class.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 . $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 %d bytes!'), $archive_max_size ) ); } if (is_array($file_list)) { $user = $file_type->getUser(); $file_list[] = [ 'name' => $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 { //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 . $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 %d bytes!'), $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' => $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()) ]; 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 %d bytes!'), $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 .= $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 FileRef|null FileRef 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; } $file = new File(); $file->user_id = $user->id; $file->name = $archive->convertArchiveFilename(basename($archive_path)); $file->mime_type = get_mime_type($file->name); $file->size = $file_info['size']; $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_path = pathinfo($file->getPath(), PATHINFO_DIRNAME); // Create the directory for the file, if necessary: if (!is_dir($file_path)) { mkdir($file_path); } // Ok, now we read all data from $file_resource and put it into // the file's path: if (file_put_contents($file->getPath(), $file_resource) === false) { //Something went wrong: abort and clean up! $file->delete(); return null; } // Ok, we now must create a FileRef: $file_ref = new FileRef(); $file_ref->file_id = $file->id; $file_ref->folder_id = $target_folder->getId(); $file_ref->user_id = $user->id; $file_ref->name = $file->name; if ($file_ref->store()) { return $file_ref; } //Something went wrong: abort and clean up! $file_ref->delete(); return null; } /** * Extracts an archive into a folder inside the Stud.IP file area. * * @param FileRef $archive_file_ref 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 FileRef[] Array with extracted files, represented as FileRef objects. */ public static function extractArchiveFileToFolder( FileRef $archive_file_ref, 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(); $archive->open($archive_file_ref->file->getPath()); // loop over all entries in the zip archive and put each entry // in the current folder or one of its subfolders: $file_refs = []; 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_ref = self::extractFileFromArchive( $archive, $entry_info['name'], $extracted_entry_destination_folder, $user ); if ($file_ref instanceof FileRef) { $file_refs[] = $file_ref; } } } return $file_refs; } }