Skip to content

Commit 00a550c

Browse files
committed
fix: Delete files on submission/question/form deletion
Signed-off-by: Kostiantyn Miakshyn <molodchick@gmail.com>
1 parent 38a94d0 commit 00a550c

4 files changed

Lines changed: 171 additions & 14 deletions

File tree

lib/Controller/ApiController.php

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
use OCP\AppFramework\OCS\OCSNotFoundException;
4747
use OCP\AppFramework\OCSController;
4848
use OCP\BackgroundJob\IJobList;
49+
use OCP\Files\Folder;
4950
use OCP\Files\IMimeTypeDetector;
5051
use OCP\Files\IRootFolder;
52+
use OCP\Files\NotFoundException;
5153
use OCP\IL10N;
5254
use OCP\IRequest;
5355
use OCP\IUser;
@@ -742,6 +744,10 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse {
742744
$question->setOrder(0);
743745
$this->questionMapper->update($question);
744746

747+
if ($question->getType() === Constants::ANSWER_TYPE_FILE) {
748+
$this->deleteQuestionFolder($form, $question);
749+
}
750+
745751
// Update all question-order > deleted order.
746752
$formQuestions = $this->questionMapper->findByForm($formId);
747753
foreach ($formQuestions as $question) {
@@ -761,6 +767,43 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse {
761767
return new DataResponse($questionId);
762768
}
763769

770+
/**
771+
* Delete question folders from all submissions
772+
* @param Form $form The form
773+
* @param Question $question The question
774+
*/
775+
private function deleteQuestionFolder(Form $form, Question $question): void {
776+
try {
777+
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
778+
$formFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
779+
780+
$formFolder = $userFolder->get($formFolderPath);
781+
if (!$formFolder instanceof Folder) {
782+
return;
783+
}
784+
$questionFolderPrefix = $question->getId() . ' - ';
785+
786+
// Iterate through submission folders and delete matching question folders
787+
foreach ($formFolder->getDirectoryListing() as $submissionFolder) {
788+
if (!$submissionFolder instanceof Folder) {
789+
continue;
790+
}
791+
foreach ($submissionFolder->getDirectoryListing() as $node) {
792+
if (str_starts_with($node->getName(), $questionFolderPrefix)) {
793+
$node->delete();
794+
}
795+
}
796+
}
797+
} catch (NotFoundException) {
798+
// Do nothing
799+
} catch (\Throwable $e) {
800+
$this->logger->warning('Failed to delete question folders: {error}', [
801+
'error' => $e->getMessage(),
802+
'questionId' => $question->getId(),
803+
]);
804+
}
805+
}
806+
764807
/**
765808
* Updates the Order of all Questions of a Form
766809
*
@@ -1589,7 +1632,7 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse {
15891632
}
15901633

15911634
// Delete submission (incl. Answers)
1592-
$this->submissionMapper->deleteById($submissionId);
1635+
$this->submissionMapper->deleteById($form, $submissionId);
15931636
$this->formMapper->update($form);
15941637

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

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

18241867
$file = $userFolder->getById($uploadedFile->getFileId())[0];
18251868
$name = $folder->getNonExistingName($file->getName());

lib/Db/AnswerMapper.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,48 @@ public function deleteBySubmission(int $submissionId): void {
5454

5555
$qb->executeStatement();
5656
}
57+
58+
/**
59+
* Collect all fileIds for answers of a specific submission
60+
* @param int $submissionId
61+
* @return int[] Array of fileIds
62+
*/
63+
public function findFileIdsBySubmission(int $submissionId): array {
64+
$qb = $this->db->getQueryBuilder();
65+
66+
$qb->select('file_id')
67+
->from($this->getTableName())
68+
->where(
69+
$qb->expr()->eq('submission_id', $qb->createNamedParameter($submissionId, IQueryBuilder::PARAM_INT))
70+
)
71+
->andWhere($qb->expr()->isNotNull('file_id'));
72+
73+
$result = $qb->executeQuery();
74+
$rows = $result->fetchFirstColumn();
75+
$result->closeCursor();
76+
77+
return array_map('intval', $rows);
78+
}
79+
80+
/**
81+
* Collect all fileIds for answers of a specific question
82+
* @param int $questionId
83+
* @return int[] Array of fileIds
84+
*/
85+
public function findFileIdsByQuestion(int $questionId): array {
86+
$qb = $this->db->getQueryBuilder();
87+
88+
$qb->select('file_id')
89+
->from($this->getTableName())
90+
->where(
91+
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT))
92+
)
93+
->andWhere($qb->expr()->isNotNull('file_id'));
94+
95+
$result = $qb->executeQuery();
96+
$rows = $result->fetchFirstColumn();
97+
$result->closeCursor();
98+
99+
return array_map('intval', $rows);
100+
}
57101
}

