diff --git a/appinfo/routes.php b/appinfo/routes.php index 8ebe6478b..6d30cecd6 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -227,6 +227,14 @@ 'apiVersion' => 'v2' ] ], + [ + 'name' => 'api#exportQuestion', + 'url' => '/api/{apiVersion}/submissions/exportQuestion/{questionId}', + '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 9706b1970..01dcd8171 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -1096,6 +1096,39 @@ public function exportSubmissions(string $hash): DataDownloadResponse { $csv = $this->submissionService->getSubmissionsCsv($hash); return new DataDownloadResponse($csv['data'], $csv['fileName'], 'text/csv'); } + /** + * @NoAdminRequired + * @NoCSRFRequired + * + * Export submissions of a specified Question + * + * @param int $questionId of the question + * @return DataDownloadResponse + * @throws OCSBadRequestException + * @throws OCSForbiddenException + */ + public function exportQuestion(int $questionId): DataDownloadResponse { + $this->logger->debug('Export submissions for Question: {questionId}', [ + 'questionId' => $questionId, + ]); + + try { + $question = $this->questionMapper->findById($questionId); + $formId = $question->getFormId(); + $form = $this->formMapper->findById($formId); + } catch (IMapperException $e) { + $this->logger->debug('Could not find question'); + 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->getQuestionCsv($formId, $questionId); + return new DataDownloadResponse($csv['data'], $csv['fileName'], 'text/csv'); + } /** * @NoAdminRequired diff --git a/lib/Db/AnswerMapper.php b/lib/Db/AnswerMapper.php index c5cd12747..3e1e0fd6c 100644 --- a/lib/Db/AnswerMapper.php +++ b/lib/Db/AnswerMapper.php @@ -57,6 +57,29 @@ public function findBySubmission(int $submissionId): array { return $this->findEntities($qb); } + /** + * @param int $submissionId + * @param int $questionId + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Answer[] + */ + + public function findBySubmissionAndQuestionId(int $submissionId, int $questionId): array { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('submission_id', $qb->createNamedParameter($submissionId, IQueryBuilder::PARAM_INT)) + ) + ->andWhere( + $qb->expr()->eq('question_id', $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT)) + + ); + + return $this->findEntities($qb); + } + /** * @param int $submissionId */ diff --git a/lib/Service/SubmissionService.php b/lib/Service/SubmissionService.php index ea1c17ea2..801733660 100644 --- a/lib/Service/SubmissionService.php +++ b/lib/Service/SubmissionService.php @@ -264,6 +264,90 @@ public function getSubmissionsCsv(string $hash): array { 'data' => $this->array2csv($header, $data), ]; } + /** + * Create Question specific CSV from Submissions to form + * @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 getQuestionCsv(int $formId, int $questionId): array { + $form = $this->formMapper->findByid($formId); + + try { + $submissionEntities = $this->submissionMapper->findByForm($form->getId()); + } catch (DoesNotExistException $e) { + // Just ignore, if no Data. Returns empty Submissions-Array + } + + $question = $this->questionMapper->findById($questionId); + $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'); + $header[] = $question->getText(); + + + // Init dataset + $data = []; + + + // Process each answers + foreach ($submissionEntities as $submission) { + $currentSubmissionAnswers = $this->answerMapper->findBySubmissionAndQuestionId($submission->getId(), $questionId); + $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($currentSubmissionAnswers, 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; + }, []); + + + $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 diff --git a/src/components/Results/ResultsSummary.vue b/src/components/Results/ResultsSummary.vue index 94f79c0a6..c6343b939 100644 --- a/src/components/Results/ResultsSummary.vue +++ b/src/components/Results/ResultsSummary.vue @@ -22,7 +22,18 @@