Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\BackgroundJob\IJobList;
use OCP\Files\Folder;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IUser;
Expand Down Expand Up @@ -742,6 +744,10 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse {
$question->setOrder(0);
$this->questionMapper->update($question);

if ($question->getType() === Constants::ANSWER_TYPE_FILE) {
$this->deleteQuestionFolder($form, $question);
}

// Update all question-order > deleted order.
$formQuestions = $this->questionMapper->findByForm($formId);
foreach ($formQuestions as $question) {
Expand All @@ -761,6 +767,43 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse {
return new DataResponse($questionId);
}

/**
* Delete question folders from all submissions
* @param Form $form The form
* @param Question $question The question
*/
private function deleteQuestionFolder(Form $form, Question $question): void {
try {
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
$formFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);

$formFolder = $userFolder->get($formFolderPath);
if (!$formFolder instanceof Folder) {
return;
}
$questionFolderPrefix = $question->getId() . ' - ';

// Iterate through submission folders and delete matching question folders
foreach ($formFolder->getDirectoryListing() as $submissionFolder) {
if (!$submissionFolder instanceof Folder) {
continue;
}
foreach ($submissionFolder->getDirectoryListing() as $node) {
if (str_starts_with($node->getName(), $questionFolderPrefix)) {
$node->delete();
}
}
}
} catch (NotFoundException) {
// Do nothing
} catch (\Throwable $e) {
$this->logger->warning('Failed to delete question folders: {error}', [
'error' => $e->getMessage(),
'questionId' => $question->getId(),
]);
}
}

/**
* Updates the Order of all Questions of a Form
*
Expand Down Expand Up @@ -1589,7 +1632,7 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse {
}

// Delete submission (incl. Answers)
$this->submissionMapper->deleteById($submissionId);
$this->submissionMapper->deleteById($form, $submissionId);
$this->formMapper->update($form);

return new DataResponse($submissionId);
Expand Down Expand Up @@ -1743,7 +1786,7 @@ public function uploadFiles(int $formId, int $questionId, string $shareHash = ''
} else {
$folder = $userFolder->newFolder($path);
}
/** @var \OCP\Files\Folder $folder */
/** @var Folder $folder */

$fileName = $folder->getNonExistingName($uploadedFile['name']);
$file = $folder->newFile($fileName, file_get_contents($uploadedFile['tmp_name']));
Expand Down Expand Up @@ -1819,7 +1862,7 @@ private function storeAnswersForQuestion(Form $form, $submissionId, array $quest
} else {
$folder = $userFolder->newFolder($path);
}
/** @var \OCP\Files\Folder $folder */
/** @var Folder $folder */

$file = $userFolder->getById($uploadedFile->getFileId())[0];
$name = $folder->getNonExistingName($file->getName());
Expand Down
44 changes: 44 additions & 0 deletions lib/Db/AnswerMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,48 @@

$qb->executeStatement();
}

/**
* Collect all fileIds for answers of a specific submission
* @param int $submissionId
* @return int[] Array of fileIds
*/
public function findFileIdsBySubmission(int $submissionId): array {
$qb = $this->db->getQueryBuilder();

$qb->select('file_id')
->from($this->getTableName())
->where(
$qb->expr()->eq('submission_id', $qb->createNamedParameter($submissionId, IQueryBuilder::PARAM_INT))
)
->andWhere($qb->expr()->isNotNull('file_id'));

$result = $qb->executeQuery();
$rows = $result->fetchFirstColumn();

Check failure on line 74 in lib/Db/AnswerMapper.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable32

UndefinedInterfaceMethod

lib/Db/AnswerMapper.php:74:20: UndefinedInterfaceMethod: Method OCP\DB\IResult::fetchFirstColumn does not exist (see https://psalm.dev/181)
$result->closeCursor();

return array_map('intval', $rows);
}

/**
* Collect all fileIds for answers of a specific question
* @param int $questionId
* @return int[] Array of fileIds
*/
public function findFileIdsByQuestion(int $questionId): array {
$qb = $this->db->getQueryBuilder();

$qb->select('file_id')
->from($this->getTableName())
->where(
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT))
)
->andWhere($qb->expr()->isNotNull('file_id'));

$result = $qb->executeQuery();
$rows = $result->fetchFirstColumn();

