Skip to content

Commit

Permalink
Add support for file question (start work on multiple files + store e…
Browse files Browse the repository at this point in the history
…xtra options)

Signed-off-by: Konstantin Myakshin <[email protected]>
  • Loading branch information
Koc committed Apr 11, 2024
1 parent 3bffb99 commit ffbcf78
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 82 deletions.
4 changes: 2 additions & 2 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,8 @@
]
],
[
'name' => 'api#uploadFile',
'url' => '/api/{apiVersion}/uploadFile/{formId}',
'name' => 'api#uploadFiles',
'url' => '/api/{apiVersion}/uploadFiles/{formId}/{questionId}',
'verb' => 'POST',
'requirements' => [
'apiVersion' => 'v2.5'
Expand Down
13 changes: 5 additions & 8 deletions lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ class Constants {
'document',
'presentation',
'spreadsheet',
'drawing',
'pdf',
'image',
'video',
Expand All @@ -176,13 +175,6 @@ class Constants {
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.oasis.opendocument.spreadsheet',
],
'drawing' => [
'application/vnd.oasis.opendocument.graphics',
'image/png',
'image/jpeg',
'image/gif',
'image/bmp',
],
'pdf' => [
'application/pdf',
],
Expand All @@ -191,11 +183,16 @@ class Constants {
'image/jpeg',
'image/gif',
'image/bmp',
'image/tiff',
'image/webp',
//fixme: add Apple's HEIC/HEIF
],
'video' => [
'video/mp4',
'video/webm',
'video/ogg',
'video/quicktime',
'video/x-msvideo',
],
'audio' => [
'audio/mpeg',
Expand Down
98 changes: 58 additions & 40 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1065,74 +1065,92 @@ private function storeAnswersForQuestion($submissionId, array $question, array $
* @NoAdminRequired
* @PublicPage
*
* Uploads a temporary file to the server
* Uploads a temporary files to the server during form filling
*
* @return Response
*/
public function uploadFile(int $formId): Response {
// plan:
// +create migration to store temporary files in the table: id (hashed or uuid), form_id, original_file_name, file_id, created_at
// +create a new folder in form owner dir (formId), store file inside it with random file name
//
// +return back file_name and id
// during form submission store the file_id in the answer as separate column, and original file name in the text column

// during view check info about file id, and original file name, and display the file name with link
// during export do the same

$this->logger->debug('Uploading file for formId: {formId}', ['formId' => $formId]);

$uploadedFile = $this->request->getUploadedFile('file');
public function uploadFiles(int $formId, int $questionId): Response {
$this->logger->debug('Uploading files for formId: {formId}, questionId: {questionId}',
['formId' => $formId, 'questionId' => $questionId]);

$uploadedFiles = [];
foreach ($this->request->getUploadedFile('files') as $key => $files) {
foreach ($files as $i => $value) {
$uploadedFiles[$i][$key] = $value;
}
}

if (is_null($uploadedFile)) {
if (!count($uploadedFiles)) {
return new JSONResponse(
['data' => ['message' => $this->l10n->t('Invalid file provided')]],
['data' => ['message' => $this->l10n->t('No file provided')]],
Http::STATUS_BAD_REQUEST
);
}

$error = $uploadedFile['error'] ?? 0;
if ($error !== UPLOAD_ERR_OK) {
$this->logger->error('Failed to get the uploaded file. PHP file upload error code: ' . $error);
$response = [];
foreach ($uploadedFiles as $uploadedFile) {
$error = $uploadedFile['error'] ?? 0;
if ($error !== UPLOAD_ERR_OK) {
$this->logger->error('Failed to get the uploaded file. PHP file upload error code: ' . $error,
['file_name' => $uploadedFile['name']]);

return new JSONResponse(
['data' => ['message' => $this->l10n->t('Failed to upload the file')]],
Http::STATUS_BAD_REQUEST
);
}
return new JSONResponse(
['data' => ['message' => $this->l10n->t('Failed to upload the file "{file}".',
['file' => $uploadedFile['name']])]],
Http::STATUS_BAD_REQUEST
);
}

//fixme: add support of the multiple files?
//fixme: do we need add validation per file size and supported mime types?
if (is_uploaded_file($uploadedFile['tmp_name']) && !Filesystem::isFileBlacklisted($uploadedFile['name'])) {
$form = $this->formMapper->findById($formId);
$userFolder = $this->storage->getUserFolder($form->getOwnerId());
$path = 'forms/form-' . $formId;
$question = $this->questionMapper->findById($questionId);
$path = $this->formsService->getUploadedFilePath($form, $question);

if (!is_uploaded_file($uploadedFile['tmp_name'])) {
return new JSONResponse(
['data' => ['message' => $this->l10n->t('Invalid file provided')]],
Http::STATUS_BAD_REQUEST
);
}

$userFolder->getStorage()->verifyPath($path, $uploadedFile['name']);
// {user}/{formsfolder}/{formid}

//fixme: add validation of supported mime types
//fixme: add validation of max file size
//fixme: add validation of max file count

if ($userFolder->nodeExists($path)) {
$formFolder = $userFolder->get($path);
$folder = $userFolder->get($path);
} else {
$formFolder = $userFolder->newFolder($path);
$folder = $userFolder->newFolder($path);
}

$uploadedFileId = Uuid::uuid4()->toString();
$extension = pathinfo($uploadedFile['name'], PATHINFO_EXTENSION);
$file = $formFolder->newFile($uploadedFileId.'.'.$extension, file_get_contents($uploadedFile['tmp_name']));
$fileName = $folder->getNonExistingName($uploadedFile['name']);

Check failure on line 1129 in lib/Controller/ApiController.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefinedInterfaceMethod

lib/Controller/ApiController.php:1129:25: UndefinedInterfaceMethod: Method OCP\Files\Node::getNonExistingName does not exist (see https://psalm.dev/181)

try {
$file = $folder->newFile($fileName, file_get_contents($uploadedFile['tmp_name']));

Check failure on line 1132 in lib/Controller/ApiController.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefinedInterfaceMethod

lib/Controller/ApiController.php:1132:22: UndefinedInterfaceMethod: Method OCP\Files\Node::newFile does not exist (see https://psalm.dev/181)
} catch (\Throwable $e) {
var_dump($e->getTraceAsString());

Check failure on line 1134 in lib/Controller/ApiController.php

View workflow job for this annotation

GitHub Actions / Static analysis

ForbiddenCode

lib/Controller/ApiController.php:1134:5: ForbiddenCode: Unsafe var_dump (see https://psalm.dev/002)
}
exit;

$uploadedFileId = Uuid::uuid4()->toString();
$uploadedFileEntity = new UploadedFile();
$uploadedFileEntity->setId($uploadedFileId);
$uploadedFileEntity->setFormId($formId);
$uploadedFileEntity->setOriginalFileName($uploadedFile['name']);
$uploadedFileEntity->setOriginalFileName($fileName);
$uploadedFileEntity->setFileId($file->getId());
$uploadedFileEntity->setCreated(time());
$this->uploadedFileMapper->insert($uploadedFileEntity);

return new DataResponse(['uploadedFileId' => $uploadedFileId, 'uploadedFileName' => $uploadedFile['name']]);
$response[] = [
'uploadedFileId' => $uploadedFileId,
'fileName' => $fileName,
];
}

return new JSONResponse(
['data' => ['message' => $this->l10n->t('Invalid file provided')]],
Http::STATUS_BAD_REQUEST
);
return new DataResponse($response);
}

/**
Expand Down
25 changes: 14 additions & 11 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use OCA\Forms\Db\Form;
use OCA\Forms\Db\FormMapper;
use OCA\Forms\Db\OptionMapper;
use OCA\Forms\Db\Question;
use OCA\Forms\Db\QuestionMapper;
use OCA\Forms\Db\Share;
use OCA\Forms\Db\ShareMapper;
Expand Down Expand Up @@ -579,15 +580,6 @@ public function setLastUpdatedTimestamp(int $formId): void {
* @return bool if the settings are valid
*/
public function areExtraSettingsValid(array $extraSettings, string $questionType) {
// Document
// Presentation
// Spreadsheet
// Drawing
// Pdf
// Image
// Video
// Audio

if (count($extraSettings) === 0) {
return true;
}
Expand Down Expand Up @@ -692,8 +684,19 @@ public function getFileName(Form $form, string $fileFormat): string {
// TRANSLATORS Appendix for CSV-Export: 'Form Title (responses).csv'
$fileName = $form->getTitle() . ' (' . $this->l10n->t('responses') . ').'.$fileFormat;

// Sanitize file name, replace all invalid characters
return str_replace(mb_str_split(\OCP\Constants::FILENAME_INVALID_CHARS), '-', $fileName);
return self::normalizeFileName($fileName);
}

public function getUploadedFilePath(Form $form, Question $question): string {

return implode('/', [
'forms',
self::normalizeFileName($form->getId().'. '.$form->getTitle()),
self::normalizeFileName($question->getId().'. '.$question->getName())
]);
}

private static function normalizeFileName(string $fileName): string {
return str_replace(mb_str_split(\OCP\Constants::FILENAME_INVALID_CHARS), '-', $fileName);
}
}
Loading

0 comments on commit ffbcf78

Please sign in to comment.