Skip to content

Commit 6a29439

Browse files
committed
fix: Delete files on submission/question/form deletion
Signed-off-by: Kostiantyn Miakshyn <molodchick@gmail.com>
1 parent 91f55e3 commit 6a29439

4 files changed

Lines changed: 148 additions & 16 deletions

File tree

lib/Controller/ApiController.php

Lines changed: 48 additions & 5 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) {
@@ -1589,7 +1595,7 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse {
15891595
}
15901596

15911597
// Delete submission (incl. Answers)
1592-
$this->submissionMapper->deleteById($submissionId);
1598+
$this->submissionMapper->deleteById($form, $submissionId);
15931599
$this->formMapper->update($form);
15941600

15951601
return new DataResponse($submissionId);
@@ -1743,8 +1749,7 @@ public function uploadFiles(int $formId, int $questionId, string $shareHash = ''
17431749
} else {
17441750
$folder = $userFolder->newFolder($path);
17451751
}
1746-
/** @var \OCP\Files\Folder $folder */
1747-
1752+
/** @var Folder $folder */
17481753
$fileName = $folder->getNonExistingName($uploadedFile['name']);
17491754
$file = $folder->newFile($fileName, file_get_contents($uploadedFile['tmp_name']));
17501755

@@ -1819,8 +1824,7 @@ private function storeAnswersForQuestion(Form $form, $submissionId, array $quest
18191824
} else {
18201825
$folder = $userFolder->newFolder($path);
18211826
}
1822-
/** @var \OCP\Files\Folder $folder */
1823-
1827+
/** @var Folder $folder */
18241828
$file = $userFolder->getById($uploadedFile->getFileId())[0];
18251829
$name = $folder->getNonExistingName($file->getName());
18261830
$file->move($folder->getPath() . '/' . $name);
@@ -1992,4 +1996,43 @@ private function handleOwnerTransfer(Form $form, int $formId, string $currentUse
19921996
$this->formMapper->update($form);
19931997
return new DataResponse($form->getOwnerId());
19941998
}
1999+
2000+
2001+
2002+
/**
2003+
* Delete question folders from all submissions
2004+
* @param Form $form The form
2005+
* @param Question $question The question
2006+
*/
2007+
private function deleteQuestionFolder(Form $form, Question $question): void {
2008+
try {
2009+
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
2010+
$formFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
2011+
2012+
$formFolder = $userFolder->get($formFolderPath);
2013+
if (!$formFolder instanceof Folder) {
2014+
return;
2015+
}
2016+
$questionFolderPrefix = $question->getId() . ' - ';
2017+
2018+
// Iterate through submission folders and delete matching question folders
2019+
foreach ($formFolder->getDirectoryListing() as $submissionFolder) {
2020+
if (!$submissionFolder instanceof Folder) {
2021+
continue;
2022+
}
2023+
foreach ($submissionFolder->getDirectoryListing() as $node) {
2024+
if (str_starts_with($node->getName(), $questionFolderPrefix)) {
2025+
$node->delete();
2026+
}
2027+
}
2028+
}
2029+
} catch (NotFoundException) {
2030+
// Do nothing
2031+
} catch (\Throwable $e) {
2032+
$this->logger->warning('Failed to delete question folders: {error}', [
2033+
'error' => $e->getMessage(),
2034+
'questionId' => $question->getId(),
2035+
]);
2036+
}
2037+
}
19952038
}

lib/Db/AnswerMapper.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,26 @@ 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+
}
5779
}

lib/Db/FormMapper.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@
1313
use OCP\AppFramework\Db\QBMapper;
1414
use OCP\Comments\ICommentsManager;
1515
use OCP\DB\QueryBuilder\IQueryBuilder;
16+
use OCP\Files\Folder;
17+
use OCP\Files\IRootFolder;
18+
use OCP\Files\NotFoundException;
1619
use OCP\IDBConnection;
1720
use OCP\Share\IShare;
21+
use Psr\Log\LoggerInterface;
1822

1923
/**
2024
* @extends QBMapper<Form>
@@ -32,6 +36,8 @@ public function __construct(
3236
private SubmissionMapper $submissionMapper,
3337
private ConfigService $configService,
3438
private ICommentsManager $commentsManager,
39+
private IRootFolder $rootFolder,
40+
private LoggerInterface $logger,
3541
) {
3642
parent::__construct($db, 'forms_v2_forms', Form::class);
3743
}
@@ -225,6 +231,37 @@ public function deleteForm(Form $form): void {
225231
$this->shareMapper->deleteByForm($formId);
226232
$this->questionMapper->deleteByForm($formId);
227233
$this->commentsManager->deleteCommentsAtObject('forms', (string)$formId);
234+
$this->deleteFormFolder($form);
228235
$this->delete($form);
229236
}
237+
238+
/**
239+
* Delete the form folder from the file system
240+
* @param Form $form The form instance
241+
*/
242+
private function deleteFormFolder(Form $form): void {
243+
try {
244+
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
245+
$formsFolder = $userFolder->get(Constants::FILES_FOLDER);
246+
247+
if (!$formsFolder instanceof Folder) {
248+
return;
249+
}
250+
$formFolderPrefix = $form->getId() . ' - ';
251+
252+
// Iterate through form folders and delete matching folders
253+
foreach ($formsFolder->getDirectoryListing() as $node) {
254+
if (str_starts_with($node->getName(), $formFolderPrefix)) {
255+
$node->delete();
256+
}
257+
}
258+
} catch (NotFoundException) {
259+
// do nothing
260+
} catch (\Throwable $e) {
261+
$this->logger->warning('Failed to delete form folder: {error}', [
262+
'error' => $e->getMessage(),
263+
'formId' => $form->getId(),
264+
]);
265+
}
266+
}
230267
}

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)