Skip to content

Commit 367d0fc

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

10 files changed

Lines changed: 198 additions & 115 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: 6 additions & 6 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;
@@ -29,7 +30,6 @@
2930
use OCP\AppFramework\OCS\OCSForbiddenException;
3031
use OCP\AppFramework\OCS\OCSNotFoundException;
3132
use OCP\AppFramework\OCSController;
32-
use OCP\Files\IRootFolder;
3333
use OCP\Files\NotFoundException;
3434
use OCP\IGroup;
3535
use OCP\IGroupManager;
@@ -61,7 +61,7 @@ public function __construct(
6161
private IUserManager $userManager,
6262
private ISecureRandom $secureRandom,
6363
private CirclesService $circlesService,
64-
private IRootFolder $rootFolder,
64+
private FilePathHelper $filePathHelper,
6565
private IManager $shareManager,
6666
) {
6767
parent::__construct($appName, $request);
@@ -272,8 +272,8 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da
272272

273273
if (in_array($formShare->getShareType(), [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_USERGROUP, IShare::TYPE_CIRCLE], true)) {
274274
if (in_array(Constants::PERMISSION_RESULTS, $keyValuePairs['permissions'], true)) {
275-
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
276-
$uploadedFilesFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
275+
$userFolder = $this->filePathHelper->rootFolder->getUserFolder($form->getOwnerId());
276+
$uploadedFilesFolderPath = $this->filePathHelper->getFormUploadedFilesFolderPath($form);
277277
try {
278278
/** @var \OCP\Files\Folder $folder */
279279
$folder = $userFolder->get($uploadedFilesFolderPath);
@@ -357,8 +357,8 @@ private function removeUploadedFilesShare(Form $form, Share $formShare): void {
357357
return;
358358
}
359359

360-
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
361-
$uploadedFilesFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
360+
$userFolder = $this->filePathHelper->rootFolder->getUserFolder($form->getOwnerId());
361+
$uploadedFilesFolderPath = $this->filePathHelper->getFormUploadedFilesFolderPath($form);
362362
try {
363363
$folder = $userFolder->get($uploadedFilesFolderPath);
364364
} 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: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
public 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 the form folder for a given form
52+
* Searches by prefix to handle form renames
53+
* Returns the first matching folder (most recent)
54+
*/
55+
public function getFormFolder(Form $form): ?Folder {
56+
$formsFolder = $this->getFormsFolder($form->getOwnerId());
57+
if ($formsFolder === null) {
58+
return null;
59+
}
60+
61+
$formFolderPrefix = $form->getId() . ' - ';
62+
63+
// Search for folder matching the form ID prefix
64+
foreach ($formsFolder->getDirectoryListing() as $node) {
65+
if (str_starts_with($node->getName(), $formFolderPrefix) && $node instanceof Folder) {
66+
return $node;
67+
}
68+
}
69+
70+
return null;
71+
}
72+
73+
/**
74+
* Get all form folders matching the form ID prefix
75+
* Useful for cleanup operations when form may have been renamed or deleted
76+
* @param int $formId The form ID
77+
* @param string $ownerId The owner user ID
78+
* @return Folder[]
79+
*/
80+
public function getAllFormFoldersById(int $formId, string $ownerId): array {
81+
$formsFolder = $this->getFormsFolder($ownerId);
82+
if ($formsFolder === null) {
83+
return [];
84+
}
85+
86+
$formFolderPrefix = $formId . ' - ';
87+
$matchingFolders = [];
88+
89+
// Collect all folders matching the form ID prefix
90+
foreach ($formsFolder->getDirectoryListing() as $node) {
91+
if (str_starts_with($node->getName(), $formFolderPrefix) && $node instanceof Folder) {
92+
$matchingFolders[] = $node;
93+
}
94+
}
95+
96+
return $matchingFolders;
97+
}
98+
99+
/**
100+
* Get the forms folder for a user
101+
*/
102+
public function getFormsFolder(string $ownerId): ?Folder {
103+
try {
104+
$userFolder = $this->rootFolder->getUserFolder($ownerId);
105+
$formsFolder = $userFolder->get(Constants::FILES_FOLDER);
106+
107+
if (!$formsFolder instanceof Folder) {
108+
return null;
109+
}
110+
111+
return $formsFolder;
112+
} catch (NotFoundException) {
113+
return null;
114+
}
115+
}
116+
117+
/**
118+
* Get the submission folder for a specific submission
119+
* Searches across all form folders to handle form renames
120+
*/
121+
public function getSubmissionFolder(Form $form, int $submissionId): ?Folder {
122+
$formFolders = $this->getAllFormFoldersById($form->getId(), $form->getOwnerId());
123+
124+
// Search for submission folder in all matching form folders
125+
foreach ($formFolders as $formFolder) {
126+
try {
127+
$submissionFolder = $formFolder->get((string)$submissionId);
128+
if ($submissionFolder instanceof Folder) {
129+
return $submissionFolder;
130+
}
131+
} catch (NotFoundException) {
132+
// Continue to next form folder
133+
}
134+
}
135+
136+
return null;
137+
}
138+
}

0 commit comments

Comments
 (0)