Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
use OCA\Forms\Capabilities;
use OCA\Forms\FormsMigrator;
use OCA\Forms\Listener\AnalyticsDatasourceListener;
use OCA\Forms\Listener\CommentsEntityListener;
use OCA\Forms\Listener\UserDeletedListener;
use OCA\Forms\Middleware\ThrottleFormAccessMiddleware;
use OCA\Forms\Search\SearchProvider;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Comments\CommentsEntityEvent;
use OCP\User\Events\UserDeletedEvent;

class Application extends App implements IBootstrap {
Expand All @@ -44,6 +46,7 @@ public function register(IRegistrationContext $context): void {
$context->registerCapability(Capabilities::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(DatasourceEvent::class, AnalyticsDatasourceListener::class);
$context->registerEventListener(CommentsEntityEvent::class, CommentsEntityListener::class);
$context->registerMiddleware(ThrottleFormAccessMiddleware::class);
$context->registerSearchProvider(SearchProvider::class);
$context->registerUserMigrator(FormsMigrator::class);
Expand Down
2 changes: 2 additions & 0 deletions lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Constants {
public const CONFIG_KEY_RESTRICTCREATION = 'restrictCreation';
public const CONFIG_KEY_ALLOWCONFIRMATIONEMAIL = 'allowConfirmationEmail';
public const CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT = 'confirmationEmailRateLimit';
public const CONFIG_KEY_ALLOWCOMMENTS = 'allowComments';
public const CONFIG_KEYS = [
self::CONFIG_KEY_ALLOWPERMITALL,
self::CONFIG_KEY_ALLOWPUBLICLINK,
Expand All @@ -28,6 +29,7 @@ class Constants {
self::CONFIG_KEY_RESTRICTCREATION,
self::CONFIG_KEY_ALLOWCONFIRMATIONEMAIL,
self::CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT,
self::CONFIG_KEY_ALLOWCOMMENTS,
];

/**
Expand Down
6 changes: 5 additions & 1 deletion lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\Comments\ICommentsManager;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
Expand All @@ -51,6 +52,7 @@ public function __construct(
private FormsService $formsService,
private IAccountManager $accountManager,
private IInitialState $initialState,
private ICommentsManager $commentsManager,
private IL10N $l10n,
private IUrlGenerator $urlGenerator,
private IUserManager $userManager,
Expand All @@ -66,7 +68,9 @@ public function __construct(
#[NoCSRFRequired()]
#[FrontpageRoute(verb: 'GET', url: '/')]
public function index(?string $hash = null, ?int $submissionId = null): TemplateResponse {
Util::addScript($this->appName, 'forms-main');
// Ensure the Comments client is available and load comments resources
$this->commentsManager->load();
Util::addScript($this->appName, 'forms-main', 'comments');
Util::addStyle($this->appName, 'forms');
Util::addStyle($this->appName, 'forms-style');
$this->insertHeaderOnIos();
Expand Down
6 changes: 6 additions & 0 deletions lib/Db/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
* @method void setConfirmationEmailBody(string|null $value)
* @method int|null getConfirmationEmailQuestionId()
* @method void setConfirmationEmailQuestionId(int|null $value)
* @method bool getAllowComments()
* @method void setAllowComments(bool $value)
*/
class Form extends Entity {
protected $hash;
Expand All @@ -86,6 +88,7 @@ class Form extends Entity {
protected $confirmationEmailSubject;
protected $confirmationEmailBody;
protected $confirmationEmailQuestionId;
protected $allowComments;

/**
* Form constructor.
Expand All @@ -104,6 +107,7 @@ public function __construct() {
$this->addType('maxSubmissions', 'integer');
$this->addType('confirmationEmailEnabled', 'boolean');
$this->addType('confirmationEmailQuestionId', 'integer');
$this->addType('allowComments', 'boolean');
}

// JSON-Decoding of access-column.
Expand Down Expand Up @@ -182,6 +186,7 @@ public function setAccess(array $access): void {
* confirmationEmailSubject: ?string,
* confirmationEmailBody: ?string,
* confirmationEmailQuestionId: ?int,
* allowComments: bool,
* }
*/
public function read() {
Expand Down Expand Up @@ -210,6 +215,7 @@ public function read() {
'confirmationEmailSubject' => $this->getConfirmationEmailSubject(),
'confirmationEmailBody' => $this->getConfirmationEmailBody(),
'confirmationEmailQuestionId' => $this->getConfirmationEmailQuestionId(),
'allowComments' => $this->getAllowComments(),
];
}
}
1 change: 1 addition & 0 deletions lib/FormsMigrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public function import(IUser $user, IImportSource $importSource, OutputInterface
$form->setConfirmationEmailSubject($formData['confirmationEmailSubject'] ?? null);
$form->setConfirmationEmailBody($formData['confirmationEmailBody'] ?? null);
$form->setConfirmationEmailQuestionId(null); // Set to null initially, updated after questions are imported
$form->setAllowComments($formData['allowComments'] ?? false);

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

Expand Down
43 changes: 43 additions & 0 deletions lib/Listener/CommentsEntityListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Forms\Listener;

use OCA\Forms\Db\FormMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Comments\CommentsEntityEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;

/**
* @template-implements IEventListener<Event>
*/
class CommentsEntityListener implements IEventListener {
public function __construct(
protected FormMapper $formMapper,
) {
}

#[\Override]
public function handle(Event $event): void {
if (!$event instanceof CommentsEntityEvent) {
return;
}

// Register the 'forms' entity collection so the Comments app can
// check whether a given form id allows comments.
$event->addEntityCollection('forms', function ($formId) {
try {
$form = $this->formMapper->findById((int)$formId);
} catch (DoesNotExistException $e) {
return false;
}
return (bool)$form->getAllowComments();
});
}
}
44 changes: 44 additions & 0 deletions lib/Migration/Version050300Date20260511121033.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Forms\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Override;

class Version050300Date20260511121033 extends SimpleMigrationStep {

/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
#[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('forms_v2_forms');
$changed = false;

if (!$table->hasColumn('allow_comments')) {
$table->addColumn('allow_comments', Types::BOOLEAN, [
'notnull' => false,
'default' => 0,
]);
$changed = true;
}

return $changed ? $schema : null;
}
}
1 change: 1 addition & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
* confirmationEmailSubject: ?string,
* confirmationEmailBody: ?string,
* confirmationEmailQuestionId: ?int,
* allowComments: bool,
* }
*
* @psalm-type FormsUploadedFile = array{
Expand Down
5 changes: 5 additions & 0 deletions lib/Service/ConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public function getConfirmationEmailRateLimit(): int {
return $this->appConfig->getAppValueInt(Constants::CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT, 3);
}

public function getAllowComments(): bool {
return $this->appConfig->getAppValueBool(Constants::CONFIG_KEY_ALLOWCOMMENTS, false);
}

/**
* Provide the full AppConfig
*/
Expand All @@ -77,6 +81,7 @@ public function getAppConfig(): array {
Constants::CONFIG_KEY_ALLOWCONFIRMATIONEMAIL => $this->getAllowConfirmationEmail(),
Constants::CONFIG_KEY_CONFIRMATIONEMAILRATELIMIT => $this->getConfirmationEmailRateLimit(),
'isMailConfigured' => $this->isMailConfigured(),
Constants::CONFIG_KEY_ALLOWCOMMENTS => $this->getAllowComments(),

// Additional, calculated information out of Config
'canCreateForms' => $this->canCreateForms()
Expand Down
6 changes: 5 additions & 1 deletion openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@
"confirmationEmailEnabled",
"confirmationEmailSubject",
"confirmationEmailBody",
"confirmationEmailQuestionId"
"confirmationEmailQuestionId",
"allowComments"
],
"properties": {
"id": {
Expand Down Expand Up @@ -252,6 +253,9 @@
"type": "integer",
"format": "int64",
"nullable": true
},
"allowComments": {
"type": "boolean"
}
}
},
Expand Down
7 changes: 6 additions & 1 deletion src/Forms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@
@update:sidebarOpened="sidebarOpened = $event"
@openSharing="openSharing" />
<Sidebar
v-if="!selectedForm.partial && canEdit"
v-if="
!selectedForm.partial
&& (canEdit || (allowComments && selectedForm.allowComments))
"
:form="selectedForm"
:sidebarOpened="sidebarOpened"
:active="sidebarActive"
Expand Down Expand Up @@ -206,6 +209,7 @@ export default {
const allSharedForms = ref([])
const showArchivedForms = ref(false)
const canCreateForms = ref(loadState(appName, 'appConfig').canCreateForms)
const allowComments = ref(loadState(appName, 'appConfig').allowComments)

const PERMISSION_TYPES = PermissionTypes.data().PERMISSION_TYPES

Expand Down Expand Up @@ -475,6 +479,7 @@ export default {
allSharedForms,
showArchivedForms,
canCreateForms,
allowComments,
isMobile,
selectedForm,
updateSelectedForm,
Expand Down
15 changes: 15 additions & 0 deletions src/FormsSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@
}}
</NcCheckboxRadioSwitch>
</NcSettingsSection>
<NcSettingsSection :name="t('forms', 'Comments')">
<NcCheckboxRadioSwitch
v-model="appConfig.allowComments"
:loading="loading.allowComments"
type="switch"
@update:modelValue="onAllowCommentsChange">
{{ t('forms', 'Allow comments') }}
</NcCheckboxRadioSwitch>
</NcSettingsSection>
</div>
</template>

Expand Down Expand Up @@ -192,6 +201,12 @@ export default {
await this.saveAppConfig('confirmationEmailRateLimit', value)
},

async onAllowCommentsChange(newVal) {
this.loading.allowComments = true
await this.saveAppConfig('allowComments', newVal)
this.loading.allowComments = false
},

/**
* Save a key-value pair to the appConfig.
*
Expand Down
12 changes: 12 additions & 0 deletions src/components/SidebarTabs/SettingsSidebarTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@
@update:modelValue="onAllowEditSubmissionsChange">
{{ t('forms', 'Allow editing own responses') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
v-if="appConfig.allowComments"
:modelValue="form.allowComments"
:disabled="formArchived || locked"
type="switch"
@update:modelValue="onAllowCommentsChange">
{{ t('forms', 'Allow comments') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
:modelValue="formExpires"
:disabled="formArchived || locked"
Expand Down Expand Up @@ -602,6 +610,10 @@ export default {
this.$emit('update:formProp', 'allowEditSubmissions', checked)
},

onAllowCommentsChange(checked) {
this.$emit('update:formProp', 'allowComments', checked)
},

onFormExpiresChange(checked) {
if (checked) {
this.$emit(
Expand Down
Loading
Loading