Check failure on line 96 in lib/Db/AnswerMapper.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable32

UndefinedInterfaceMethod

lib/Db/AnswerMapper.php:96:20: UndefinedInterfaceMethod: Method OCP\DB\IResult::fetchFirstColumn does not exist (see https://psalm.dev/181)
$result->closeCursor();

return array_map('intval', $rows);
}
}
39 changes: 39 additions & 0 deletions lib/Db/FormMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;

/**
* @extends QBMapper<Form>
Expand All @@ -30,6 +34,8 @@ public function __construct(
private ShareMapper $shareMapper,
private SubmissionMapper $submissionMapper,
private ConfigService $configService,
private IRootFolder $rootFolder,
private LoggerInterface $logger,
) {
parent::__construct($db, 'forms_v2_forms', Form::class);
}
Expand Down Expand Up @@ -221,6 +227,39 @@ public function deleteForm(Form $form): void {
$this->submissionMapper->deleteByForm($form->getId());
$this->shareMapper->deleteByForm($form->getId());
$this->questionMapper->deleteByForm($form->getId());

$this->deleteFormFolder($form);

$this->delete($form);
}

/**
* Delete the form folder from the file system
* @param Form $form The form instance
*/
private function deleteFormFolder(Form $form): void {
try {
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
$formsFolder = $userFolder->get(Constants::FILES_FOLDER);

if (!$formsFolder instanceof Folder) {
return;
}
$formFolderPrefix = $form->getId() . ' - ';

// Iterate through form folders and delete matching folders
foreach ($formsFolder->getDirectoryListing() as $node) {
if (str_starts_with($node->getName(), $formFolderPrefix)) {
$node->delete();
}
}
} catch (NotFoundException) {
// do nothing
} catch (\Throwable $e) {
$this->logger->warning('Failed to delete form folder: {error}', [
'error' => $e->getMessage(),
'formId' => $form->getId(),
]);
}
}
}
52 changes: 41 additions & 11 deletions lib/Db/SubmissionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IRootFolder;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;

/**
* @extends QBMapper<Submission>
Expand All @@ -20,10 +22,14 @@ class SubmissionMapper extends QBMapper {
* SubmissionMapper constructor.
* @param IDBConnection $db
* @param AnswerMapper $answerMapper
* @param IRootFolder $rootFolder
* @param LoggerInterface $logger
*/
public function __construct(
IDBConnection $db,
private AnswerMapper $answerMapper,
private IRootFolder $rootFolder,
private LoggerInterface $logger,
) {
parent::__construct($db, 'forms_v2_submissions', Submission::class);
}
Expand Down Expand Up @@ -179,22 +185,20 @@ protected function countSubmissionsWithFilters(int $formId, ?string $userId = nu

/**
* Delete the Submission, including answers.
* @param Form $form Form the submission belongs to.
* @param int $id of the submission to delete
*/
public function deleteById(int $id): void {
$qb = $this->db->getQueryBuilder();

// First delete corresponding answers.
public function deleteById(Form $form, int $id): void {
$submissionEntity = $this->findById($id);
$this->answerMapper->deleteBySubmission($submissionEntity->getId());

//Delete Submission
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
);
$fileIds = $this->answerMapper->findFileIdsBySubmission($submissionEntity->getId());
if (!empty($fileIds)) {
$this->deleteFilesByIds($fileIds, $form->getOwnerId());
}

$qb->executeStatement();
$this->answerMapper->deleteBySubmission($submissionEntity->getId());

$this->delete($submissionEntity);
}

/**
Expand All @@ -218,4 +222,30 @@ public function deleteByForm(int $formId): void {

$qb->executeStatement();
}

/**
* Delete files by their IDs
* @param int[] $fileIds Array of file IDs to delete
* @param string $userId User ID to get the user folder
*/
private function deleteFilesByIds(array $fileIds, string $userId): void {
if (empty($fileIds)) {
return;
}

try {
$userFolder = $this->rootFolder->getUserFolder($userId);
foreach ($fileIds as $fileId) {
$nodes = $userFolder->getById($fileId);
if (!empty($nodes)) {
$nodes[0]->delete();
}
}
} catch (\Throwable $e) {
$this->logger->warning('Failed to delete files: {error}', [
'error' => $e->getMessage(),
'fileIds' => $fileIds,
]);
}
}
}
Loading