Skip to content

Commit 711f6f6

Browse files
committed
fix: Delete files on submission/question/form deletion v2
Signed-off-by: Kostiantyn Miakshyn <molodchick@gmail.com>
1 parent ec01ca4 commit 711f6f6

11 files changed

Lines changed: 185 additions & 111 deletions

File tree

lib/BackgroundJob/DeleteQuestionFoldersJob.php

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,17 @@
77

88
namespace OCA\Forms\BackgroundJob;
99

10-
use OCA\Forms\Db\FormMapper;
11-
use OCA\Forms\Service\FormsService;
10+
use OCA\Forms\Helper\FilePathHelper;
1211
use OCP\AppFramework\Utility\ITimeFactory;
1312
use OCP\BackgroundJob\QueuedJob;
1413
use OCP\Files\Folder;
15-
use OCP\Files\IRootFolder;
1614
use OCP\Files\NotFoundException;
1715
use Psr\Log\LoggerInterface;
1816

1917
class DeleteQuestionFoldersJob extends QueuedJob {
2018
public function __construct(
2119
ITimeFactory $time,
22-
private FormMapper $formMapper,
23-
private FormsService $formsService,
24-
private IRootFolder $rootFolder,
20+
private FilePathHelper $filePathHelper,
2521
private LoggerInterface $logger,
2622
) {
2723
parent::__construct($time);
@@ -36,17 +32,13 @@ public function run($argument): void {
3632
$ownerId = $argument['ownerId'];
3733

3834
try {
39-
$form = $this->formMapper->findById($formId);
4035
$this->logger->debug('Deleting question folders for question {questionId} in form {formId}', [
4136
'questionId' => $questionId,
4237
'formId' => $formId,
4338
]);
4439

45-
$userFolder = $this->rootFolder->getUserFolder($ownerId);
46-
$formFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
47-
48-
$formFolder = $userFolder->get($formFolderPath);
49-
if (!$formFolder instanceof Folder) {
40+
$formFolders = $this->filePathHelper->getAllFormFoldersById($formId, $ownerId);
41+
if (empty($formFolders)) {
5042
$this->logger->notice('Form folder not found, nothing to delete', [
5143
'formId' => $formId,
5244
]);
@@ -56,15 +48,18 @@ public function run($argument): void {
5648
$questionFolderPrefix = $questionId . ' - ';
5749
$deletedCount = 0;
5850

59-
// Iterate through submission folders and delete matching question folders
60-
foreach ($formFolder->getDirectoryListing() as $submissionFolder) {
61-
if (!$submissionFolder instanceof Folder) {
62-
continue;
63-
}
64-
foreach ($submissionFolder->getDirectoryListing() as $questionFolder) {
65-
if (str_starts_with($questionFolder->getName(), $questionFolderPrefix)) {
66-
$questionFolder->delete();
67-
$deletedCount++;
51+
// Iterate through all form folders (handles form renames)
52+
foreach ($formFolders as $formFolder) {
53+
// Iterate through submission folders and delete matching question folders
54+
foreach ($formFolder->getDirectoryListing() as $submissionFolder) {
55+
if (!$submissionFolder instanceof Folder) {
56+
continue;
57+
}
58+
foreach ($submissionFolder->getDirectoryListing() as $questionFolder) {
59+
if (str_starts_with($questionFolder->getName(), $questionFolderPrefix)) {
60+
$questionFolder->delete();
61+
$deletedCount++;
62+
}
6863
}
6964
}
7065
}

lib/Controller/ShareApiController.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use OCA\Forms\Db\FormMapper;
1515
use OCA\Forms\Db\Share;
1616
use OCA\Forms\Db\ShareMapper;
17+
use OCA\Forms\Helper\FilePathHelper;
1718
use OCA\Forms\ResponseDefinitions;
1819
use OCA\Forms\Service\CirclesService;
1920
use OCA\Forms\Service\ConfigService;
@@ -62,6 +63,7 @@ public function __construct(
6263
private ISecureRandom $secureRandom,
6364
private CirclesService $circlesService,
6465
private IRootFolder $rootFolder,
66+
private FilePathHelper $filePathHelper,
6567
private IManager $shareManager,
6668
) {
6769
parent::__construct($appName, $request);
@@ -273,7 +275,7 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da
273275
if (in_array($formShare->getShareType(), [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_USERGROUP, IShare::TYPE_CIRCLE], true)) {
274276
if (in_array(Constants::PERMISSION_RESULTS, $keyValuePairs['permissions'], true)) {
275277
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
276-
$uploadedFilesFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
278+
$uploadedFilesFolderPath = $this->filePathHelper->getFormUploadedFilesFolderPath($form);
277279
try {
278280
/** @var \OCP\Files\Folder $folder */
279281
$folder = $userFolder->get($uploadedFilesFolderPath);
@@ -358,7 +360,7 @@ private function removeUploadedFilesShare(Form $form, Share $formShare): void {
358360
}
359361

360362
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
361-
$uploadedFilesFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
363+
$uploadedFilesFolderPath = $this->filePathHelper->getFormUploadedFilesFolderPath($form);
362364
try {
363365
$folder = $userFolder->get($uploadedFilesFolderPath);
364366
} catch (NotFoundException $e) {

lib/Db/AnswerMapper.php

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

lib/Db/FormMapper.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
namespace OCA\Forms\Db;
99

1010
use OCA\Forms\Constants;
11+
use OCA\Forms\Helper\FilePathHelper;
1112
use OCA\Forms\Service\ConfigService;
1213
use OCP\AppFramework\Db\Entity;
1314
use OCP\AppFramework\Db\QBMapper;
1415
use OCP\Comments\ICommentsManager;
1516
use OCP\DB\QueryBuilder\IQueryBuilder;
1617
use OCP\Files\Folder;
17-
use OCP\Files\IRootFolder;
18-
use OCP\Files\NotFoundException;
1918
use OCP\IDBConnection;
2019
use OCP\Share\IShare;
2120
use Psr\Log\LoggerInterface;
@@ -36,7 +35,7 @@ public function __construct(
3635
private SubmissionMapper $submissionMapper,
3736
private ConfigService $configService,
3837
private ICommentsManager $commentsManager,
39-
private IRootFolder $rootFolder,
38+
private FilePathHelper $filePathHelper,
4039
private LoggerInterface $logger,
4140
) {
4241
parent::__construct($db, 'forms_v2_forms', Form::class);
@@ -240,10 +239,8 @@ public function deleteForm(Form $form): void {
240239
*/
241240
private function deleteFormFolder(Form $form): void {
242241
try {
243-
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
244-
$formsFolder = $userFolder->get(Constants::FILES_FOLDER);
245-
246-
if (!$formsFolder instanceof Folder) {
242+
$formsFolder = $this->filePathHelper->getFormsFolder($form->getOwnerId());
243+
if ($formsFolder === null) {
247244
return;
248245
}
249246
$formFolderPrefix = $form->getId() . ' - ';
@@ -254,8 +251,6 @@ private function deleteFormFolder(Form $form): void {
254251
$node->delete();
255252
}
256253
}
257-
} catch (NotFoundException) {
258-
// do nothing
259254
} catch (\Throwable $e) {
260255
$this->logger->warning('Failed to delete form folder: {error}', [
261256
'error' => $e->getMessage(),

lib/Db/SubmissionMapper.php

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77

88
namespace OCA\Forms\Db;
99

10-
use OCA\Forms\Service\FormsService;
10+
use OCA\Forms\Helper\FilePathHelper;
1111
use OCP\AppFramework\Db\DoesNotExistException;
1212
use OCP\AppFramework\Db\QBMapper;
1313
use OCP\DB\QueryBuilder\IQueryBuilder;
1414
use OCP\Files\Folder;
15-
use OCP\Files\IRootFolder;
16-
use OCP\Files\NotFoundException;
1715
use OCP\IDBConnection;
1816
use Psr\Log\LoggerInterface;
1917

@@ -25,16 +23,14 @@ class SubmissionMapper extends QBMapper {
2523
* SubmissionMapper constructor.
2624
* @param IDBConnection $db
2725
* @param AnswerMapper $answerMapper
28-
* @param IRootFolder $rootFolder
26+
* @param FilePathHelper $filePathHelper
2927
* @param LoggerInterface $logger
30-
* @param FormsService $formsService
3128
*/
3229
public function __construct(
3330
IDBConnection $db,
3431
private AnswerMapper $answerMapper,
35-
private IRootFolder $rootFolder,
32+
private FilePathHelper $filePathHelper,
3633
private LoggerInterface $logger,
37-
private FormsService $formsService,
3834
) {
3935
parent::__construct($db, 'forms_v2_submissions', Submission::class);
4036
}
@@ -232,20 +228,10 @@ public function deleteByForm(int $formId): void {
232228
*/
233229
private function deleteSubmissionFolder(Form $form, int $submissionId): void {
234230
try {
235-
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
236-
$formFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
237-
238-
$formFolder = $userFolder->get($formFolderPath);
239-
if (!$formFolder instanceof Folder) {
240-
return;
241-
}
242-
243-
$submissionFolder = $formFolder->get((string)$submissionId);
244-
if ($submissionFolder instanceof Folder) {
231+
$submissionFolder = $this->filePathHelper->getSubmissionFolder($form, $submissionId);
232+
if ($submissionFolder !== null) {
245233
$submissionFolder->delete();
246234
}
247-
} catch (NotFoundException) {
248-
// Folder doesn't exist, do nothing
249235
} catch (\Throwable $e) {
250236
$this->logger->warning('Failed to delete submission folder: {error}', [
251237
'error' => $e->getMessage(),

lib/Helper/FilePathHelper.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
namespace OCA\Forms\Helper;
9+
10+
use OCA\Forms\Constants;
11+
use OCA\Forms\Db\Form;
12+
use OCP\Files\Folder;
13+
use OCP\Files\IRootFolder;
14+
use OCP\Files\NotFoundException;
15+
16+
class FilePathHelper {
17+
public function __construct(
18+
private IRootFolder $rootFolder,
19+
) {
20+
}
21+
22+
/**
23+
* Normalize a filename by replacing invalid characters
24+
*/
25+
public function normalizeFileName(string $fileName): string {
26+
return trim(str_replace(Constants::FILENAME_INVALID_CHARS, '-', $fileName));
27+
}
28+
29+
/**
30+
* Get the folder path for uploaded files of a form
31+
*/
32+
public function getFormUploadedFilesFolderPath(Form $form): string {
33+
return implode('/', [
34+
Constants::FILES_FOLDER,
35+
$this->normalizeFileName($form->getId() . ' - ' . $form->getTitle()),
36+
]);
37+
}
38+
39+
/**
40+
* Get the full file path for a specific uploaded file
41+
*/
42+
public function getUploadedFilePath(Form $form, int $submissionId, int $questionId, ?string $questionName, string $questionText): string {
43+
return implode('/', [
44+
$this->getFormUploadedFilesFolderPath($form),
45+
$submissionId,
46+
$this->normalizeFileName($questionId . ' - ' . ($questionName ?: $questionText))
47+
]);
48+
}
49+
50+
/**
51+
* Get all form folders matching the form ID prefix
52+
* Useful for cleanup operations when form may have been renamed or deleted
53+
* @param int $formId The form ID
54+
* @param string $ownerId The owner user ID
55+
* @return Folder[]
56+
*/
57+
public function getAllFormFoldersById(int $formId, string $ownerId): array {
58+
$formsFolder = $this->getFormsFolder($ownerId);
59+
if ($formsFolder === null) {
60+
return [];
61+
}
62+
63+
$formFolderPrefix = $formId . ' - ';
64+
$matchingFolders = [];
65+
66+
// Collect all folders matching the form ID prefix
67+
foreach ($formsFolder->getDirectoryListing() as $node) {
68+
if (str_starts_with($node->getName(), $formFolderPrefix) && $node instanceof Folder) {
69+
$matchingFolders[] = $node;
70+
}
71+
}
72+
73+
return $matchingFolders;
74+
}
75+
76+
/**
77+
* Get the forms folder for a user
78+
*/
79+
public function getFormsFolder(string $ownerId): ?Folder {
80+
try {
81+
$userFolder = $this->rootFolder->getUserFolder($ownerId);
82+
$formsFolder = $userFolder->get(Constants::FILES_FOLDER);
83+
84+
if (!$formsFolder instanceof Folder) {
85+
return null;
86+
}
87+
88+
return $formsFolder;
89+
} catch (NotFoundException) {
90+
return null;
91+
}
92+
}
93+
94+
/**
95+
* Get the submission folder for a specific submission
96+
* Searches across all form folders to handle form renames
97+
*/
98+
public function getSubmissionFolder(Form $form, int $submissionId): ?Folder {
99+
$formFolders = $this->getAllFormFoldersById($form->getId(), $form->getOwnerId());
100+
101+
// Search for submission folder in all matching form folders
102+
foreach ($formFolders as $formFolder) {
103+
try {
104+
$submissionFolder = $formFolder->get((string)$submissionId);
105+
if ($submissionFolder instanceof Folder) {
106+
return $submissionFolder;
107+
}
108+
} catch (NotFoundException) {
109+
// Continue to next form folder
110+
}
111+
}
112+
113+
return null;
114+
}
115+
}

0 commit comments

Comments
 (0)