Skip to content

Commit b4f7da2

Browse files
committed
feat: add support for comments in forms
Signed-off-by: Christian Hartmann <chris-hartmann@gmx.de>
1 parent 3da5124 commit b4f7da2

24 files changed

Lines changed: 373 additions & 22 deletions

lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
use OCA\Forms\Capabilities;
1414
use OCA\Forms\FormsMigrator;
1515
use OCA\Forms\Listener\AnalyticsDatasourceListener;
16+
use OCA\Forms\Listener\CommentsEntityListener;
1617
use OCA\Forms\Listener\UserDeletedListener;
1718
use OCA\Forms\Middleware\ThrottleFormAccessMiddleware;
1819
use OCA\Forms\Search\SearchProvider;
1920
use OCP\AppFramework\App;
2021
use OCP\AppFramework\Bootstrap\IBootContext;
2122
use OCP\AppFramework\Bootstrap\IBootstrap;
2223
use OCP\AppFramework\Bootstrap\IRegistrationContext;
24+
use OCP\Comments\CommentsEntityEvent;
2325
use OCP\User\Events\UserDeletedEvent;
2426

2527
class Application extends App implements IBootstrap {
@@ -44,6 +46,7 @@ public function register(IRegistrationContext $context): void {
4446
$context->registerCapability(Capabilities::class);
4547
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
4648
$context->registerEventListener(DatasourceEvent::class, AnalyticsDatasourceListener::class);
49+
$context->registerEventListener(CommentsEntityEvent::class, CommentsEntityListener::class);
4750
$context->registerMiddleware(ThrottleFormAccessMiddleware::class);
4851
$context->registerSearchProvider(SearchProvider::class);
4952
$context->registerUserMigrator(FormsMigrator::class);

lib/Constants.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class Constants {
2020
public const CONFIG_KEY_RESTRICTCREATION = 'restrictCreation';
2121
public const CONFIG_KEY_ALLOWCONFIRMATIONEMAIL = 'allowConfirmationEmail';
2222
public const CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT = 'confirmationEmailRateLimit';
23+
public const CONFIG_KEY_ALLOWCOMMENTS = 'allowComments';
2324
public const CONFIG_KEYS = [
2425
self::CONFIG_KEY_ALLOWPERMITALL,
2526
self::CONFIG_KEY_ALLOWPUBLICLINK,
@@ -28,6 +29,7 @@ class Constants {
2829
self::CONFIG_KEY_RESTRICTCREATION,
2930
self::CONFIG_KEY_ALLOWCONFIRMATIONEMAIL,
3031
self::CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT,
32+
self::CONFIG_KEY_ALLOWCOMMENTS,
3133
];
3234
public const CONFIG_KEY_TYPES = [
3335
self::CONFIG_KEY_ALLOWPERMITALL => 'bool',

lib/Controller/PageController.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
3030
use OCP\AppFramework\Http\TemplateResponse;
3131
use OCP\AppFramework\Services\IInitialState;
32+
use OCP\Comments\ICommentsManager;
3233
use OCP\IL10N;
3334
use OCP\IRequest;
3435
use OCP\IURLGenerator;
@@ -51,6 +52,7 @@ public function __construct(
5152
private FormsService $formsService,
5253
private IAccountManager $accountManager,
5354
private IInitialState $initialState,
55+
private ICommentsManager $commentsManager,
5456
private IL10N $l10n,
5557
private IUrlGenerator $urlGenerator,
5658
private IUserManager $userManager,
@@ -66,7 +68,9 @@ public function __construct(
6668
#[NoCSRFRequired()]
6769
#[FrontpageRoute(verb: 'GET', url: '/')]
6870
public function index(?string $hash = null, ?int $submissionId = null): TemplateResponse {
69-
Util::addScript($this->appName, 'forms-main');
71+
// Ensure the Comments client is available and load comments resources
72+
$this->commentsManager->load();
73+
Util::addScript($this->appName, 'forms-main', 'comments');
7074
Util::addStyle($this->appName, 'forms');
7175
Util::addStyle($this->appName, 'forms-style');
7276
$this->insertHeaderOnIos();

lib/Db/Form.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161
* @method void setConfirmationEmailBody(string|null $value)
6262
* @method int|null getConfirmationEmailQuestionId()
6363
* @method void setConfirmationEmailQuestionId(int|null $value)
64+
* @method bool getAllowComments()
65+
* @method void setAllowComments(bool $value)
6466
*/
6567
class Form extends Entity {
6668
protected $hash;
@@ -86,6 +88,7 @@ class Form extends Entity {
8688
protected $confirmationEmailSubject;
8789
protected $confirmationEmailBody;
8890
protected $confirmationEmailQuestionId;
91+
protected $allowComments;
8992

9093
/**
9194
* Form constructor.
@@ -104,6 +107,7 @@ public function __construct() {
104107
$this->addType('maxSubmissions', 'integer');
105108
$this->addType('confirmationEmailEnabled', 'boolean');
106109
$this->addType('confirmationEmailQuestionId', 'integer');
110+
$this->addType('allowComments', 'boolean');
107111
}
108112

109113
// JSON-Decoding of access-column.
@@ -182,6 +186,7 @@ public function setAccess(array $access): void {
182186
* confirmationEmailSubject: ?string,
183187
* confirmationEmailBody: ?string,
184188
* confirmationEmailQuestionId: ?int,
189+
* allowComments: bool,
185190
* }
186191
*/
187192
public function read() {
@@ -210,6 +215,7 @@ public function read() {
210215
'confirmationEmailSubject' => $this->getConfirmationEmailSubject(),
211216
'confirmationEmailBody' => $this->getConfirmationEmailBody(),
212217
'confirmationEmailQuestionId' => $this->getConfirmationEmailQuestionId(),
218+
'allowComments' => $this->getAllowComments(),
213219
];
214220
}
215221
}

lib/Db/FormMapper.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use OCA\Forms\Service\ConfigService;
1212
use OCP\AppFramework\Db\Entity;
1313
use OCP\AppFramework\Db\QBMapper;
14+
use OCP\Comments\ICommentsManager;
1415
use OCP\DB\QueryBuilder\IQueryBuilder;
1516
use OCP\IDBConnection;
1617
use OCP\Share\IShare;
@@ -30,6 +31,7 @@ public function __construct(
3031
private ShareMapper $shareMapper,
3132
private SubmissionMapper $submissionMapper,
3233
private ConfigService $configService,
34+
private ICommentsManager $commentsManager,
3335
) {
3436
parent::__construct($db, 'forms_v2_forms', Form::class);
3537
}
@@ -218,9 +220,11 @@ public function findAllByOwnerId(string $ownerId, ?string $queryTerm = null): ar
218220
*/
219221
public function deleteForm(Form $form): void {
220222
// Delete Submissions(incl. Answers), Questions(incl. Options), Shares and Form.
221-
$this->submissionMapper->deleteByForm($form->getId());
222-
$this->shareMapper->deleteByForm($form->getId());
223-
$this->questionMapper->deleteByForm($form->getId());
223+
$formId = $form->getId();
224+
$this->submissionMapper->deleteByForm($formId);
225+
$this->shareMapper->deleteByForm($formId);
226+
$this->questionMapper->deleteByForm($formId);
227+
$this->commentsManager->deleteCommentsAtObject('forms', (string)$formId);
224228
$this->delete($form);
225229
}
226230
}

lib/FormsMigrator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ public function import(IUser $user, IImportSource $importSource, OutputInterface
153153
$form->setConfirmationEmailSubject($formData['confirmationEmailSubject'] ?? null);
154154
$form->setConfirmationEmailBody($formData['confirmationEmailBody'] ?? null);
155155
$form->setConfirmationEmailQuestionId(null); // Set to null initially, updated after questions are imported
156+
$form->setAllowComments($formData['allowComments'] ?? false);
156157

157158
$this->formMapper->insert($form);
158159

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Forms\Listener;
10+
11+
use OCA\Forms\Db\FormMapper;
12+
use OCA\Forms\Service\FormsService;
13+
use OCP\AppFramework\Db\DoesNotExistException;
14+
use OCP\Comments\CommentsEntityEvent;
15+
use OCP\EventDispatcher\Event;
16+
use OCP\EventDispatcher\IEventListener;
17+
18+
/**
19+
* @template-implements IEventListener<Event>
20+
*/
21+
class CommentsEntityListener implements IEventListener {
22+
public function __construct(
23+
protected FormMapper $formMapper,
24+
protected FormsService $formService,
25+
) {
26+
}
27+
28+
#[\Override]
29+
public function handle(Event $event): void {
30+
if (!$event instanceof CommentsEntityEvent) {
31+
return;
32+
}
33+
34+
// Register the 'forms' entity collection so the Comments app can
35+
// check whether a given form id allows comments.
36+
$event->addEntityCollection('forms', function ($formId) {
37+
try {
38+
$form = $this->formMapper->findById((int)$formId);
39+
} catch (DoesNotExistException $e) {
40+
return false;
41+
}
42+
return $this->formService->hasUserAccess($form) && $form->getAllowComments();
43+
});
44+
}
45+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Forms\Migration;
11+
12+
use Closure;
13+
use OCP\DB\ISchemaWrapper;
14+
use OCP\DB\Types;
15+
use OCP\Migration\IOutput;
16+
use OCP\Migration\SimpleMigrationStep;
17+
use Override;
18+
19+
class Version050300Date20260511121033 extends SimpleMigrationStep {
20+
21+
/**
22+
* @param IOutput $output
23+
* @param Closure(): ISchemaWrapper $schemaClosure
24+
* @param array $options
25+
* @return null|ISchemaWrapper
26+
*/
27+
#[Override]
28+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
29+
/** @var ISchemaWrapper $schema */
30+
$schema = $schemaClosure();
31+
$table = $schema->getTable('forms_v2_forms');
32+
$changed = false;
33+
34+
if (!$table->hasColumn('allow_comments')) {
35+
$table->addColumn('allow_comments', Types::BOOLEAN, [
36+
'notnull' => false,
37+
'default' => 0,
38+
]);
39+
$changed = true;
40+
}
41+
42+
return $changed ? $schema : null;
43+
}
44+
}

lib/ResponseDefinitions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
* confirmationEmailSubject: ?string,
147147
* confirmationEmailBody: ?string,
148148
* confirmationEmailQuestionId: ?int,
149+
* allowComments: bool,
149150
* }
150151
*
151152
* @psalm-type FormsUploadedFile = array{

lib/Service/ConfigService.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public function getConfirmationEmailRateLimit(): int {
6464
return $this->appConfig->getAppValueInt(Constants::CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT, 3);
6565
}
6666

67+
public function getAllowComments(): bool {
68+
return $this->appConfig->getAppValueBool(Constants::CONFIG_KEY_ALLOWCOMMENTS, false);
69+
}
70+
6771
/**
6872
* Provide the full AppConfig
6973
*/
@@ -77,6 +81,7 @@ public function getAppConfig(): array {
7781
Constants::CONFIG_KEY_ALLOWCONFIRMATIONEMAIL => $this->getAllowConfirmationEmail(),
7882
Constants::CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT => $this->getConfirmationEmailRateLimit(),
7983
'isMailConfigured' => $this->isMailConfigured(),
84+
Constants::CONFIG_KEY_ALLOWCOMMENTS => $this->getAllowComments(),
8085

8186
// Additional, calculated information out of Config
8287
'canCreateForms' => $this->canCreateForms()

0 commit comments

Comments
 (0)