Skip to content

Commit

Permalink
Add support for file question
Browse files Browse the repository at this point in the history
Signed-off-by: Konstantin Myakshin <[email protected]>
  • Loading branch information
Koc committed May 1, 2024
1 parent 28901cc commit d694698
Show file tree
Hide file tree
Showing 26 changed files with 1,300 additions and 109 deletions.
6 changes: 5 additions & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- **🙋 Get involved!** We have lots of stuff planned like more question types, collaboration on forms, [and much more](https://github.com/nextcloud/forms/milestones)!
]]></description>

<version>4.2.3</version>
<version>4.2.4</version>
<licence>agpl</licence>

<author>Affan Hussain</author>
Expand Down Expand Up @@ -54,6 +54,10 @@
<nextcloud min-version="28" max-version="29" />
</dependencies>

<background-jobs>
<job>OCA\Forms\BackgroundJob\CleanupUploadedFilesJob</job>
</background-jobs>

<settings>
<admin>OCA\Forms\Settings\Settings</admin>
<admin-section>OCA\Forms\Settings\SettingsSection</admin-section>
Expand Down
8 changes: 8 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,14 @@
'apiVersion' => 'v2(\.[1-4])?'
]
],
[
'name' => 'api#uploadFiles',
'url' => '/api/{apiVersion}/uploadFiles/{formId}/{questionId}',
'verb' => 'POST',
'requirements' => [
'apiVersion' => 'v2.5'
]
],
[
'name' => 'api#insertSubmission',
'url' => '/api/{apiVersion}/submission/insert',
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"phpunit/phpunit": "^9"
},
"require": {
"phpoffice/phpspreadsheet": "^2.0"
"phpoffice/phpspreadsheet": "^2.0",
"symfony/polyfill-uuid": "^1.29"
},
"extra": {
"bamarni-bin": {
Expand Down
81 changes: 80 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ This file contains the API-Documentation. For more information on the returned D
- Completely new way of handling access & shares.

### Other API changes
- In API version 2.5 the following endpoints were introduced:
- `POST /api/2.5/uploadFiles/{formId}/{questionId}` to upload files to answer before form submitting
- In API version 2.4 the following endpoints were introduced:
- `POST /api/2.4/form/link/{fileFormat}` to link form to a file
- `POST /api/2.4/form/unlink` to unlink form from a file
Expand Down Expand Up @@ -629,6 +631,21 @@ Delete all Submissions to a form
"data": 3
```

### Upload a file
Upload a files to answer before form submitting
- Endpoint: `/api/2.5/uploadFiles/{formId}/{questionId}`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _formId_ | Integer | ID of the form to upload the file to |
| _questionId_ | Integer | ID of the question to upload the file to |
| _files_ | Array of files | Files to upload |
- Response: **Status-Code OK**, as well as the id of the uploaded file and it's name.
```
"data": {"uploadedFileId": "uuid4", "fileName": "string"}
```

### Insert a Submission
Store Submission to Database
- Endpoint: `/api/v2.4/submission/insert`
Expand All @@ -644,10 +661,15 @@ Store Submission to Database
- QuestionID as key
- An **array** of values as value --> Even for short Text Answers, wrapped into Array.
- For Question-Types with pre-defined answers (`multiple`, `multiple_unique`, `dropdown`), the array contains the corresponding option-IDs.
- For File-Uploads, the array contains the objects with key `uploadedFileId` (value from Upload a file endpoint).
```
{
"1":[27,32], // dropdown or multiple
"2":["ShortTextAnswer"], // All Text-Based Question-Types
"3":[ // File-Upload
{"uploadedFileId": "uuid4"},
{"uploadedFileId": "uuid4"}
],
}
```
- Response: **Status-Code OK**.
Expand Down
20 changes: 12 additions & 8 deletions docs/DataStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,15 @@ Currently supported Question-Types are:
## Extra Settings
Optional extra settings for some [Question Types](#question-types)

| Extra Setting | Question Type | Type | Values | Description |
|--------------------|---------------|---------|--------|-------------|
| `allowOtherAnswer` | `multiple, multiple_unique` | Boolean | `true/false` | Allows the user to specify a custom answer |
| `shuffleOptions` | `dropdown, multiple, multiple_unique` | Boolean | `true/false` | The list of options should be shuffled |
| `optionsLimitMax` | `multiple` | Integer | - | Maximum number of options that can be selected |
| `optionsLimitMin` | `multiple` | Integer | - | Minimum number of options that must be selected |
| `validationType` | `short` | string | `null, 'phone', 'email', 'regex', 'number'` | Custom validation for checking a submission |
| `validationRegex` | `short` | string | regular expression | if `validationType` is 'regex' this defines the regular expression to apply |
| Extra Setting | Question Type | Type | Values | Description |
|-------------------------|---------------------------------------|------------------|---------------------------------------------|-----------------------------------------------------------------------------|
| `allowOtherAnswer` | `multiple, multiple_unique` | Boolean | `true/false` | Allows the user to specify a custom answer |
| `shuffleOptions` | `dropdown, multiple, multiple_unique` | Boolean | `true/false` | The list of options should be shuffled |
| `optionsLimitMax` | `multiple` | Integer | - | Maximum number of options that can be selected |
| `optionsLimitMin` | `multiple` | Integer | - | Minimum number of options that must be selected |
| `validationType` | `short` | string | `null, 'phone', 'email', 'regex', 'number'` | Custom validation for checking a submission |
| `validationRegex` | `short` | string | regular expression | if `validationType` is 'regex' this defines the regular expression to apply |
| `allowedFileTypes` | `file` | Array of strings | `'image', 'x-office/document'` | Allowed file types for file upload |
| `allowedFileExtensions` | `file` | Array of strings | `'jpg', 'png'` | Allowed file extensions for file upload |
| `maxAllowedFilesCount` | `file` | Integer | - | Maximum number of files that can be uploaded, 0 means no limit |
| `maxFileSize` | `file` | Integer | - | Maximum file size in bytes, 0 means no limit |
92 changes: 92 additions & 0 deletions lib/BackgroundJob/CleanupUploadedFilesJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace OCA\Forms\BackgroundJob;

use OCA\Forms\Constants;
use OCA\Forms\Db\FormMapper;
use OCA\Forms\Db\UploadedFileMapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use Psr\Log\LoggerInterface;

class CleanupUploadedFilesJob extends TimedJob {
private const FILE_LIFETIME = '-2 days';

public function __construct(

Check warning on line 17 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L17

Added line #L17 was not covered by tests
private IRootFolder $storage,
private FormMapper $formMapper,
private UploadedFileMapper $uploadedFileMapper,
private LoggerInterface $logger,
ITimeFactory $time) {
parent::__construct($time);

Check warning on line 23 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L23

Added line #L23 was not covered by tests

$this->setInterval(60 * 60 * 24);

Check warning on line 25 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L25

Added line #L25 was not covered by tests

}

/**
* @param array $argument
*/
public function run($argument): void {
$dateTime = new \DateTimeImmutable(self::FILE_LIFETIME);

Check warning on line 33 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L32-L33

Added lines #L32 - L33 were not covered by tests

$this->logger->info('Deleting files that were uploaded before {before} and still not submitted.', [
'before' => $dateTime->format(\DateTimeImmutable::ATOM),
]);

Check warning on line 37 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L35-L37

Added lines #L35 - L37 were not covered by tests

$uploadedFiles = $this->uploadedFileMapper->findUploadedEarlierThan($dateTime);

Check warning on line 39 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L39

Added line #L39 was not covered by tests

$deleted = 0;
$usersToCleanup = [];
foreach ($uploadedFiles as $uploadedFile) {
$this->logger->info('Deleting uploaded file "{originalFileName}" for form {formId}.', [
'originalFileName' => $uploadedFile->getOriginalFileName(),
'formId' => $uploadedFile->getFormId(),
]);

Check warning on line 47 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L41-L47

Added lines #L41 - L47 were not covered by tests

$form = $this->formMapper->findById($uploadedFile->getFormId());
$usersToCleanup[$form->getOwnerId()] = true;
$userFolder = $this->storage->getUserFolder($form->getOwnerId());

Check warning on line 51 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L49-L51

Added lines #L49 - L51 were not covered by tests

$nodes = $userFolder->getById($uploadedFile->getFileId());

Check warning on line 53 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L53

Added line #L53 was not covered by tests

if (!empty($nodes)) {
$node = $nodes[0];
$node->delete();

Check warning on line 57 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L55-L57

Added lines #L55 - L57 were not covered by tests
} else {
$this->logger->warning('Could not find uploaded file "{fileId}" for deletion.', [
'fileId' => $uploadedFile->getFileId(),
]);

Check warning on line 61 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L59-L61

Added lines #L59 - L61 were not covered by tests
}

$this->uploadedFileMapper->delete($uploadedFile);

Check warning on line 64 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L64

Added line #L64 was not covered by tests

$deleted++;

Check warning on line 66 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L66

Added line #L66 was not covered by tests
}

$this->logger->info('Deleted {deleted} uploaded files.', ['deleted' => $deleted]);

Check warning on line 69 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L69

Added line #L69 was not covered by tests

// now delete empty folders in user folders
$deleted = 0;
foreach (array_keys($usersToCleanup) as $userId) {
$this->logger->info('Cleaning up empty folders for user {userId}.', ['userId' => $userId]);
$userFolder = $this->storage->getUserFolder($userId);

Check warning on line 75 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L72-L75

Added lines #L72 - L75 were not covered by tests

$unsubmittedFilesFolder = $userFolder->get(Constants::UNSUBMITTED_FILES_FOLDER);
if (!$unsubmittedFilesFolder instanceof Folder) {
continue;

Check warning on line 79 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L77-L79

Added lines #L77 - L79 were not covered by tests
}

foreach ($unsubmittedFilesFolder->getDirectoryListing() as $node) {
if ($node->getName() < $dateTime->getTimestamp()) {
$node->delete();
$deleted++;

Check warning on line 85 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L82-L85

Added lines #L82 - L85 were not covered by tests
}
}
}

$this->logger->info('Deleted {deleted} folders.', ['deleted' => $deleted]);

Check warning on line 90 in lib/BackgroundJob/CleanupUploadedFilesJob.php

View check run for this annotation

Codecov / codecov/patch

lib/BackgroundJob/CleanupUploadedFilesJob.php#L90

Added line #L90 was not covered by tests
}
}
23 changes: 22 additions & 1 deletion lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Constants {
public const ANSWER_TYPE_DATE = 'date';
public const ANSWER_TYPE_DATETIME = 'datetime';
public const ANSWER_TYPE_TIME = 'time';
public const ANSWER_TYPE_FILE = 'file';

// All AnswerTypes
public const ANSWER_TYPES = [
Expand All @@ -101,7 +102,8 @@ class Constants {
self::ANSWER_TYPE_LONG,
self::ANSWER_TYPE_DATE,
self::ANSWER_TYPE_DATETIME,
self::ANSWER_TYPE_TIME
self::ANSWER_TYPE_TIME,
self::ANSWER_TYPE_FILE,
];

// AnswerTypes, that need/have predefined Options
Expand Down Expand Up @@ -155,6 +157,21 @@ class Constants {
'validationRegex' => ['string'],
];

public const EXTRA_SETTINGS_FILE = [
'allowedFileTypes',
'allowedFileExtensions',
'maxAllowedFilesCount',
'maxFileSize',
];

// should be in sync with FileTypes.js
public const EXTRA_SETTINGS_ALLOWED_FILE_TYPES = [
'image',
'x-office/document',
'x-office/presentation',
'x-office/spreadsheet',
];

/**
* !! Keep in sync with src/mixins/ShareTypes.js !!
*/
Expand Down Expand Up @@ -204,4 +221,8 @@ class Constants {
];

public const DEFAULT_FILE_FORMAT = 'csv';

public const UNSUBMITTED_FILES_FOLDER = 'forms/unsubmitted';

public const FILES_FOLDER = 'forms';
}
Loading

0 comments on commit d694698

Please sign in to comment.