Skip to content

Commit 4e500db

Browse files
committed
load submission
Signed-off-by: Christian Hartmann <[email protected]>
1 parent a382ed2 commit 4e500db

File tree

10 files changed

+403
-6
lines changed

10 files changed

+403
-6
lines changed

docs/API_v3.md

+37
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,43 @@ Upload a file to an answer before form submission
822822
"data": {"uploadedFileId": integer, "fileName": "string"}
823823
```
824824

825+
### Get a specific submission
826+
827+
Get all Submissions to a Form
828+
829+
- Endpoint: `/api/v3/forms/{formId}/submissions/{submissionId}`
830+
- Method: `GET`
831+
- Url-Parameters:
832+
| Parameter | Type | Description |
833+
|-----------|---------|-------------|
834+
| _formId_ | Integer | ID of the form to get the submissions for |
835+
| _submissionId_ | Integer | ID of the submission to get |
836+
- Response: The submission
837+
838+
```
839+
"data": {
840+
"id": 6,
841+
"formId": 3,
842+
"userId": "jonas",
843+
"timestamp": 1611274453,
844+
"answers": [
845+
{
846+
"id": 8,
847+
"submissionId": 6,
848+
"questionId": 1,
849+
"text": "Option 3"
850+
},
851+
{
852+
"id": 9,
853+
"submissionId": 6,
854+
"questionId": 2,
855+
"text": "One more."
856+
},
857+
],
858+
"userDisplayName": "jonas"
859+
}
860+
```
861+
825862
### Insert a Submission
826863

827864
Store Submission to Database

lib/Controller/ApiController.php

+47
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
* @psalm-import-type FormsPartialForm from ResponseDefinitions
6363
* @psalm-import-type FormsQuestion from ResponseDefinitions
6464
* @psalm-import-type FormsQuestionType from ResponseDefinitions
65+
* @paslm-import-type FormsSubmission from ResponseDefinitions
6566
* @psalm-import-type FormsSubmissions from ResponseDefinitions
6667
* @psalm-import-type FormsUploadedFile from ResponseDefinitions
6768
*/
@@ -1200,6 +1201,52 @@ public function getSubmissions(int $formId, ?string $fileFormat = null): DataRes
12001201
return new DataResponse($response);
12011202
}
12021203

1204+
/**
1205+
* Get a specific submission
1206+
*
1207+
* @param int $formId of the form
1208+
* @param int $submissionId of the submission
1209+
* @return DataResponse<Http::STATUS_OK, FormsSubmission, array{}>
1210+
* @throws OCSNotFoundException Could not find form
1211+
* @throws OCSForbiddenException The current user has no permission to get this submission
1212+
*
1213+
* 200: the submissions of the form
1214+
*/
1215+
#[CORS()]
1216+
#[NoAdminRequired()]
1217+
#[BruteForceProtection(action: 'form')]
1218+
#[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}/submissions/{submissionId}')]
1219+
public function getSubmission(int $formId, int $submissionId): DataResponse|DataDownloadResponse {
1220+
$form = $this->getFormIfAllowed($formId, Constants::PERMISSION_RESULTS);
1221+
1222+
$submission = $this->submissionService->getSubmission($submissionId);
1223+
if ($submission === null) {
1224+
throw new OCSNotFoundException('Submission doesn\'t exist');
1225+
}
1226+
1227+
if ($submission['formId'] !== $formId) {
1228+
throw new OCSBadRequestException('Submission doesn\'t belong to given form');
1229+
}
1230+
1231+
// Append Display Names
1232+
if (substr($submission['userId'], 0, 10) === 'anon-user-') {
1233+
// Anonymous User
1234+
// TRANSLATORS On Results when listing the single Responses to the form, this text is shown as heading of the Response.
1235+
$submission['userDisplayName'] = $this->l10n->t('Anonymous response');
1236+
} else {
1237+
$userEntity = $this->userManager->get($submission['userId']);
1238+
1239+
if ($userEntity instanceof IUser) {
1240+
$submission['userDisplayName'] = $userEntity->getDisplayName();
1241+
} else {
1242+
// Fallback, should not occur regularly.
1243+
$submission['userDisplayName'] = $submission['userId'];
1244+
}
1245+
}
1246+
1247+
return new DataResponse($submission);
1248+
}
1249+
12031250
/**
12041251
* Delete all submissions of a specified form
12051252
*

lib/Controller/PageController.php

+24-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use OCA\Forms\Db\Form;
1212
use OCA\Forms\Db\FormMapper;
1313
use OCA\Forms\Db\ShareMapper;
14+
use OCA\Forms\Db\Submission;
15+
use OCA\Forms\Db\SubmissionMapper;
1416
use OCA\Forms\Service\ConfigService;
1517
use OCA\Forms\Service\FormsService;
1618

@@ -45,6 +47,7 @@ public function __construct(
4547
IRequest $request,
4648
private FormMapper $formMapper,
4749
private ShareMapper $shareMapper,
50+
private SubmissionMapper $submissionMapper,
4851
private ConfigService $configService,
4952
private FormsService $formsService,
5053
private IAccountManager $accountManager,
@@ -63,7 +66,7 @@ public function __construct(
6366
#[NoAdminRequired()]
6467
#[NoCSRFRequired()]
6568
#[FrontpageRoute(verb: 'GET', url: '/')]
66-
public function index(?string $hash = null): TemplateResponse {
69+
public function index(?string $hash = null, ?int $submissionId = null): TemplateResponse {
6770
Util::addScript($this->appName, 'forms-main');
6871
Util::addStyle($this->appName, 'forms');
6972
Util::addStyle($this->appName, 'forms-style');
@@ -81,6 +84,16 @@ public function index(?string $hash = null): TemplateResponse {
8184
}
8285
}
8386

87+
if (isset($submissionId)) {
88+
try {
89+
$submission = $this->submissionMapper->findById($submissionId);
90+
$this->initialState->provideInitialState('submissionId', $submission->id);
91+
} catch (DoesNotExistException $e) {
92+
// Provide null to indicate no form was found
93+
$this->initialState->provideInitialState('submissionId', null);
94+
}
95+
}
96+
8497
return new TemplateResponse($this->appName, self::TEMPLATE_MAIN, [
8598
'id-app-content' => '#app-content-vue',
8699
'id-app-navigation' => '#app-navigation-vue',
@@ -97,6 +110,16 @@ public function views(string $hash): TemplateResponse {
97110
return $this->index($hash);
98111
}
99112

113+
/**
114+
* @return TemplateResponse
115+
*/
116+
#[NoAdminRequired()]
117+
#[NoCSRFRequired()]
118+
#[FrontpageRoute(verb: 'GET', url: '/{hash}/submit/{submissionId}', requirements: ['hash' => '[a-zA-Z0-9]{16,}', 'submissionId' => '\d+'])]
119+
public function submitViewWithSubmission(string $hash, int $submissionId): TemplateResponse {
120+
return $this->formMapper->findByHash($hash)->getAllowEdit() ? $this->index($hash, $submissionId) : $this->index($hash);
121+
}
122+
100123
/**
101124
* @param string $hash
102125
* @return RedirectResponse|TemplateResponse Redirect to login or internal view.

lib/Service/SubmissionService.php

+23
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,29 @@ public function getSubmissions(int $formId, ?string $userId = null): array {
121121
}
122122
}
123123

124+
/**
125+
* Load specific submission
126+
*
127+
* @param integer $submissionId id of the submission
128+
* @return array{
129+
* id: int,
130+
* formId: int,
131+
* userId: string,
132+
* timestamp: int,
133+
* answers: list<FormsAnswer>,
134+
* }
135+
*/
136+
public function getSubmission(int $submissionId): ?array {
137+
try {
138+
$submissionEntity = $this->submissionMapper->findById($submissionId);
139+
$submission = $submissionEntity->read();
140+
$submission['answers'] = $this->getAnswers($submission['id']);
141+
return $submission;
142+
} catch (DoesNotExistException $e) {
143+
return null;
144+
}
145+
}
146+
124147
/**
125148
* Export Submissions to Cloud-Filesystem
126149
*

openapi.json

+133
Original file line numberDiff line numberDiff line change
@@ -3414,6 +3414,139 @@
34143414
}
34153415
},
34163416
"/ocs/v2.php/apps/forms/api/v3/forms/{formId}/submissions/{submissionId}": {
3417+
"get": {
3418+
"operationId": "api-get-submission",
3419+
"summary": "Get a specific submission",
3420+
"description": "This endpoint allows CORS requests",
3421+
"tags": [
3422+
"api"
3423+
],
3424+
"security": [
3425+
{
3426+
"basic_auth": []
3427+
}
3428+
],
3429+
"parameters": [
3430+
{
3431+
"name": "formId",
3432+
"in": "path",
3433+
"description": "of the form",
3434+
"required": true,
3435+
"schema": {
3436+
"type": "integer",
3437+
"format": "int64"
3438+
}
3439+
},
3440+
{
3441+
"name": "submissionId",
3442+
"in": "path",
3443+
"description": "of the submission",
3444+
"required": true,
3445+
"schema": {
3446+
"type": "integer",
3447+
"format": "int64"
3448+
}
3449+
},
3450+
{
3451+
"name": "OCS-APIRequest",
3452+
"in": "header",
3453+
"description": "Required to be true for the API request to pass",
3454+
"required": true,
3455+
"schema": {
3456+
"type": "boolean",
3457+
"default": true
3458+
}
3459+
}
3460+
],
3461+
"responses": {
3462+
"200": {
3463+
"description": "the submissions of the form",
3464+
"content": {
3465+
"application/json": {
3466+
"schema": {
3467+
"type": "object",
3468+
"required": [
3469+
"ocs"
3470+
],
3471+
"properties": {
3472+
"ocs": {
3473+
"type": "object",
3474+
"required": [
3475+
"meta",
3476+
"data"
3477+
],
3478+
"properties": {
3479+
"meta": {
3480+
"$ref": "#/components/schemas/OCSMeta"
3481+
},
3482+
"data": {
3483+
"$ref": "#/components/schemas/Submission"
3484+
}
3485+
}
3486+
}
3487+
}
3488+
}
3489+
}
3490+
}
3491+
},
3492+
"404": {
3493+
"description": "Could not find form",
3494+
"content": {
3495+
"application/json": {
3496+
"schema": {
3497+
"type": "object",
3498+
"required": [
3499+
"ocs"
3500+
],
3501+
"properties": {
3502+
"ocs": {
3503+
"type": "object",
3504+
"required": [
3505+
"meta",
3506+
"data"
3507+
],
3508+
"properties": {
3509+
"meta": {
3510+
"$ref": "#/components/schemas/OCSMeta"
3511+
},
3512+
"data": {}
3513+
}
3514+
}
3515+
}
3516+
}
3517+
}
3518+
}
3519+
},
3520+
"403": {
3521+
"description": "The current user has no permission to get this submission",
3522+
"content": {
3523+
"application/json": {
3524+
"schema": {
3525+
"type": "object",
3526+
"required": [
3527+
"ocs"
3528+
],
3529+
"properties": {
3530+
"ocs": {
3531+
"type": "object",
3532+
"required": [
3533+
"meta",
3534+
"data"
3535+
],
3536+
"properties": {
3537+
"meta": {
3538+
"$ref": "#/components/schemas/OCSMeta"
3539+
},
3540+
"data": {}
3541+
}
3542+
}
3543+
}
3544+
}
3545+
}
3546+
}
3547+
}
3548+
}
3549+
},
34173550
"put": {
34183551
"operationId": "api-update-submission",
34193552
"summary": "Update an existing submission",

src/components/Questions/QuestionDropdown.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export default {
150150
}
151151

152152
const selected = this.values.map((id) =>
153-
this.options.find((option) => option.id === id),
153+
this.options.find((option) => option.id === parseInt(id)),
154154
)
155155

156156
return this.isMultiple ? selected : selected[0]

0 commit comments

Comments
 (0)