From 5f82d725b048c2310ff294b379b9993119b11629 Mon Sep 17 00:00:00 2001
From: Moritz Strohm <strohm@data-quest.de>
Date: Mon, 7 Aug 2023 09:41:34 +0000
Subject: [PATCH] file/unzipquestion: allow unzipping of ZIP files in file
 system plugin file areas, re #2988

Merge request studip/studip!2007
---
 app/controllers/file.php                    | 208 +++++++++++++-------
 app/views/file/edit_license.php             |   9 +-
 app/views/file/unzipquestion.php            |   4 +-
 lib/filesystem/FileArchiveManager.class.php |  66 ++++---
 lib/filesystem/StandardFile.php             |   2 +-
 5 files changed, 184 insertions(+), 105 deletions(-)

diff --git a/app/controllers/file.php b/app/controllers/file.php
index f30404f3f62..15bca3dbe08 100644
--- a/app/controllers/file.php
+++ b/app/controllers/file.php
@@ -74,6 +74,7 @@ class FileController extends AuthenticatedController
         }
 
         URLHelper::addLinkParam('from_plugin', Request::get('from_plugin'));
+        URLHelper::addLinkParam('to_plugin', Request::get('to_plugin'));
 
         if (!$folder || !$folder->isWritable($GLOBALS['user']->id)) {
             throw new AccessDeniedException();
@@ -171,32 +172,58 @@ class FileController extends AuthenticatedController
 
     public function unzipquestion_action()
     {
-        $this->file_refs      = FileRef::findMany(Request::getArray('file_refs'));
-        $this->file_ref       = $this->file_refs[0];
-        $this->current_folder = $this->file_ref->folder->getTypedFolder();
+        $this->to_plugin      = Request::get('to_plugin');
+        URLHelper::addLinkParam('to_plugin', $this->to_plugin);
+
+        $this->files      = [];
+        if ($this->to_plugin) {
+            //Plugin file area.
+            $plugin = PluginManager::getInstance()->getPlugin($this->to_plugin);
+            if (!$plugin) {
+                throw new Trails_Exception(404, _('Plugin existiert nicht.'));
+            }
+            if (!($plugin instanceof FilesystemPlugin)) {
+                throw new Trails_Exception(400, _('Das Plugin ist kein Dateibereich-Plugin.'));
+            }
+            $file_ids = Request::getArray('file_refs');
+            foreach ($file_ids as $file_id) {
+                $file = $plugin->getPreparedFile($file_id);
+                if ($file instanceof FileType) {
+                    $this->files[] = $file;
+                }
+            }
+        } else {
+            //Stud.IP file area.
+            $file_refs = FileRef::findMany(Request::getArray('file_refs'));
+            foreach ($file_refs as $file_ref) {
+                $this->files[] = $file_ref->getFileType();
+            }
+        }
+        $this->first_file     = $this->files[0];
+        $this->current_folder = $this->first_file->getFolderType();
 
         if (Request::isPost()) {
             $changes = [];
 
             if (Request::submitted('unzip')) {
                 //unzip!
-                $this->file_refs = FileArchiveManager::extractArchiveFileToFolder(
-                    $this->file_ref,
+                $this->files = FileArchiveManager::extractArchiveFileToFolder(
+                    $this->first_file,
                     $this->current_folder,
                     $GLOBALS['user']->id
                 );
 
                 $ref_ids = [];
 
-                foreach ($this->file_refs as $file_ref) {
-                    $ref_ids[] = $file_ref->id;
+                foreach ($this->files as $file) {
+                    $ref_ids[] = $file->getId();
                 }
 
                 //Delete the original zip file:
-                $changes['removed_files'] = [$this->file_ref->id];
-                $this->file_ref->delete();
+                $changes['removed_files'] = [$this->first_file->getId()];
+                $this->first_file->delete();
             } else {
-                $ref_ids = [$this->file_ref->getId()];
+                $ref_ids = [$this->first_file->getId()];
             }
 
             $this->flash->set('file_refs', $ref_ids);
@@ -213,23 +240,25 @@ class FileController extends AuthenticatedController
                     'add_folders' => [],
                 ];
                 $added_folders = [];
-                foreach ($this->file_refs as $fileref) {
-                    if ($fileref->folder->id === $this->current_folder->id) {
-                        $payload['added_files'][] = FilesystemVueDataManager::getFileVueData($fileref->getFileType(), $this->current_folder);
+                foreach ($this->files as $file) {
+                    $folder = $file->getFolderType();
+                    if ($folder && $folder->getId() === $this->current_folder->getId()) {
+                        $payload['added_files'][] = FilesystemVueDataManager::getFileVueData($file, $this->current_folder);
                     } elseif (
-                        $fileref->folder->parentfolder->id === $this->current_folder->id
-                        && !in_array($fileref->folder->id, $added_folders)
+                        $folder->getParent() &&
+                        $folder->getParent()->getId() === $this->current_folder->getId()
+                        && !in_array($folder->getId(), $added_folders)
                     ) {
                         if ($topFolder === null) {
                             $topFolder = $this->current_folder;
-                            while ($topFolder->parentfolder !== null) {
-                                $topFolder = $topFolder->parentFolder;
+                            while ($topFolder->getParent() !== null) {
+                                $topFolder = $topFolder->getParent();
                             }
                         }
 
-                        $payload['added_folders'][] = FilesystemVueDataManager::getFolderVueData($fileref->getFolderType(), $this->current_folder);
+                        $payload['added_folders'][] = FilesystemVueDataManager::getFolderVueData($folder, $this->current_folder);
 
-                        $added_folders[] = $fileref->folder->id;
+                        $added_folders[] = $folder->getId();
                     }
                 }
 
@@ -378,7 +407,7 @@ class FileController extends AuthenticatedController
             $force_save = Request::submitted('force_save');
             $this->name = trim(Request::get('name'));
             $this->description = Request::get('description');
-            $this->store_accessibility_flag($file_ref_id);
+            $this->store_accessibility_flag($this->file);
 
             $this->content_terms_of_use_id = Request::get('content_terms_of_use_id');
 
@@ -1570,17 +1599,48 @@ class FileController extends AuthenticatedController
         }
     }
 
-    public function edit_license_action($folder_id = null)
+
+    protected function loadFiles($param = 'files', $plugin = null, $with_blob = false)
     {
-        $this->re_location = Request::get('re_location');
-        $file_ref_ids = Request::getArray('file_refs');
-        if (!$file_ref_ids) {
+        $result = [];
+        $file_ids = Request::getArray($param);
+        if (!$file_ids) {
             //In case the file ref IDs are not set in the request
             //they may still be set in the flash object of the controller:
-            $file_ref_ids = $this->flash->get('file_refs');
+            $file_ids = $this->flash->get($param);
+        }
+        if ($plugin instanceof FilesystemPlugin) {
+            foreach ($file_ids as $file_id) {
+                $file = $plugin->getPreparedFile($file_id);
+                if ($file instanceof FileType) {
+                    $result[] = $file;
+                }
+            }
+        } else {
+            //Stud.IP core file system:
+            $file_refs = FileRef::findMany($file_ids);
+            foreach ($file_refs as $file_ref) {
+                $file = $file_ref->getFileType();
+                if ($file instanceof FileType) {
+                    $result[] = $file;
+                }
+            }
         }
-        $this->file_refs = FileRef::findMany($file_ref_ids);
-        $this->folder = $this->file_refs[0]->folder;
+        return $result;
+    }
+
+    public function edit_license_action($folder_id = null)
+    {
+        $this->re_location = Request::get('re_location');
+        $this->files = [];
+        $this->file = null;
+        $this->file_ref = null;
+        $this->plugin = null;
+        if (Request::submitted('to_plugin')) {
+            $this->plugin = PluginManager::getInstance()->getPlugin(Request::get('to_plugin'));
+        }
+        $this->files = $this->loadFiles('file_refs', $this->plugin);
+        $this->folder = $this->files[0]->getFolderType();
         $this->show_description_field = Config::get()->ENABLE_DESCRIPTION_ENTRY_ON_UPLOAD;
         if ($folder_id == 'bulk') {
             $this->show_description_field = false;
@@ -1589,51 +1649,54 @@ class FileController extends AuthenticatedController
         if (Request::isPost()) {
             CSRFProtection::verifyUnsafeRequest();
 
-            if (count($file_ref_ids) === 1) {
+            if (count($this->files) === 1) {
                 // store flag if file is an accessible file
-                $this->store_accessibility_flag($file_ref_ids[0]);
+                $this->store_accessibility_flag($this->files[0]);
             }
 
             if (($folder_id == 'bulk') && !Request::submitted('accept')) {
-                $file_ref_ids = Request::getArray('ids');
-                $this->file_refs = FileRef::findMany($file_ref_ids);
+                $this->files = $this->loadFiles('ids', $this->plugin);
             } else {
                 $description = Request::get('description');
                 $success_files = [];
                 $error_files = [];
-                foreach ($this->file_refs as $file_ref) {
+                foreach ($this->files as $file) {
                     //Check if the user may change the license of the file:
-                    $folder = $file_ref['folder'];
+                    $folder = $file->getFolderType();
                     if (!$folder) {
                         //We have no way of determining whether the user may change
                         //the license.
-                        $error_files[] = sprintf(_('Die Datei "%s" ist ungültig!'), $file_ref['name']);
+                        $error_files[] = sprintf(_('Die Datei "%s" ist ungültig!'), $file['name']);
                         continue;
                     }
-
-                    $file_ref['content_terms_of_use_id'] = Request::option('content_terms_of_use_id');
-                    if ($this->show_description_field && $description) {
-                        $file_ref['description'] = $description;
-                    }
-                    if ($file_ref->isDirty()) {
+                    if ($file instanceof StandardFile) {
+                        //Due to missing methods in the FileType interface,
+                        //terms of use and the description can only be set
+                        //for StandardFile instances.
+                        $file_ref = $file->getFileRef();
+                        $file_ref->content_terms_of_use_id = Request::option('content_terms_of_use_id');
+                        if ($this->show_description_field && $description) {
+                            $file_ref->description = $description;
+                        }
                         if ($file_ref->store()) {
-                            $success_files[] = $file_ref['name'];
+                            $success_files[] = $file->getFilename();
                         } else {
-                            $error_files[] = sprintf(_('Fehler beim Speichern der Datei "%s"!'), $file_ref['name']);
+                            $error_files[] = sprintf(_('Fehler beim Speichern der Datei "%s"!'), $file['name']);
                         }
-                    } else {
-                        $success_files[] = $file_ref['name'];
                     }
 
-                    $this->file = $file_ref->getFileType();
-                    $this->current_folder = $file_ref->folder->getTypedFolder();
+                    $this->file = $file;
+                    if ($file instanceof StandardFile) {
+                        $this->file_ref = $file->getFileRef();
+                    }
+                    $this->current_folder = $folder;
                     $this->marked_element_ids = [];
                     $payload['html'][] = FilesystemVueDataManager::getFileVueData($this->file, $this->current_folder);
                 }
                 if (Request::isXhr() && !$this->re_location) {
                     $payload = ['html' => []];
-                    foreach ($this->file_refs as $file_ref) {
-                        $folder = $file_ref->folder->getTypedFolder();
+                    foreach ($this->files as $file) {
+                        $folder = $file->getFolderType();
 
                         // Skip files not in current folder (during archive extract)
                         if ($folder_id && $folder_id !== $folder->getId()) {
@@ -1641,31 +1704,34 @@ class FileController extends AuthenticatedController
                         }
 
                         $payload['html'][] = FilesystemVueDataManager::getFileVueData(
-                            $file_ref->getFileType(),
-                            $file_ref->folder->getTypedFolder()
+                            $file,
+                            $file->getFolderType()
                         );
                     }
 
                     $plugins = PluginManager::getInstance()->getPlugins('FileUploadHook');
                     $redirect = null;
-                    foreach ($plugins as $plugin) {
-                        $url = $plugin->getAdditionalUploadWizardPage($file_ref);
+                    foreach ($plugins as $upload_hook_plugin) {
+                        $url = $upload_hook_plugin->getAdditionalUploadWizardPage($file_ref);
                         if ($url) {
                             $redirect = $url;
                             break;
                         }
                     }
 
-                    if (count($file_ref_ids) === 1) {
+                    if (count($this->files) === 1) {
                         if (Config::get()->OERCAMPUS_ENABLED
                             && Config::get()->OER_ENABLE_POST_UPLOAD
                             && $GLOBALS['perm']->have_perm('tutor')
                         ) {
-                            if ($file_ref['content_terms_of_use_id'] === 'SELFMADE_NONPUB'
-                                || $file_ref['content_terms_of_use_id'] === 'FREE_LICENSE'
-                            ) {
-                                $this->redirect('file/oer_post_upload/' . $file_ref['id']);
-                                return;
+                            if ($this->file instanceof StandardFile) {
+                                $file_ref = $this->file->getFileRef();
+                                if ($file_ref['content_terms_of_use_id'] === 'SELFMADE_NONPUB'
+                                    || $file_ref['content_terms_of_use_id'] === 'FREE_LICENSE'
+                                ) {
+                                    $this->redirect('file/oer_post_upload/' . $file_ref['id']);
+                                    return;
+                                }
                             }
                         }
                     }
@@ -1677,15 +1743,15 @@ class FileController extends AuthenticatedController
 
                     $payload['url'] = $this->generateFilesUrl(
                         $this->folder,
-                        $file_ref
+                        $this->file
                     );
 
-                    if ($folder_id == 'bulk' && $this->file_refs) {
+                    if ($folder_id == 'bulk' && $this->files) {
                         if ($success_files) {
                             if (count($success_files) == 1) {
                                 PageLayout::postSuccess(sprintf(
                                     _('Die Lizenz der Datei "%s" wurde geändert.'),
-                                    htmlReady($this->file_refs[0]->name)
+                                    htmlReady($this->files[0]->getName())
                                 ));
                             } else {
                                 sort($success_files);
@@ -1699,7 +1765,7 @@ class FileController extends AuthenticatedController
                             if (count($error_files) == 1) {
                                 PageLayout::postError(sprintf(
                                     _('Die Lizenz der Datei "%s" konnte nicht geändert werden!'),
-                                    htmlReady($this->file_refs[0]->name)
+                                    htmlReady($this->files[0]->getName())
                                 ));
                             } else {
                                 PageLayout::postError(
@@ -1731,9 +1797,9 @@ class FileController extends AuthenticatedController
             ngettext(
                 'Zusatzangaben und Lizenz wählen',
                 'Zusatzangaben und Lizenz auswählen: %s Dateien',
-                count($this->file_refs)
+                count($this->files)
             ),
-            count($this->file_refs)
+            count($this->files)
         ));
 
         $this->licenses = ContentTermsOfUse::findBySQL("1 ORDER BY position ASC, id ASC");
@@ -2224,7 +2290,7 @@ class FileController extends AuthenticatedController
     {
         require_once 'app/controllers/files.php';
 
-        return \FilesController::getRangeLink($folder) . '#fileref_' . $fileRef->id;
+        return \FilesController::getRangeLink($folder) . '#fileref_' . $fileRef->getId();
     }
 
     private function getFolderSize($folder): array
@@ -2238,10 +2304,14 @@ class FileController extends AuthenticatedController
         return [$folder_size, $folder_file_amount];
     }
 
-    private function store_accessibility_flag($file_ref_id)
+    private function store_accessibility_flag(FileType $file)
     {
-        $file_ref = FileRef::find($file_ref_id);
-        $file_ref->file['is_accessible'] = Request::get('is_accessible');
-        $file_ref->file->store();
+        if ($file instanceof StandardFile) {
+            $file_ref = $file->getFileRef();
+            if ($file_ref) {
+                $file_ref->file['is_accessible'] = Request::get('is_accessible');
+                $file_ref->file->store();
+            }
+        }
     }
 }
diff --git a/app/views/file/edit_license.php b/app/views/file/edit_license.php
index ddd031d153c..a9cf94e0f33 100644
--- a/app/views/file/edit_license.php
+++ b/app/views/file/edit_license.php
@@ -1,8 +1,9 @@
 <form action="<?= $controller->link_for('file/edit_license', $origin_folder_id ?? null) ?>"
       method="post" class="default" data-dialog="reload-on-close">
 <input type="hidden" name="re_location" value="<?= htmlReady($re_location) ?>">
-<? foreach ($file_refs as $file_ref) : ?>
-    <input type="hidden" name="file_refs[]" value="<?= htmlReady($file_ref->id) ?>">
+    <input type="hidden" name="to_plugin" value="<?= $plugin ? $plugin->getPluginName() : '' ?>">
+<? foreach ($files as $file) : ?>
+    <input type="hidden" name="file_refs[]" value="<?= htmlReady($file->getId()) ?>">
 <? endforeach ?>
 
 
@@ -17,7 +18,7 @@
 
     <? endif ?>
 
-    <? if (count($file_refs) === 1) : ?>
+    <? if (count($files) === 1) : ?>
         <fieldset>
             <legend><?= _('Barrierefreiheit') ?></legend>
             <label>
@@ -30,7 +31,7 @@
 
         <?= $this->render_partial('file/_terms_of_use_select.php', [
             'content_terms_of_use_entries' => $licenses,
-            'selected_terms_of_use_id'     => $file_ref->content_terms_of_use_id
+            'selected_terms_of_use_id'     => $file_ref->content_terms_of_use_id ?? ''
         ]) ?>
 
     <footer data-dialog-button>
diff --git a/app/views/file/unzipquestion.php b/app/views/file/unzipquestion.php
index a7e82b1b2ee..361ae63e400 100644
--- a/app/views/file/unzipquestion.php
+++ b/app/views/file/unzipquestion.php
@@ -1,7 +1,7 @@
 <form action="<?= $controller->url_for('file/unzipquestion') ?>" method="post" data-dialog>
 
-<? foreach ($file_refs as $file_ref): ?>
-    <input type="hidden" name="file_refs[]" value="<?= htmlReady($file_ref->id) ?>">
+<? foreach ($files as $file): ?>
+    <input type="hidden" name="file_refs[]" value="<?= htmlReady($file->getId()) ?>">
 <? endforeach ?>
 
     <?= Icon::create('unit-test', Icon::ROLE_INACTIVE)->asImg(120, ['style' => 'display: block; margin-left: auto; margin-right: auto;']) ?>
diff --git a/lib/filesystem/FileArchiveManager.class.php b/lib/filesystem/FileArchiveManager.class.php
index e7a0d40581c..498bb5814d0 100644
--- a/lib/filesystem/FileArchiveManager.class.php
+++ b/lib/filesystem/FileArchiveManager.class.php
@@ -828,7 +828,7 @@ class FileArchiveManager
      * @param FolderType $target_folder The folder where the file shall be stored.
      * @param User $user The user who wishes to extract the file from the archive.
      *
-     * @return FileRef|null FileRef instance on success, null otherwise.
+     * @return FileType|null FileType instance on success, null otherwise.
      */
     public static function extractFileFromArchive(
         Studip\ZipArchive $archive,
@@ -844,59 +844,62 @@ class FileArchiveManager
             return null;
         }
 
-        $file = new File();
-        $file->user_id   = $user->id;
-        $file->name      = $archive->convertArchiveFilename(basename($archive_path));
-        $file->mime_type = get_mime_type($file->name);
-        $file->size      = $file_info['size'];
-        $file->store();
+        $studip_file = new File();
+        $studip_file->user_id   = $user->id;
+        $studip_file->name      = $archive->convertArchiveFilename(basename($archive_path));
+        $studip_file->mime_type = get_mime_type($studip_file->name);
+        $studip_file->size      = $file_info['size'];
+        $studip_file->id = $studip_file->getNewId();
+        //$file->store();
 
         // Ok, we have a file object in the database. Now we must connect
         // it with the data file by extracting the data file into
         // the place, where the file's content has to be placed.
-        $file_path = pathinfo($file->getPath(), PATHINFO_DIRNAME);
+        $file_dir = pathinfo($studip_file->getPath(), PATHINFO_DIRNAME);
+        $file_path = $file_dir . '/' . $studip_file->id;
 
         // Create the directory for the file, if necessary:
-        if (!is_dir($file_path)) {
-            mkdir($file_path);
+        if (!is_dir($file_dir)) {
+            mkdir($file_dir);
         }
 
         // Ok, now we read all data from $file_resource and put it into
         // the file's path:
-        if (file_put_contents($file->getPath(), $file_resource) === false) {
+        if (file_put_contents($file_path, $file_resource) === false) {
             //Something went wrong: abort and clean up!
-            $file->delete();
+            //$file->delete();
             return null;
         }
 
-        // Ok, we now must create a FileRef:
+        // Ok, we now must create a File:
         $file_ref = new FileRef();
-        $file_ref->file_id   = $file->id;
+        $file_ref->file_id   = $studip_file->id;
         $file_ref->folder_id = $target_folder->getId();
         $file_ref->user_id   = $user->id;
-        $file_ref->name     = $file->name;
-        if ($file_ref->store()) {
-            return $file_ref;
+        $file_ref->name      = $studip_file->name;
+        $file_ref->file      = $studip_file;
+        $file = new StandardFile($file_ref);
+        if ($saved_file = $target_folder->addFile($file, $user->id)) {
+            return $saved_file;
         }
 
-        //Something went wrong: abort and clean up!
-        $file_ref->delete();
+        //Something went wrong:
         return null;
     }
 
     /**
      * Extracts an archive into a folder inside the Stud.IP file area.
      *
-     * @param FileRef $archive_file_ref The archive file which shall be extracted.
+     * @param FileType $archive_file The archive file which shall be extracted.
      * @param FolderType $folder The folder where the archive shall be extracted.
      * @param string $user_id The ID of the user who wants to extract the archive.
      *
-     * @return FileRef[] Array with extracted files, represented as FileRef objects.
+     * @return FileType[] Array with extracted files, represented as FileRef objects.
      */
     public static function extractArchiveFileToFolder(
-        FileRef $archive_file_ref,
+        FileType   $archive_file,
         FolderType $folder,
-        $user_id = null
+                   $user_id = null
     )
     {
         $user = $user_id ? User::find($user_id) : User::findCurrent();
@@ -913,11 +916,16 @@ class FileArchiveManager
         $keep_hierarchy = $folder->isSubfolderAllowed($user->id);
 
         $archive = new Studip\ZipArchive();
-        $archive->open($archive_file_ref->file->getPath());
+        $standard_archive_file = $archive_file->convertToStandardFile();
+        if (!($standard_archive_file instanceof StandardFile)) {
+            //Error converting the archive file.
+            return [];
+        }
+        $archive->open($standard_archive_file->getPath());
 
         // loop over all entries in the zip archive and put each entry
         // in the current folder or one of its subfolders:
-        $file_refs = [];
+        $files = [];
 
         for ($i = 0; $i < $archive->numFiles; $i++) {
             $entry_info = $archive->statIndex($i);
@@ -972,19 +980,19 @@ class FileArchiveManager
                 //we extract one file:
                 //$entry_info['name'] is necessary because we need the full path
                 //to the entry inside the archive.
-                $file_ref = self::extractFileFromArchive(
+                $file = self::extractFileFromArchive(
                     $archive,
                     $entry_info['name'],
                     $extracted_entry_destination_folder,
                     $user
                 );
 
-                if ($file_ref instanceof FileRef) {
-                    $file_refs[] = $file_ref;
+                if ($file instanceof FileType) {
+                    $files[] = $file;
                 }
             }
         }
 
-        return $file_refs;
+        return $files;
     }
 }
diff --git a/lib/filesystem/StandardFile.php b/lib/filesystem/StandardFile.php
index 4547d37c0f7..5723ef48f65 100644
--- a/lib/filesystem/StandardFile.php
+++ b/lib/filesystem/StandardFile.php
@@ -39,7 +39,7 @@ class StandardFile implements FileType, ArrayAccess, StandardFileInterface
         $mime_type  = $data['type'] ?: get_mime_type($data['name']);
         $filesize   = $data['size'] ?: filesize($data['tmp_name']);
         $file_path  = $data['tmp_name'];
-        $error_code = $data['error'];
+        $error_code = $data['error'] ?? '';
 
         if ($error_code) {
             //error handling
-- 
GitLab