lib/Db/FormMapper.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
use OCP\AppFramework\Db\Entity;
1313
use OCP\AppFramework\Db\QBMapper;
1414
use OCP\DB\QueryBuilder\IQueryBuilder;
15+
use OCP\Files\Folder;
16+
use OCP\Files\IRootFolder;
17+
use OCP\Files\NotFoundException;
1518
use OCP\IDBConnection;
1619
use OCP\Share\IShare;
20+
use Psr\Log\LoggerInterface;
1721

1822
/**
1923
* @extends QBMapper<Form>
@@ -30,6 +34,8 @@ public function __construct(
3034
private ShareMapper $shareMapper,
3135
private SubmissionMapper $submissionMapper,
3236
private ConfigService $configService,
37+
private IRootFolder $rootFolder,
38+
private LoggerInterface $logger,
3339
) {
3440
parent::__construct($db, 'forms_v2_forms', Form::class);
3541
}
@@ -221,6 +227,40 @@ public function deleteForm(Form $form): void {
221227
$this->submissionMapper->deleteByForm($form->getId());
222228
$this->shareMapper->deleteByForm($form->getId());
223229
$this->questionMapper->deleteByForm($form->getId());
230+
231+
$this->deleteFormFolder($form);
232+
224233
$this->delete($form);
225234
}
235+
236+
/**
237+
* Delete the form folder from the file system
238+
* @param Form $form The form instance
239+
*/
240+
private function deleteFormFolder(Form $form): void {
241+
try {
242+
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
243+
$formsFolder = $userFolder->get(Constants::FILES_FOLDER);
244+
245+
if (!$formsFolder instanceof Folder) {
246+
return;
247+
}
248+
$formFolderPrefix = $form->getId() . ' - ';
249+
250+
// Iterate through form folders and delete matching folder
251+
foreach ($formsFolder->getDirectoryListing() as $node) {
252+
if (str_starts_with($node->getName(), $formFolderPrefix)) {
253+
$node->delete();
254+
break;
255+
}
256+
}
257+
} catch (NotFoundException) {
258+
// do nothing
259+
} catch (\Throwable $e) {
260+
$this->logger->warning('Failed to delete form folder: {error}', [
261+
'error' => $e->getMessage(),
262+
'formId' => $form->getId(),
263+
]);
264+
}
265+
}
226266
}

lib/Db/SubmissionMapper.php

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
use OCP\AppFramework\Db\DoesNotExistException;
1111
use OCP\AppFramework\Db\QBMapper;
1212
use OCP\DB\QueryBuilder\IQueryBuilder;
13+
use OCP\Files\IRootFolder;
1314
use OCP\IDBConnection;
15+
use Psr\Log\LoggerInterface;
1416

1517
/**
1618
* @extends QBMapper<Submission>
@@ -20,10 +22,14 @@ class SubmissionMapper extends QBMapper {
2022
* SubmissionMapper constructor.
2123
* @param IDBConnection $db
2224
* @param AnswerMapper $answerMapper
25+
* @param IRootFolder $rootFolder
26+
* @param LoggerInterface $logger
2327
*/
2428
public function __construct(
2529
IDBConnection $db,
2630
private AnswerMapper $answerMapper,
31+
private IRootFolder $rootFolder,
32+
private LoggerInterface $logger,
2733
) {
2834
parent::__construct($db, 'forms_v2_submissions', Submission::class);
2935
}
@@ -179,22 +185,20 @@ protected function countSubmissionsWithFilters(int $formId, ?string $userId = nu
179185

180186
/**
181187
* Delete the Submission, including answers.
188+
* @param Form $form Form the submission belongs to.
182189
* @param int $id of the submission to delete
183190
*/
184-
public function deleteById(int $id): void {
185-
$qb = $this->db->getQueryBuilder();
186-
187-
// First delete corresponding answers.
191+
public function deleteById(Form $form, int $id): void {
188192
$submissionEntity = $this->findById($id);
189-
$this->answerMapper->deleteBySubmission($submissionEntity->getId());
190193

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

197-
$qb->executeStatement();
199+
$this->answerMapper->deleteBySubmission($submissionEntity->getId());
200+
201+
$this->delete($submissionEntity);
198202
}
199203

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

219223
$qb->executeStatement();
220224
}
225+
226+
/**
227+
* Delete files by their IDs
228+
* @param int[] $fileIds Array of file IDs to delete
229+
* @param string $userId User ID to get the user folder
230+
*/
231+
private function deleteFilesByIds(array $fileIds, string $userId): void {
232+
if (empty($fileIds)) {
233+
return;
234+
}
235+
236+
try {
237+
$userFolder = $this->rootFolder->getUserFolder($userId);
238+
foreach ($fileIds as $fileId) {
239+
$nodes = $userFolder->getById($fileId);
240+
if (!empty($nodes)) {
241+
$nodes[0]->delete();
242+
}
243+
}
244+
} catch (\Throwable $e) {
245+
$this->logger->warning('Failed to delete files: {error}', [
246+
'error' => $e->getMessage(),
247+
'fileIds' => $fileIds,
248+
]);
249+
}
250+
}
221251
}

0 commit comments

Comments
 (0)