Select Git revision
PluginRepository.php
Forked from
Stud.IP / Stud.IP
Source project has a limited visibility.
-
Jan-Hendrik Willms authored
Merge request studip/studip!3697
Jan-Hendrik Willms authoredMerge request studip/studip!3697
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
FileManager.php 72.19 KiB
<?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';
}
list($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'])) {
$error = [];
foreach ($uploaded_files['name'] as $key => $filename) {
$proceed = true;
if (Config::get()->VIRUSSCAN_ON_UPLOAD) {
$scanned = Virusscanner::scan($uploaded_files['tmp_name'][$key]);
if (count($scanned) > 0) {
$proceed = false;
if ($scanned['found']) {
$error[] = sprintf(
_('Die Datei "%1$s" wurde vom Virenscanner überprüft. Dabei wurde das Virus ' .
'"%2$s" gefunden. Die Datei wird nicht hochgeladen.'),
$filename, $scanned['found']
);
} else if ($scanned['error']) {
$error[] = sprintf(
_('Die Datei "%1$s" wurde vom Virenscanner überprüft. Dabei ist ein Problem ' .
'aufgetreten: %2$s'),
$filename, $scanned['error']
);
}
}
}
if ($proceed) {
$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'] ?? null;
$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 (isset($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'] ?? null;
if ($disposition_header) {
$header_parts = explode(';', $disposition_header);
foreach ($header_parts as $part) {
$part = trim($part);
$chunks = explode('=', $part, 2);
if (count($chunks) === 2 && mb_strtolower($chunks[0]) === 'filename') {
$header['filename'] = trim($chunks[1], '"');
}
}
} else {
$header['filename'] = basename($url_parts['path'] ?? '');
}
// Weg über einen Locationheader:
$location_header = $header['Location'] ?? $header['location'] ?? null;
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] ?? 0,
];
}
/**
* 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['Institut_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);
}
}