Skip to content

Commit

Permalink
Save a Single Question as csv
Browse files Browse the repository at this point in the history
Signed-off-by: hamza221 <hamzamahjoubi221@gmail.com>
  • Loading branch information
hamza221 committed Aug 6, 2023
1 parent 08f7537 commit 88f6dd9
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 11 deletions.
16 changes: 16 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,22 @@
'apiVersion' => 'v2(\.1)?'
]
],
[
'name' => 'api#exportQuestion',
'url' => '/api/{apiVersion}/submissions/exportQuestion/{questionId}',
'verb' => 'GET',
'requirements' => [
'apiVersion' => 'v2'
]
],
[
'name' => 'api#exportSubmission',
'url' => '/api/{apiVersion}/submissions/exportSubmission/{submissionId}',
'verb' => 'GET',
'requirements' => [
'apiVersion' => 'v2'
]
],
[
'name' => 'api#exportSubmissionsToCloud',
'url' => '/api/{apiVersion}/submissions/export',
Expand Down
65 changes: 65 additions & 0 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,72 @@ 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);

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

View workflow job for this annotation

GitHub Actions / Nextcloud

InvalidArgument

lib/Controller/ApiController.php:1176:51: InvalidArgument: Argument 1 of OCA\Forms\Service\SubmissionService::getQuestionCsv expects string, but int provided (see https://psalm.dev/004)

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

View workflow job for this annotation

GitHub Actions / Nextcloud

InvalidArgument

lib/Controller/ApiController.php:1176:60: InvalidArgument: Argument 2 of OCA\Forms\Service\SubmissionService::getQuestionCsv expects string, but int provided (see https://psalm.dev/004)
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 (!$this->formsService->canSeeResults($form->id)) {
$this->logger->debug('The current user has no permission to get the results for this form');
throw new OCSForbiddenException();
}

$csv = $this->submissionService->getSubmissionCsv($formId, $submissionId);

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

View workflow job for this annotation

GitHub Actions / Nextcloud

InvalidArgument

lib/Controller/ApiController.php:1209:53: InvalidArgument: Argument 1 of OCA\Forms\Service\SubmissionService::getSubmissionCsv expects string, but int provided (see https://psalm.dev/004)
return new DataDownloadResponse($csv['data'], $csv['fileName'], 'text/csv');
}
/**
* @CORS
* @NoAdminRequired
Expand Down
23 changes: 23 additions & 0 deletions lib/Db/AnswerMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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
*/
Expand Down
181 changes: 173 additions & 8 deletions lib/Service/SubmissionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
use OCA\Forms\Db\FormMapper;
use OCA\Forms\Db\QuestionMapper;
use OCA\Forms\Db\SubmissionMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\NotPermittedException;
use OCP\IConfig;
use OCP\IDateTimeFormatter;

use OCP\IL10N;
use OCP\IUser;
Expand Down Expand Up @@ -89,6 +89,7 @@ public function __construct(FormMapper $formMapper,
AnswerMapper $answerMapper,
IRootFolder $storage,
IConfig $config,
IDateTimeFormatter $dateTimeFormatter,
IL10N $l10n,
LoggerInterface $logger,
IUserManager $userManager,
Expand All @@ -99,6 +100,7 @@ public function __construct(FormMapper $formMapper,
$this->answerMapper = $answerMapper;
$this->storage = $storage;
$this->config = $config;
$this->dateTimeFormatter = $dateTimeFormatter;

Check failure on line 103 in lib/Service/SubmissionService.php

View workflow job for this annotation

GitHub Actions / Nextcloud

UndefinedThisPropertyAssignment

lib/Service/SubmissionService.php:103:3: UndefinedThisPropertyAssignment: Instance property OCA\Forms\Service\SubmissionService::$dateTimeFormatter is not defined (see https://psalm.dev/040)
$this->l10n = $l10n;
$this->logger = $logger;
$this->userManager = $userManager;
Expand Down Expand Up @@ -156,7 +158,6 @@ public function getSubmissions(int $formId): array {
* @throws NotPermittedException
*/
public function writeCsvToCloud(string $hash, string $path): string {
/** @var \OCP\Files\Folder|\OCP\Files\File $node */
$node = $this->storage->getUserFolder($this->currentUser->getUID())->get($path);

// Get Data
Expand All @@ -167,17 +168,14 @@ public function writeCsvToCloud(string $hash, string $path): string {
if ($node->getExtension() === 'csv') {
$csvData['fileName'] = $node->getName();
}
/** @var \OCP\Files\Folder $node */
$node = $node->getParent();
}

// check if file exists, create otherwise.
try {
/** @var \OCP\Files\File $file */
$file = $node->get($csvData['fileName']);
} catch (\OCP\Files\NotFoundException $e) {
$node->newFile($csvData['fileName']);
/** @var \OCP\Files\File $file */
$file = $node->get($csvData['fileName']);
}

Expand Down Expand Up @@ -232,9 +230,9 @@ public function getSubmissionsCsv(string $hash): array {
$row[] = $user->getUID();
$row[] = $user->getDisplayName();
}

// Date
$row[] = date_format(date_timestamp_set(new DateTime(), $submission->getTimestamp())->setTimezone(new DateTimeZone($userTimezone)), 'c');
$row[] = $this->dateTimeFormatter->formatDateTime($submission->getTimestamp(), 'full', 'full', new DateTimeZone($userTimezone), $this->l10n);

Check failure on line 235 in lib/Service/SubmissionService.php

View workflow job for this annotation

GitHub Actions / Nextcloud

UndefinedThisPropertyFetch

lib/Service/SubmissionService.php:235:13: UndefinedThisPropertyFetch: Instance property OCA\Forms\Service\SubmissionService::$dateTimeFormatter is not defined (see https://psalm.dev/041)

// Answers, make sure we keep the question order
$answers = array_reduce($this->answerMapper->findBySubmission($submission->getId()), function (array $carry, Answer $answer) {
Expand Down Expand Up @@ -269,7 +267,174 @@ 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

Check failure on line 272 in lib/Service/SubmissionService.php

View workflow job for this annotation

GitHub Actions / Nextcloud

MismatchingDocblockParamType

lib/Service/SubmissionService.php:272:12: MismatchingDocblockParamType: Parameter $formId has wrong type 'string', should be 'int' (see https://psalm.dev/141)
* @param string $questionId id of the question

Check failure on line 273 in lib/Service/SubmissionService.php

View workflow job for this annotation

GitHub Actions / Nextcloud

MismatchingDocblockParamType

lib/Service/SubmissionService.php:273:12: MismatchingDocblockParamType: Parameter $questionId has wrong type 'string', should be 'int' (see https://psalm.dev/141)
* @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),
];
}

/**
* 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
Expand Down
Loading

0 comments on commit 88f6dd9

Please sign in to comment.