From a7d618465f5cb6d88a22e3507b4feae6c91cd38b Mon Sep 17 00:00:00 2001 From: hamza221 Date: Wed, 7 Dec 2022 22:29:17 +0100 Subject: [PATCH] Download a single submission Signed-off-by: hamza221 --- appinfo/routes.php | 8 +++ lib/Controller/ApiController.php | 32 ++++++++++ lib/Service/SubmissionService.php | 84 +++++++++++++++++++++++++++ src/components/Results/Submission.vue | 17 +++++- 4 files changed, 140 insertions(+), 1 deletion(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 6d30cecd6..05c043d14 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -235,6 +235,14 @@ 'apiVersion' => 'v2' ] ], + [ + 'name' => 'api#exportSubmission', + 'url' => '/api/{apiVersion}/submissions/exportSubmission/{submissionId}', + 'verb' => 'GET', + 'requirements' => [ + 'apiVersion' => 'v2' + ] + ], [ 'name' => 'api#exportSubmissionsToCloud', 'url' => '/api/{apiVersion}/submissions/export', diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 01dcd8171..733c56439 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -1129,7 +1129,39 @@ public function exportQuestion(int $questionId): DataDownloadResponse { $csv = $this->submissionService->getQuestionCsv($formId, $questionId); return new DataDownloadResponse($csv['data'], $csv['fileName'], 'text/csv'); } + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * Export a single submission + * + * @param int $submissionId of the submission + * @return DataDownloadResponse + * @throws OCSBadRequestException + * @throws OCSForbiddenException + */ + public function exportSubmission(int $submissionId): DataDownloadResponse { + $this->logger->debug('Export submission: {submissionId}', [ + 'submissionId' => $submissionId, + ]); + try { + $submission = $this->submissionMapper->findById($submissionId); + $formId = $submission->getFormId(); + $form = $this->formMapper->findById($formId); + } catch (IMapperException $e) { + $this->logger->debug('Could not find submission'); + throw new OCSBadRequestException(); + } + + if ($form->getOwnerId() !== $this->currentUser->getUID()) { + $this->logger->debug('This form is not owned by the current user'); + throw new OCSForbiddenException(); + } + + $csv = $this->submissionService->getSubmissionCsv($formId, $submissionId); + return new DataDownloadResponse($csv['data'], $csv['fileName'], 'text/csv'); + } /** * @NoAdminRequired * diff --git a/lib/Service/SubmissionService.php b/lib/Service/SubmissionService.php index 5a674316c..63026623e 100644 --- a/lib/Service/SubmissionService.php +++ b/lib/Service/SubmissionService.php @@ -348,6 +348,90 @@ public function getQuestionCsv(int $formId, int $questionId): array { ]; } + /** + * Create a submission Csv + * @param string $formId id of the form + * @param string $questionId id of the question + * @return array{fileName:string,data:string} Array with 'fileName' and 'data' + */ + + public function getSubmissionCsv(int $formId ,int $submissionId): array { + + try { + $submission = $this->submissionMapper->findById($submissionId); + $form = $this->formMapper->findById($formId); + + } catch (DoesNotExistException $e) { + // Just ignore, if no Data. Returns empty Submissions-Array + } + + $questions = $this->questionMapper->findByForm($form->getId()); + $defaultTimeZone = date_default_timezone_get(); + $userTimezone = $this->config->getUserValue($this->currentUser->getUID(), 'core', 'timezone', $defaultTimeZone); + + // Process initial header + $header = []; + $header[] = $this->l10n->t('User ID'); + $header[] = $this->l10n->t('User display name'); + $header[] = $this->l10n->t('Timestamp'); + foreach ($questions as $question) { + $header[] = $question->getText(); + } + + // Init dataset + $data = []; + + // Process each answers + + $row = []; + + // User + $user = $this->userManager->get($submission->getUserId()); + if ($user === null) { + // Give empty userId + $row[] = ''; + // TRANSLATORS Shown on export if no Display-Name is available. + $row[] = $this->l10n->t('Anonymous user'); + } else { + $row[] = $user->getUID(); + $row[] = $user->getDisplayName(); + } + + // Date + $row[] = $this->dateTimeFormatter->formatDateTime($submission->getTimestamp(), 'full', 'full', new DateTimeZone($userTimezone), $this->l10n); + + // Answers, make sure we keep the question order + $answers = array_reduce($this->answerMapper->findBySubmission($submission->getId()), function (array $carry, Answer $answer) { + $questionId = $answer->getQuestionId(); + + // If key exists, insert separator + if (key_exists($questionId, $carry)) { + $carry[$questionId] .= '; ' . $answer->getText(); + } else { + $carry[$questionId] = $answer->getText(); + } + + return $carry; + }, []); + + foreach ($questions as $question) { + $row[] = key_exists($question->getId(), $answers) + ? $answers[$question->getId()] + : null; + } + + $data[] = $row; + + + // TRANSLATORS Appendix for CSV-Export: 'Form Title (responses).csv' + $fileName = $form->getTitle() . ' (' . $this->l10n->t('responses') . ').csv'; + + return [ + 'fileName' => $fileName, + 'data' => $this->array2csv($header, $data), + ]; + } + /** * Convert an array to a csv string * @param array $array diff --git a/src/components/Results/Submission.vue b/src/components/Results/Submission.vue index ed649e302..7af6ec561 100644 --- a/src/components/Results/Submission.vue +++ b/src/components/Results/Submission.vue @@ -25,6 +25,12 @@

{{ submission.userDisplayName }}

+ + + {{ t('forms', 'Download this response') }} +