<?php /** * FileManager.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 André Noack <noack@data-quest.de> * @author Moritz Strohm <strohm@data-quest.de> * @copyright 2016 Stud.IP Core-Group * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 * @category Stud.IP */ /** * The FileManager class contains methods that faciliate the management of files * and folders. Furthermore its methods perform necessary additional checks * so that files and folders are managed in a correct manner. * * It is recommended to use the methods of this class for file and folder * management instead of writing own methods. */ class FileManager { //FILE HELPER METHODS /** * Removes special characters from the file name (and by that cleaning * the file name) so that the file name which is returned by this method * works on every operating system. * * @param string $file_name The file name that shall be "cleaned". * @param bool $shorten_name True, if the file name shall be shortened to * 31 characters. False, if the full length shall be kept (default). * * @return string The "cleaned" file name. */ public static function cleanFileName($file_name = null, $shorten_name = false) { if(!$file_name) { //If you put an empty string in, you will get an empty string out! return $file_name; } $bad_characters = [ ':', chr(92), '/', '"', '>', '<', '*', '|', '?', ' ', '(', ')', '&', '[', ']', '#', chr(36), '\'', '*', ';', '^', '`', '{', '}', '|', '~', chr(255) ]; $replacement_characters = [ '', '', '', '', '', '', '', '', '', '_', '', '', '+', '', '', '', '', '', '', '-', '', '', '', '', '-', '', '' ]; //All control characters shall be deleted: for($i = 0; $i < 0x20; $i++) { $bad_characters[] = chr($i); $replacement_characters[] = ''; } $clean_file_name = str_replace( $bad_characters, $replacement_characters, $file_name ); if($clean_file_name[0] == '.') { $clean_file_name = mb_substr($clean_file_name, 1, mb_strlen($clean_file_name)); } if($shorten_name === true) { //If we have to shorten the file name we have to split it up //into file name and extension. $tmp_file_name = pathinfo($clean_file_name, PATHINFO_FILENAME); $file_extension = pathinfo($clean_file_name, PATHINFO_EXTENSION); $clean_file_name = mb_substr($tmp_file_name, 0, 28) . '.' . $file_extension; } return $clean_file_name; } /** * Returns the icon name for a given mime type. * * @param string $mime_type The mime type whose icon is requested. * * @return string The icon name for the mime type. */ public static function getIconNameForMimeType($mime_type = null) { $application_category_icons = [ 'file-pdf' => ['pdf'], 'file-ppt' => ['powerpoint','presentation'], 'file-excel' => ['excel', 'spreadsheet', 'csv'], 'file-word' => ['word', 'wordprocessingml', 'opendocument.text', 'rtf'], 'file-archive' => ['zip', 'rar', 'arj', '7z'], ]; if (!$mime_type) { //No mime type given: We can only assume it is a generic file. return 'file-generic'; } [$category, $type] = explode('/', $mime_type, 2); switch($category) { case 'image': return 'file-pic'; case 'audio': return 'file-audio'; case 'video': return 'file-video'; case 'text': if ($type === 'csv') { //CSV files: return 'file-excel'; } //other text files: return 'file-text'; case 'application': //loop through all application category icons //and return the icon name that matches the regular expression //for an application mime type: foreach ($application_category_icons as $icon_name => $type_name) { if (preg_match('/' . implode('|', $type_name) . '/i', $type)) { return $icon_name; } } } //If code execution reaches this point, no special mime type icon //was detected. return 'file-generic'; } /** * Returns the icon for a given mime type. * * @param string $mime_type The mime type whose icon is requested. * @param string $role The requested role * @param array $attributes Optional additional attributes * * @return Icon The icon for the mime type. */ public static function getIconForMimeType( $mime_type = null, $role = Icon::ROLE_CLICKABLE, $attributes = [] ) { $icon = self::getIconNameForMimeType($mime_type); return Icon::create($icon, $role, $attributes); } /** * Returns the icon for a given file ref. * * @param FileRef|stdClass $ref The file ref whose icon is requested. * @param string $role The requested role * @param array $attributes Optional additional attributes * @return Icon The icon for the file ref. */ public static function getIconForFileRef($ref, $role = Icon::ROLE_CLICKABLE, array $attributes = []) { return self::getIconForMimeType($ref->mime_type); } /** * Builds a download URL for the file archive of an archived course. * * @param ArchivedCourse $archived_course An archived course whose file * archive is requested. * @param bool $protected_archive True, if the protected file archive * is requested. False, if the "readable for everyone" file archive * is requested (default). * * @return string The download link for the file or an empty string on failure. */ public static function getDownloadURLForArchivedCourse( ArchivedCourse $archived_course, $protected_archive = false ) { $file_id = $protected_archive ? $archived_course->archiv_protected_file_id : $archived_course->archiv_file_id; if ($file_id) { $file_name = sprintf( '%s-%s.zip', $protected_archive ? _('Geschützte Dateisammlung') : _('Dateisammlung'), mb_substr($archived_course->name, 0, 200) ); //file_id is set: file archive exists return URLHelper::getURL('sendfile.php', [ 'type' => 1, 'file_id' => $file_id, 'file_name' => $file_name, 'force_download' => true, //because archive files are ZIP files ], true); } //file_id is empty: no file archive available return ''; } /** * Builds a download link for the file archive of an archived course. * * @param ArchivedCourse $archived_course An archived course whose file * archive is requested. * @param bool $protected_archive True, if the protected file archive * is requested. False, if the "readable for everyone" file archive * is requested (default). * * @return string The download link for the file or an empty string on failure. */ public static function getDownloadLinkForArchivedCourse( ArchivedCourse $archived_course, $protected_archive = false ) { return htmlReady( self::getDownloadURLForArchivedCourse( $archived_course, $protected_archive ) ); } /** * Builds a download link for temporary files. */ public static function getDownloadLinkForTemporaryFile( $temporary_file_name = null, $download_file_name = null ) { return htmlReady( self::getDownloadURLForTemporaryFile( $temporary_file_name, $download_file_name ) ); } /** * Builds a download URL for temporary files. */ public static function getDownloadURLForTemporaryFile( $temporary_file_name = null, $download_file_name = null ) { return URLHelper::getURL('sendfile.php', [ 'token' => Token::create(), 'type' => 4, 'file_id' => $temporary_file_name, 'file_name' => $download_file_name, 'force_download' => true, //because temporary files have a reason for their name ], true); } //FILE METHODS /** * This is a helper method that checks an uploaded file for errors * which appeared during upload. * @param array $uploaded_file */ public static function checkUploadedFileStatus($uploaded_file) { $errors = []; if ($uploaded_file['error'] === UPLOAD_ERR_INI_SIZE) { $errors[] = sprintf(_('Ein Systemfehler ist beim Upload aufgetreten. Fehlercode: %s.'), 'upload_max_filesize=' . ini_get('upload_max_filesize')); } elseif ($uploaded_file['error'] > 0) { $errors[] = sprintf( _('Ein Systemfehler ist beim Upload aufgetreten. Fehlercode: %s.'), $uploaded_file['error'] ); } return $errors; } /** * Handles uploading one or more files * * @param array $uploaded_files A two-dimensional array with file data for all uploaded files. * The array has the following structure in the second dimension: * [ * 'name': The name of the file * 'error': An integer telling if there were errors. 0, if no errors occured. * 'type': The uploaded file's mime type. * 'tmp_name': Name of the temporary file that was created right after the upload. * 'size': Size of the uploaded file in bytes. * ] * @param FolderType $folder the folder where the files are inserted * @param string $user_id the ID of the user who wants to upload files * * @return array Array with the created file objects and error strings */ public static function handleFileUpload(array $uploaded_files, FolderType $folder, $user_id = null) { $user_id || $user_id = $GLOBALS['user']->id; $result = []; $error = []; //check if user has write permissions for the folder: if (!$folder->isWritable($user_id)) { $error[] = _('Keine Schreibrechte für Zielordner!'); return compact('error'); } //Check if uploaded files[name] is an array. //This check is necessary to find out, if $uploaded_files is a //two-dimensional array. Each index of the first dimension //contains an array attribute for uploaded files, one entry per file. if (is_array($uploaded_files['name'])) { foreach ($uploaded_files['name'] as $key => $filename) { $uploaded_file = StandardFile::create([ 'name' => $filename, 'type' => $uploaded_files['type'][$key] ?: get_mime_type($filename), 'size' => $uploaded_files['size'][$key], 'tmp_name' => $uploaded_files['tmp_name'][$key], 'error' => $uploaded_files['error'][$key] ]); if ($uploaded_file instanceof FileType) { //validate the upload by looking at the folder where the //uploaded file shall be stored: if ($folder_error = $folder->validateUpload($uploaded_file, $user_id)) { $error[] = $folder_error; $uploaded_file->delete(); continue; } $new_reference = $folder->addFile($uploaded_file, $user_id); if (!$new_reference){ $error[] = _('Ein Systemfehler ist beim Upload aufgetreten.'); } else { $result['files'][] = $new_reference; } } else { $error = array_merge($error, $uploaded_file); } } } return array_merge($result, compact('error')); } /** * This method handles updating the File a FileRef is pointing to. * * The parameters $source, $user and $uploaded_file_data are required * for this method to work. * * @param FileRef $source The file reference pointing to a file that * shall be updated. * @param User $user The user who wishes to update the file. * @param array $uploaded_file_data The data of the uploaded new version * of the file that is going to be updated. * @param bool $update_filename True, if the file name of the File and the * FileRef shall be set to the name of the uploaded new version * of the file. False otherwise. * @param bool $update_other_references If other FileRefs pointing to the * File that is going to be updated shall be updated too, set this * to True. In case only the FileRef $source and its file shall be * updated, set this to False. In the latter case the File will be * copied and the copy gets updated. * * @return FileRef|string[] On success the updated $source FileRef is returned. * On failure an array with error messages is returned. */ public static function updateFileRef( FileRef $source, User $user, $uploaded_file_data = [], $update_filename = false, $update_other_references = false ) { $errors = []; // Do some checks: $folder = $source->getFolderType(); $source_file = $source->getFileType(); if (!$source_file->isEditable($user->id)) { $errors[] = sprintf( _('Sie sind nicht dazu berechtigt, die Datei %s zu aktualisieren!'), $source->name ); return $errors; } // Check if $uploaded_file_data has valid data in it: $upload_error = $folder->validateUpload($source_file, $user->id); if ($upload_error) { $errors[] = $upload_error; return $errors; } // Ok, checks are completed: We can start updating the file. // If we don't update other file references that point to the File instance // we must first copy the file and then link the $source FileRef to the // new file: if (!$source->file) { if (!$update_other_references) { if (!$update_filename) { $uploaded_file_data['name'] = $source->name; } else { if (!$folder->deleteFile($source->getId())){ $errors[] = _('Aktualisierte Datei konnte nicht ins Stud.IP Dateisystem übernommen werden!'); return $errors; } } $new_reference = $folder->addFile($source_file); if (!$new_reference) { $errors[] = _('Aktualisierte Datei konnte nicht ins Stud.IP Dateisystem übernommen werden!'); return $errors; } } return $new_reference; } if ($update_other_references) { // We want to update all file references. In that case we can just // use the $source FileRef's file directly. $data_file = $source->file; $connect_success = $data_file->connectWithDataFile($uploaded_file_data['tmp_name']); if (!$connect_success) { $errors[] = _('Aktualisierte Datei konnte nicht ins Stud.IP Dateisystem übernommen werden!'); return $errors; } // moving the file was successful: // update the File object: $data_file->size = filesize($data_file->getPath()); $data_file->mime_type = get_mime_type($uploaded_file_data['name']); if ($update_filename) { $data_file->name = $uploaded_file_data['name']; } $data_file->user_id = $user->id; $data_file->store(); } else { // If we want to keep the old version of the file in all other // File references we must create a new File object and link // the $source FileRef to it: $upload_errors = self::checkUploadedFileStatus($uploaded_file_data); if ($upload_errors) { $errors = array_merge($errors, $upload_errors); } $data_file = new File(); $data_file->user_id = $user->id; $data_file->id = $data_file->getNewId(); $connect_success = $data_file->connectWithDataFile($uploaded_file_data['tmp_name']); if (!$connect_success) { $errors[] = _('Aktualisierte Datei konnte nicht ins Stud.IP Dateisystem übernommen werden!'); return $errors; } // moving the file was successful: // update the File object: $data_file->size = filesize($data_file->getPath()); $data_file->mime_type = get_mime_type($uploaded_file_data['name']); if ($update_filename) { $data_file->name = $uploaded_file_data['name']; } else { $data_file->name = $source->file->name; } $data_file->store(); $source->file = $data_file; $source->store(); } if ($update_filename) { $source->name = $uploaded_file_data['name']; if ($source->isDirty()) { $source->store(); } //We must find all FileRefs that point to $data_file //and change their name, too: $other_file_refs = FileRef::findBySql('file_id = :file_id AND id <> :source_id', [ 'file_id' => $source->file_id, 'source_id' => $source->id ]); foreach ($other_file_refs as $other_file_ref) { $other_file_ref->name = $uploaded_file_data['name']; if ($other_file_ref->isDirty()) { $other_file_ref->store(); } } } // Update author FileRef::findEachBySQL( function ($ref) use ($user) { $ref->user_id = $user->id; $ref->store(); }, 'file_id = :file_id', ['file_id' => $source->file_id] ); //Everything went fine: Return the updated $source FileRef: return $source; } /** * This method handles editing file reference attributes. * * Checks that have to be made during the editing of a file reference are placed * in this method so that a controller can simply call this method * to change attributes of a file reference. * * At least one of the three parameters name, description and * content_terms_of_use_id must be set. Otherwise this method * will do nothing. * * @param FileRef $file_ref The file reference that shall be edited. * @param User $user The user who wishes to edit the file reference. * @param string|null $name The new name for the file reference * @param string|null $description The new description for the file reference. * @param string|null $content_terms_of_use_id The ID of the new ContentTermsOfUse object. * @param string|null $url The new URL for the file to link to. * This is only regarded if the file_ref points to an URL instead * of a file stored by Stud.IP. * * @return FileRef|string[] The edited FileRef object on success, string array with error messages on failure. */ public static function editFileRef( FileRef $file_ref, User $user, $name = null, $description = null, $content_terms_of_use_id = null, $url = null ) { if (!$name && !$description && !$content_terms_of_use_id) { //nothing to do, no errors: return $file_ref; } if (!$file_ref->folder) { return [_('Dateireferenz ist keinem Ordner zugeordnet!')]; } $folder_type = $file_ref->folder->getTypedFolder(); if (!$folder_type) { return [_('Ordnertyp konnte nicht ermittelt werden!')]; } if (!$file_ref->getFileType()->isEditable($user->id)) { return [sprintf( _('Ungenügende Berechtigungen zum Bearbeiten der Datei %s!'), $file_ref->name )]; } // check if name is set and is different from the current name // of the file reference: if ($name && $name !== $file_ref->name) { // name is special: we have to check if files/folders in // the file_ref's folder have the same name. If so, we must // make it unique. $folder = $file_ref->folder; if (!$folder) { return [sprintf( _('Verzeichnis von Datei %s nicht gefunden!'), $file_ref->name )]; } $file_ref->name = $name; } if ($description !== null) { //description may be an empty string which is allowed here $file_ref->description = $description; } if ($content_terms_of_use_id !== null) { $content_terms_of_use = ContentTermsOfUse::find($content_terms_of_use_id); if (!$content_terms_of_use) { return [sprintf( _('Inhalts-Nutzungsbedingungen mit ID %s nicht gefunden!'), $content_terms_of_use_id )]; } $file_ref->content_terms_of_use_id = $content_terms_of_use->id; } if ($file_ref->isLink() && $url !== null) { $file_ref->file->setURL($url); if ($file_ref->file->isDirty()) { $file_ref->file->store(); } if ($file_ref->file->file_url->isDirty()) { $file_ref->file->file_url->store(); } } if (!$file_ref->isDirty() || $file_ref->store()) { //everything went fine return $file_ref; } //error while saving the changes! return [sprintf( _('Fehler beim Speichern der Änderungen bei Datei %s'), $file_ref->name )]; } /** * This method handles copying a file to a new folder. * * If the user (given by $user) is the owner of the file (by looking at the user_id * in the file reference) we can just make a new reference to that file. * Else, we must copy the file and its content. * * The file name is altered when a file with the identical name exists in * the destination folder. In that case, only the name in the FileRef object * of the file is altered and the File object's name is unchanged. * * @param FileType $source The file reference for the file that shall be copied. * @param FolderType $destination_folder The destination folder for the file. * @param User $user The user who wishes to copy the file. * * @return FileType|string[] The copied FileType object on success or an array with error messages on failure. */ public static function copyFile(FileType $source, FolderType $destination_folder, User $user) { // first we have to make sure if the user has the permissions to read the source folder // and the permissions to write to the destination folder: $source_folder = $source->getFolderType(); if (!$source_folder) { return [_('Ordnertyp des Quellordners konnte nicht ermittelt werden!')]; } if (!$source_folder->isReadable($user->id) || !$destination_folder->isWritable($user->id)) { //the user is not permitted to read the source folder //or to write to the destination folder! return [ sprintf( _('Ungenügende Berechtigungen zum Kopieren der Datei %s in Ordner %s!'), $source->getFilename(), $destination_folder->name ) ]; } $error = $destination_folder->validateUpload($source, $user->id); if ($error && is_string($error)) { return [$error]; } $newfile = $destination_folder->addFile($source); if (!$newfile) { return [_('Daten konnten nicht kopiert werden!')]; } return $newfile; } /** * This method handles moving a file to a new folder. * * @param FileType $source The file reference for the file that shall be moved. * @param FolderType $destination_folder The destination folder. * @param User $user The user who wishes to move the file. * * @return FileRef|string[] $source FileRef object on success, Array with error messages on failure. */ public static function moveFile(FileType $source, FolderType $destination_folder, User $user) { $source_folder = $source->getFolderType(); if (!$source_folder) { return [_('Ordnertyp des Quellordners konnte nicht ermittelt werden!')]; } if (!$source_folder->isReadable($user->id) || !$source->isWritable($user->id) || !$destination_folder->isWritable($user->id)) { //the user is not permitted to read the source folder //or to write to the destination folder! return [ sprintf( _('Ungenügende Berechtigungen zum Kopieren der Datei %s in Ordner %s!'), $source->getFilename(), $destination_folder->name ) ]; } $source_plugin = PluginManager::getInstance()->getPlugin($source_folder->range_id); if (!$source_plugin) { $source_plugin = PluginManager::getInstance()->getPlugin($source_folder->range_type);; } $destination_plugin = PluginManager::getInstance()->getPlugin($destination_folder->range_id); if (!$destination_plugin) { $destination_plugin = PluginManager::getInstance()->getPlugin($destination_folder->range_type);; } if (!$source_plugin && !$destination_plugin && $source instanceof StandardFile) { $error = $destination_folder->validateUpload($source, $user->id); if (!$error) { $source_fileref = FileRef::find($source->getId()); $source_fileref->folder_id = $destination_folder->getId(); if ($source_fileref->store()) { $classname = get_class($source); return new $classname($source_fileref); } else { return [_('Datei konnte nicht gespeichert werden.')]; } } else { return [$error]; } } else { $copy = self::copyFile($source, $destination_folder, $user); if (!is_array($copy)) { $source_folder->deleteFile($source->getId()); } return $copy; } } /** * This method handles deletign a file reference. * * @param FileRef $file_ref The file reference that shall be deleted * @param User $user The user who wishes to delete the file reference. * * @return FileRef|string[] The FileRef object that was deleted from the database on success * or an array with error messages on failure. */ public static function deleteFileRef(FileRef $file_ref, User $user) { $folder_type = $file_ref->getFolderType(); if (!$folder_type) { return [_('Ordnertyp des Quellordners konnte nicht ermittelt werden!')]; } if (!$file_ref->getFileType()->isWritable($user->id)) { return [sprintf( _('Ungenügende Berechtigungen zum Löschen der Datei %s in Ordner %s!'), $file_ref->name )]; } if ($file_ref->getFileType()->delete()) { return $file_ref; } return [_('Dateireferenz konnte nicht gelöscht werden.')]; } /** * Handles the sub folder creation routine. * * @param FolderType $destination_folder The folder where the subfolder shall be linked. * @param User $user The user who wishes to create the subfolder. * @param string $folder_type_class_name The FolderType class name for the new folder * @param string $name The name for the new folder * @param string $description The description of the new folder * * @returns FolderType|string[] Either the FolderType object of the * new folder or an Array with error messages. * */ public static function createSubFolder( FolderType $destination_folder, User $user, $folder_type_class_name = null, $name = null, $description = null ) { $errors = []; if (!$folder_type_class_name) { // folder_type_class_name is not set: we can't create a folder! return [_('Es wurde kein Ordnertyp angegeben!')]; } // check if folder_type_class_name has a valid class: if (!is_subclass_of($folder_type_class_name, 'FolderType')) { return [sprintf( _('Die Klasse %s ist nicht von FolderType abgeleitet!'), $folder_type_class_name )]; } if (!$name) { //name is not set: we can't create a folder! return [_('Es wurde kein Ordnername angegeben!')]; } $sub_folder = new Folder(); $sub_folder_type = new $folder_type_class_name($sub_folder); //set name and description of the new folder: $sub_folder->name = $name; if ($description) { $sub_folder->description = $description; } // check if the sub folder type is creatable in a StandardFolder, // if the destination folder is a StandardFolder: if (!in_array($folder_type_class_name, ['InboxFolder', 'OutboxFolder'])) { if (!$folder_type_class_name::availableInRange($destination_folder->range_id, $user->id)) { $errors[] = sprintf( _('Ein Ordner vom Typ %s kann nicht in einem Ordner vom Typ %s erzeugt werden!'), get_class($sub_folder_type), get_class($destination_folder) ); } } if (!$destination_folder->isSubfolderAllowed($user->id)) { $errors[] = _('Sie sind nicht dazu berechtigt, einen Unterordner zu erstellen!'); } // we can return here if we have found errors: if (!empty($errors)) { return $errors; } // check if all necessary attributes of the sub folder are set // and if they aren't set, set them here: // special case for inbox and outbox folders: these folder types // get a custom ID instead of a generic one, so it has to be set here! if ($folder_type_class_name === 'InboxFolder') { $sub_folder->id = md5('INBOX_' . $user->id); } elseif ($folder_type_class_name === 'OutboxFolder') { $sub_folder->id = md5('OUTBOX_' . $user->id); } $sub_folder->user_id = $user->id; $sub_folder->range_id = $destination_folder->range_id; $sub_folder->parent_id = $destination_folder->getId(); $sub_folder->range_type = $destination_folder->range_type; $sub_folder->folder_type = get_class($sub_folder_type); $sub_folder->store(); return $sub_folder_type; //no errors } /** * This method handles copying folders, including * copying the subfolders and files recursively. * * @param FolderType $source_folder The folder that shall be copied. * @param FolderType $destination_folder The destination folder. * @param User $user The user who wishes to copy the folder. * * @return FolderType|string[] The copy of the source_folder FolderType object on success * or an array with error messages on failure. */ public static function copyFolder(FolderType $source_folder, FolderType $destination_folder, User $user) { $new_folder = null; if (!$destination_folder->isWritable($user->id)) { return [sprintf( _('Unzureichende Berechtigungen zum Kopieren von Ordner %s in Ordner %s!'), $source_folder->name, $destination_folder->name )]; } //we have to check, if the source folder is a folder from a course. //If so, then only users with status dozent or tutor (or root) in that course //may copy the folder! if (!$source_folder->isReadable($user->id)) { return [sprintf( _('Unzureichende Berechtigungen zum Kopieren von Veranstaltungsordner %s in Ordner %s!'), $source_folder->name, $destination_folder->name )]; } //The user has the permissions to copy the folder. //Now we must check if a folder is to be copied inside itself //or one of its subfolders. This is not allowed since it //leads to infinite recursion. $recursion_error = false; //First we check if the source folder is the destination folder: if ($destination_folder->getId() == $source_folder->getId()) { $recursion_error = true; } //After that we search the hierarchy of the destination folder //for the ID of the source folder: $parent = $destination_folder->getParent(); while ($parent) { if ($parent->getId() == $source_folder->getId()) { $recursion_error = true; break; } $parent = $parent->getParent(); } if ($recursion_error) { return [ _('Ein Ordner kann nicht in sich selbst oder einen seiner Unterordner kopiert werden!') ]; } //We must copy the source folder first. //The copy must be the same folder type like the destination folder. //Therefore we must first get the destination folder's FolderType class. $new_folder_class = get_class($source_folder); $destination_folder_type = in_array($new_folder_class, self::getAvailableFolderTypes($destination_folder->range_id, $user->id)) ? $new_folder_class : "StandardFolder"; $new_folder = new $destination_folder_type(); $new_folder->name = $source_folder->name; $new_folder->user_id = $user->id; $new_folder->description = $source_folder->description; // Copy settings if applicable. foreach ($source_folder->copySettings() as $field => $content) { $new_folder->$field = $content; } $new_folder = $destination_folder->createSubfolder($new_folder); //now we go through all subfolders and copy them: foreach ($source_folder->getSubfolders() as $sub_folder) { $result = self::copyFolder($sub_folder, $new_folder, $user); if (!$result instanceof FolderType) { return $result; } } //now go through all files and copy them, too: foreach ($source_folder->getFiles() as $file) { $result = self::copyFile($file, $new_folder, $user); if (!$result instanceof FileType) { return $result; } } return $new_folder; } /** * This method handles moving folders, including * subfolders and files. * * @param FolderType $source_folder The folder that shall be moved. * @param FolderType $destination_folder The destination folder. * @param User $user The user who wishes to move the folder. * * @return FolderType|string[] The moved folder's FolderType object on success * or an array with error messages on failure. */ public static function moveFolder(FolderType $source_folder, FolderType $destination_folder, User $user) { // Leave early, if folder was not actually moved if ($source_folder->parent_id === $destination_folder->id) { return $source_folder; } if (!$destination_folder->isWritable($user->id) || !$source_folder->isEditable($user->id)) { return [sprintf( _('Unzureichende Berechtigungen zum Verschieben von Ordner %s in Ordner %s!'), $source_folder->name, $destination_folder->name )]; } //Check if the destination folder is a subfolder of the source folder //or if destination and source folder are identical: $recursion_error = false; if ($destination_folder->getId() == $source_folder->getId()) { $recursion_error = true; } $parent = $destination_folder->getParent(); while ($parent) { if ($parent->getId() == $source_folder->getId()) { $recursion_error = true; break; } $parent = $parent->getParent(); } if ($recursion_error) { return [ _('Ein Ordner kann nicht in sich selbst oder einen seiner Unterordner verschoben werden!') ]; } $new_folder = null; if ($source_folder instanceof StandardFolder) { //Standard folders just have to be put below the //destination folder. $new_folder = $destination_folder->createSubfolder($source_folder); $array_walker = function ($folder) use (&$array_walker, $destination_folder, $user) { $type = get_class($folder); if (!$type::availableInRange($destination_folder->range_id, $user->id)) { $folder = new StandardFolder($folder); } $folder->range_id = $destination_folder->range_id; $folder->range_type = $destination_folder->range_type; $folder->store(); $sub_folders = $folder->getSubFolders(); array_walk($sub_folders, $array_walker); }; $sub_folders = $new_folder->getSubfolders(); array_walk($sub_folders, $array_walker); return $new_folder; } else { //It is a plugin folder which needs special treatment. $new_folder = $destination_folder->createSubfolder( $source_folder ); } if (!is_a($new_folder, "FolderType")) { return [_('Fehler beim Verschieben des Ordners.')]; } else { //now we go through all subfolders and move them: foreach ($source_folder->getSubfolders() as $sub_folder) { $result = self::moveFolder($sub_folder, $new_folder, $user); if (!$result instanceof FolderType) { //error return $result; } } //now go through all files and move them, too: foreach ($source_folder->getFiles() as $file_ref) { if (!($file_ref instanceof FileRef)) { $file_ref = FileRef::build((array) $file_ref, false); $file_ref->setFolderType('foldertype', $source_folder); } $result = self::moveFile($file_ref->getFileType(), $new_folder, $user); if (!$result instanceof FileRef) { //error return $result; } } $source_folder->delete(); return $new_folder; } } /** * This method helps with deleting a folder. * * @param FolderType $folder The folder that shall be deleted. * @param User $user The user who wishes to delete the folder. * * @return FolderType|string[] The deleted folder's FolderType object on success * or an array with error messages on failure. */ public static function deleteFolder(FolderType $folder, User $user) { if (!$folder->isEditable($user->id)) { return [sprintf( _('Unzureichende Berechtigungen zum Löschen von Ordner %s!'), $folder->name ) ]; } if ($folder->delete()) { //everything went fine! return $folder; } //error occured! return [sprintf( _('Fehler beim Löschvorgang von Ordner %s!'), $folder->name )]; } /** * returns the available folder types, * There are several types of folders in Stud.IP. This method returns * all available folder types. * * @return array with strings representing the class names of available folder types. * */ public static function getFolderTypes() { foreach (scandir(__DIR__) as $filename) { $path = pathinfo($filename); if ($path['extension'] === 'php') { class_exists($path['filename']); } } $result = []; foreach (get_declared_classes() as $declared_class) { if (!is_a($declared_class, 'FolderType', true)) { continue; } $reflected_ft = new ReflectionClass($declared_class); $ft_sorter = $reflected_ft->getStaticPropertyValue('sorter', PHP_INT_MAX); if ($ft_sorter == 0 && $declared_class != 'StandardFolder') { $ft_sorter = PHP_INT_MAX; } $result[$declared_class] = $ft_sorter; } asort($result, SORT_NUMERIC); return array_keys($result); } /** * returns the available folder types, for given context and user * * @param string|SimpleORMap $range_id_or_object * @param string $user_id * @return array with strings representing the class names of available folder types. * */ public static function getAvailableFolderTypes($range_id_or_object, $user_id) { $result = []; foreach (self::getFolderTypes() as $type) { if ($type::availableInRange($range_id_or_object, $user_id)) { $result[] = $type; } } return $result; } /** * Copies the content of a folder (files and subfolders) into a given * path in the operating system's file system. * * @param FolderType $folder The folder whose content shall be copied. * @param string $path The path in the operating system's file system * where the content shall be copied into. * @param string $user_id The user who wishes to copy the content. * @param string $min_perms If set, the selection of subfolders and files * is limited to those which are visible for users having * the minimum permissions. * @param bool $ignore_perms If set to true, files are copied without checking * the minimum permissions or the permissions of the user given by user_id. * @return bool True on success, false on error. */ public static function copyFolderContentIntoPath( FolderType $folder, $path = null, $user_id = 'nobody', $min_perms = 'nobody', $ignore_perms = false ) { if (!$path) { return false; } // loop through all subfolders, create a directory for each subfolder // and call this method recursively: foreach ($folder->getSubfolders() as $subfolder) { if ($subfolder->isReadable($user_id) || $ignore_perms) { //User has permissions to read the folder or permission checks //are ignored. $subfolder_path = $path . '/' . $subfolder->name; mkdir($subfolder_path, 0700); $success = self::copyFolderContentIntoPath( $subfolder, $subfolder_path, $user_id, $min_perms ); if (!$success) { return false; } } } // loop through all files and copy them to the folder path: foreach ($folder->getFiles() as $file_ref) { if ($folder->isFileDownloadable($file_ref, $user_id) || $ignore_perms) { //The user (given by user_id) has the required permissions //to download the file or the permission checks are //ignored. $file_path = $path . '/' . $file_ref->name; $success = copy($file_ref->file->getPath(), $file_path); if (!$success) { return false; } } } //Everything went fine. return true; } /** * Counts the number of files inside a folder and its subfolders. * The search result can be limited to the files belonging to one user * and/or to the files which are readable for one user. * * @param FolderType $folder The folder whose files shall be counted. * @param bool $count_subfolders True, if files subfolders shall also * be counted, too (default). False otherwise. * @param string $owner_id Optional user-ID to count only files of one * user specified by the ID. * @param string $user_id Optional user-ID to count only files the user * (specified by this user-ID) can read. * * @return int The amount of files inside the folder (and its subfolders). */ public static function countFilesInFolder( FolderType $folder, $count_subfolders = true, $owner_id = null, $user_id = null ) { $num_files = 0; if ($owner_id === null) { //If the owner_id is not set we can simply count the number of all files. $folder_files = $folder->getFiles(); if ($user_id === null) { //If the user_id is also not set we can simply count all files in this folder: $num_files = count($folder_files); } else { //$user_id is set: We must check for each file if it is readable for the user //specified by $user_id: if ($folder->isReadable($user_id)) { foreach ($folder_files as $folder_file) { if ($folder->isFileDownloadable($folder_file->id, $user_id)) { $num_files++; } } } } } else { //If the owner_id is set we must check who owns the file //and count only those files whose user_id matches the owner_id specified. foreach ($folder->getFiles() as $file) { if ($file->user_id === $owner_id) { if ($user_id) { //user-ID is set: only if the file is downloadable //it will be counted! if ($folder->isFileDownloadable($file->id, $user_id)) { $num_files++; } } else { //No user-ID set: we can count the file $num_files++; } } } } if ($count_subfolders) { //If files in subfolders shall be counted too, //we must call this method recursively. foreach ($folder->getSubFolders() as $subfolder) { $num_files += self::countFilesInFolder( $subfolder, $count_subfolders, $owner_id, $user_id ); } } return $num_files; } /** * Creates a list of files and subfolders of a folder. * * @param FolderType $top_folder The folder whose content shall be retrieved. * @param string $user_id The ID of the user who wishes to get all * files and subfolders of a folder. * @param bool $check_file_permissions Set to true, if file permissions * shall be checked. Defaults to false. * @return mixed[] A mixed array with FolderType and FileType objects. */ public static function getFolderFilesRecursive( FolderType $top_folder, $user_id, $check_file_permissions = false ) { $files = []; $folders = []; $array_walker = function ($top_folder) use ( &$array_walker, &$folders, &$files, $user_id, $check_file_permissions ) { if ($top_folder->isVisible($user_id)) { $folders[$top_folder->getId()] = $top_folder; if ($top_folder->isReadable($user_id)) { if ($check_file_permissions) { //We must check for each file if it is downloadable for the user //specified by user_id: $top_folder_file_refs = $top_folder->getFiles(); foreach ($top_folder_file_refs as $file_ref) { if ($top_folder->isFileDownloadable($file_ref->id, $user_id)) { $files[] = $file_ref; } } } else { $files = array_merge($files, $top_folder->getFiles()); } array_walk($top_folder->getSubFolders(), $array_walker); } } }; $top_folders = [$top_folder]; array_walk($top_folders, $array_walker); return compact('files', 'folders'); } /** * Creates a list of readable subfolders of a folder. * * @param FolderType $top_folder * @param string $user_id * @return FolderType[] assoc array ID => FolderType */ public static function getReadableFolders(FolderType $top_folder, $user_id) { $folders = []; $array_walker = function ($top_folder) use (&$array_walker, &$folders,$user_id) { if ($top_folder->isReadable($user_id)) { $folders[$top_folder->getId()] = $top_folder; array_walk($top_folder->getSubFolders(), $array_walker); } }; $top_folders = [$top_folder]; array_walk($top_folders, $array_walker); return $folders; } /** * Creates a list of unreadable subfolders of a folder. * @deprecated use getReadableFolders() instead * * @param FolderType $top_folder * @param string $user_id * @return FolderType[] assoc array ID => FolderType */ public static function getUnreadableFolders(FolderType $top_folder, $user_id) { $folders = []; $array_walker = function ($top_folder) use (&$array_walker, &$folders,$user_id) { if (!$top_folder->isReadable($user_id)) { $folders[$top_folder->getId()] = $top_folder; } array_walk($top_folder->getSubFolders(), $array_walker); }; $top_folders = [$top_folder]; array_walk($top_folders, $array_walker); return $folders; } /** * Returns a FolderType instance for a given folder-ID. * This method can also get FolderType instances which are defined * in a file system plugin. * * @param string $id The ID of a Folder object. * @param null $pluginclass The name of a Plugin's main class. * @return FolderType|null A FolderType object if it can be retrieved * using the Folder-ID (and by option the plugin class name) * or null in case no FolderType object can be created. */ public static function getTypedFolder($id, $pluginclass = null) { if ($pluginclass === null) { $folder = Folder::find($id); if ($folder) { return $folder->getTypedFolder(); } } else { $plugin = PluginManager::getInstance()->getPlugin($pluginclass); if ($plugin instanceof FilesystemPlugin) { $folder = $plugin->getFolder($id); if ($folder instanceof FolderType) { return $folder; } } } return null; } /** * Retrieves additional data for an URL by looking at the HTTP header. * * @param string $url The URL from which additional data shall be fetched. * @param int $level The amount of redirects that have already been walked through. * The $level parameter is only useful when this method calls itself recursively. * * @return array An array with additional data retrieved from the HTTP header. */ public static function fetchURLMetadata($url, $level = 0) { if ($level > 5) { return ['response' => 'HTTP/1.0 400 Bad Request', 'response_code' => 400]; } $url_parts = @parse_url($url); // filter out localhost and reserved or private IPs if (mb_stripos($url_parts['host'], 'localhost') !== false || mb_stripos($url_parts['host'], 'loopback') !== false || (filter_var($url_parts['host'], FILTER_VALIDATE_IP) !== false && (mb_strpos($url_parts['host'], '127') === 0 || filter_var( $url_parts['host'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) === false) ) ) { return ['response' => 'HTTP/1.0 400 Bad Request', 'response_code' => 400]; } // URL links to an ftp server if ($url_parts['scheme'] === 'ftp') { if (preg_match('/[^a-z0-9_.-]/i', $url_parts['host'])) { // exists umlauts ? $IDN = new Algo26\IdnaConvert\ToIdn(); $out = $IDN->convert($url_parts['host']); // false by error $url_parts['host'] = $out ?: $url_parts['host']; } $ftp = @ftp_connect($url_parts['host'],$url_parts['port'] ?: 21, 10); if (!$ftp) { return ['response' => 'HTTP/1.0 502 Bad Gateway', 'response_code' => 502]; } if (!$url_parts['user']) { $url_parts['user'] = 'anonymous'; } if (!$url_parts['pass']) { $mailclass = new StudipMail(); $url_parts['pass'] = $mailclass->getSenderEmail(); } if (!@ftp_login($ftp, $url_parts["user"], $url_parts["pass"])) { ftp_quit($ftp); return ['response' => 'HTTP/1.0 403 Forbidden', 'response_code' => 403]; } $parsed_link['Content-Length'] = ftp_size($ftp, $url_parts['path']); ftp_quit($ftp); if ($parsed_link['Content-Length'] != -1) { $parsed_link['HTTP/1.0 200 OK'] = 'HTTP/1.0 200 OK'; $parsed_link['response_code'] = 200; } else { return ['response' => 'HTTP/1.0 404 Not Found', 'response_code' => 404]; } $parsed_link['filename'] = basename($url_parts['path']); $parsed_link['Content-Type'] = get_mime_type($parsed_link['filename']); return $parsed_link; } // "Normal" url if (!empty($url_parts['path'])) { $documentpath = $url_parts['path']; } else { $documentpath = '/'; } if (!empty($url_parts['query'])) { $documentpath .= '?' . $url_parts['query']; } $host = $url_parts['host']; $port = $url_parts['port']; $scheme = mb_strtolower($url_parts['scheme']); if (!in_array($scheme, ['http', 'https']) || !$host) { return ['response' => 'HTTP/1.0 400 Bad Request', 'response_code' => 400]; } if ($scheme === 'https') { $ssl = true; if (empty($port)) { $port = 443; } } else { $ssl = false; } if (empty($port)) { $port = 80; } if (preg_match('/[^a-z0-9_.-]/i', $host)) { // exists umlauts ? $IDN = new Algo26\IdnaConvert\ToIdn(); $out = $IDN->convert($host); // false by error $host = $out ?: $host; } if (Config::get()->HTTP_PROXY) { $proxy_context = stream_context_create(['ssl' => ['verify_peer' => false, 'verify_peer_name' => false,]]); $socket = @stream_socket_client('tcp://' . Config::get()->HTTP_PROXY, $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $proxy_context); if ($ssl) { if ($socket) { fputs($socket, 'CONNECT ' . $host . ':' . $port . " HTTP/1.0\r\nHost: $host\r\nUser-Agent: Stud.IP\r\n\r\n"); while (true) { $s = rtrim(fgets($socket, 4096)); if (preg_match('/^$/', $s)) { break; } } stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT); } } } else { $errno = $errstr = ''; $socket = @stream_socket_client(($ssl ? 'ssl://' : 'tcp://') . $host . ':' . $port, $errno, $errstr, 5, STREAM_CLIENT_CONNECT); } if (!$socket) { Log::error(__METHOD__ . ' - stream_socket_client(' . ($ssl ? 'ssl://' : 'tcp://') . $host . ':' . $port .') failed: ' . $errstr); return ['response' => 'HTTP/1.0 502 Bad Gateway', 'response_code' => 502]; } $urlString = "GET {$documentpath} HTTP/1.0\r\nHost: {$host}\r\n"; if ($url_parts['user'] && $url_parts['pass']) { $pass = $url_parts['pass']; $user = $url_parts['user']; $urlString .= "Authorization: Basic " . base64_encode("{$user}:{$pass}") . "\r\n"; } $urlString .= sprintf("User-Agent: Stud.IP v%s File Crawler\r\n", $GLOBALS['SOFTWARE_VERSION']); $urlString .= "Connection: close\r\n\r\n"; fputs($socket, $urlString); stream_set_timeout($socket, 5); $response = ''; do { $response .= fgets($socket, 128); $info = stream_get_meta_data($socket); } while (!feof($socket) && !$info['timed_out'] && mb_strlen($response) < 1024); fclose($socket); $raw_header = explode("\n", trim($response)); if (!preg_match("~^HTTP/[^\s]*\s(.*?)\s~", $raw_header[0], $status)) { return ['response' => 'HTTP/1.0 502 Bad Gateway', 'response_code' => 502]; } $header = [ 'response_code' => (int)$status[1], 'response' => trim($raw_header[0]), ]; for ($i = 0; $i < count($raw_header); $i += 1) { $parts = null; if (!trim($raw_header[$i])) { break; } $matches = preg_match('/^\S+:/', $raw_header[$i], $parts); if ($matches){ $key = trim(mb_substr($parts[0],0,-1)); $value = trim(mb_substr($raw_header[$i], mb_strlen($parts[0]))); $header[$key] = $value; } else { $header[trim($raw_header[$i])] = trim($raw_header[$i]); } } // Anderer Dateiname? $disposition_header = $header['Content-Disposition'] ?: $header['content-disposition']; if ($disposition_header) { $header_parts = explode(';', $disposition_header); foreach ($header_parts as $part) { $part = trim($part); [$key, $value] = explode('=', $part, 2); if (mb_strtolower($key) === 'filename') { $header['filename'] = trim($value, '"'); } } } else { $header['filename'] = basename($url_parts['path']); } // Weg über einen Locationheader: $location_header = $header['Location'] ?: $header['location']; if (in_array($header['response_code'], [300, 301, 302, 303, 305, 307]) && $location_header) { if (mb_strpos($location_header, 'http') !== 0) { $location_header = $url_parts['scheme'] . '://' . $url_parts['host'] . '/' . $location_header; } $header = self::fetchURLMetadata($location_header, $level + 1); } return $header; } /** * Returns an INBOX folder for the given user. * * @param User $user The user whose inbox folder is requested. * @return FolderType|null Returns the inbox folder on success, null on failure. */ public static function getInboxFolder(User $user) { $top_folder = Folder::findTopFolder($user->id, 'user'); if (!$top_folder) { return null; } $top_folder = $top_folder->getTypedFolder(); if (!$top_folder) { return null; } $inbox_folder = Folder::find(md5('INBOX_' . $user->id)); if (!$inbox_folder) { //inbox folder doesn't exist: create it, if necessary. //We need an inbox folder if there is at least one received //message with at least one attachment. $inbox_folder = FileManager::createSubFolder( $top_folder, $user, 'InboxFolder', 'Inbox', InboxFolder::getTypeName() ); if ($inbox_folder instanceof InboxFolder) { return $inbox_folder; } return null; } return $inbox_folder->getTypedFolder(); } /** * Returns a FolderType object for the outbox folder of the given user. * * @param User $user The user whose outbox folder is requested. * @return FolderType|null Returns the inbox folder on success, null on failure. */ public static function getOutboxFolder(User $user) { $top_folder = Folder::findTopFolder($user->id, 'user'); if (!$top_folder) { return null; } $top_folder = $top_folder->getTypedFolder(); if (!$top_folder) { return null; } $outbox_folder = Folder::find(md5('OUTBOX_' . $user->id)); if (!$outbox_folder) { //inbox folder doesn't exist: create it, if necessary. //We need an inbox folder if there is at least one received //message with at least one attachment. $outbox_folder = FileManager::createSubFolder( $top_folder, $user, 'OutboxFolder', 'Outbox', OutboxFolder::getTypeName() ); if ($outbox_folder instanceof OutboxFolder) { return $outbox_folder; } return null; } return $outbox_folder->getTypedFolder(); } /** * returns config array for upload types and sizes for a given range id * * @param string $range_id id of Course Institute User * @param string|null $user_id Optional user id * @return array */ public static function getUploadTypeConfig($range_id, $user_id = null) { if (is_null($user_id)) { $user_id = $GLOBALS['user']->id; } $status = $GLOBALS['perm']->get_perm($user_id); $active_upload_type = 'default'; $range_object = get_object_by_range_id($range_id); if ($range_object instanceof Course) { $status = $GLOBALS['perm']->get_studip_perm($range_id, $user_id); $active_upload_type = $range_object->status; } elseif ($range_object instanceof Institute) { $status = $GLOBALS['perm']->get_studip_perm($range_id, $user_id); $active_upload_type = 'institute'; } elseif ($range_object instanceof User) { $active_upload_type = 'personalfiles'; } elseif (Message::exists($range_id)) { $active_upload_type = 'attachments'; } return self::loadUploadTypeConfig($active_upload_type, $status); } /** * Loads the upload type configuration for a specific type and status. * * @param string $type * @param string $status * * @return array{type: string, file_types: array, file_size: int} */ public static function loadUploadTypeConfig(string $type, string $status): array { if (!isset($GLOBALS['UPLOAD_TYPES'][$type])) { $type = 'default'; } $upload_type = $GLOBALS['UPLOAD_TYPES'][$type]; return [ 'type' => $upload_type['type'], 'file_types' => $upload_type['file_types'], 'file_size' => $upload_type['file_sizes'][$status], ]; } /** * Create URL to a folder * @param FolderType $folder the folder * @param bool $include_root * @return array array of FolderType */ public static function getFullPath(FolderType $folder, $include_root = true) { $path = [$folder->getId() => $folder]; $current = $folder; while ($current = $current->getParent()) { if ($current instanceof RootFolder && !$include_root) continue; $path[$current->getId()] = $current; } return array_reverse($path, true); } /** * Create URL to a folder * @param FolderType|Folder $folder the folder * @return string URL to the folder's range */ public static function getFolderURL($folder) { if (!$folder->range_type) { return null; } switch ($folder->range_type) { case 'course': $url = URLHelper::getURL( 'dispatch.php/course/files/index/'.$folder->id, ['cid' => $folder->range_id] ); break; case 'institute': $url = URLHelper::getURL( 'dispatch.php/institute/files/index/'.$folder->id, ['cid' => $folder->range_id] ); break; case 'message': $url = URLHelper::getURL('dispatch.php/messages/overview/'.$folder->range_id, ['cid' => null]); break; case 'user': $url = URLHelper::getURL('dispatch.php/files/index/'.$folder->id, ['cid' => null]); break; default: $url = URLHelper::getURL( 'dispatch.php/files/system/'.$folder->range_type.'/'.$folder->id, ['cid' => null] ); } return $url; } /** * Create link to a folder * @param FolderType|Folder $folder the folder * @return string link to the folder's range */ public static function getFolderLink($folder) { return htmlReady(self::getFolderURL($folder)); } /** * Returns true if the mime-type of that FileType object starts with image/ * @param FileType $file The file * @return bool True if it is an image else false */ public static function fileIsImage(FileType $file) { $mimetype = $file->getMimeType(); return mb_substr($mimetype, 0, 6) === "image/"; } /** * Returns true if the mime-type of that FileType object starts with audio/ * @param FileType $file The file * @return bool True if it is an audio file else false */ public static function fileIsAudio(FileType $file) { $mimetype = $file->getMimeType(); return mb_substr($mimetype, 0, 6) === "audio/"; } /** * Returns true if the mime-type of that FileType object starts with video/ * @param FileType $file The file * @return bool True if it is an video file else false */ public static function fileIsVideo(FileType $file) { $mimetype = $file->getMimeType(); return mb_substr($mimetype, 0, 6) === "video/"; } /** * Retrieves the range-IDs for all courses and institutes a user has * access to. * * @param string $user_id The ID of the user. * * @param bool $with_personal_file_area Whether to include the user-ID * of the user in the list of range-IDs (true) or not (false). * Defaults to false. * * @returns string[] An array with all retrieved range-IDs of the user. */ public static function getRangeIdsForFolders($user_id = null, $with_personal_file_area = true) { if (!$user_id) { return []; } //Get all courses first: $courses = Course::findByUser($user_id); //Get all institutes: $institutes = Institute::getMyInstitutes($user_id); //After that, collect the range-IDs: $range_ids = []; foreach ($courses as $course) { $range_ids[] = $course->id; } foreach ($institutes as $institute) { $range_ids[] = $institute->id; } if ($with_personal_file_area) { //Add the personal file area, too: $range_ids[] = $GLOBALS['user']->id; } return $range_ids; } public static function getFileIcon($filename, $role = Icon::ROLE_CLICKABLE) { $filename = mb_strtolower($filename); $extension = (mb_strrpos($filename, ".") === false) ? $filename : substr($filename, mb_strrpos($filename, ".") + 1); $extension = strtolower($extension); switch ($extension){ case 'rtf': case 'doc': case 'docx': case 'odt': $icon = 'file-text'; break; case 'xls': case 'xlsx': case 'ods': case 'csv': case 'ppt': case 'pptx': case 'odp': $icon = 'file-office'; break; case 'zip': case 'tgz': case 'gz': case 'bz2': $icon = 'file-archive'; break; case 'pdf': $icon = 'file-pdf'; break; case 'gif': case 'jpg': case 'jpe': case 'jpeg': case 'png': case 'bmp': $icon = 'file-pic'; break; default: $icon = 'file-generic'; break; } return Icon::create($icon, $role); } }