diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php
index f832705bc..0cbe6a73e 100644
--- a/lib/Controller/ApiController.php
+++ b/lib/Controller/ApiController.php
@@ -1375,6 +1375,12 @@ public function newSubmission(int $formId, array $answers, string $shareHash = '
throw new OCSForbiddenException('Already submitted');
}
+ // Check if max submissions limit is reached
+ $maxSubmissions = $form->getMaxSubmissions();
+ if ($maxSubmissions > 0 && $this->submissionMapper->countSubmissions($formId) >= $maxSubmissions) {
+ throw new OCSForbiddenException('Maximum number of submissions reached');
+ }
+
// Insert new submission
$this->submissionMapper->insert($submission);
diff --git a/lib/Db/Form.php b/lib/Db/Form.php
index fe1637eda..d3c9999df 100644
--- a/lib/Db/Form.php
+++ b/lib/Db/Form.php
@@ -50,6 +50,8 @@
* @method string getLockedBy()
* @method void setLockedBy(string|null $value)
* @method int getLockedUntil()
+ * @method int|null getMaxSubmissions()
+ * @method void setMaxSubmissions(int|null $value)
* @method void setLockedUntil(int|null $value)
*/
class Form extends Entity {
@@ -71,6 +73,7 @@ class Form extends Entity {
protected $state;
protected $lockedBy;
protected $lockedUntil;
+ protected $maxSubmissions;
/**
* Form constructor.
@@ -86,6 +89,7 @@ public function __construct() {
$this->addType('state', 'integer');
$this->addType('lockedBy', 'string');
$this->addType('lockedUntil', 'integer');
+ $this->addType('maxSubmissions', 'integer');
}
// JSON-Decoding of access-column.
@@ -159,6 +163,7 @@ public function setAccess(array $access): void {
* state: 0|1|2,
* lockedBy: ?string,
* lockedUntil: ?int,
+ * maxSubmissions: ?int,
* }
*/
public function read() {
@@ -182,6 +187,7 @@ public function read() {
'state' => $this->getState(),
'lockedBy' => $this->getLockedBy(),
'lockedUntil' => $this->getLockedUntil(),
+ 'maxSubmissions' => $this->getMaxSubmissions(),
];
}
}
diff --git a/lib/Migration/Version050300Date20260303000000.php b/lib/Migration/Version050300Date20260303000000.php
new file mode 100644
index 000000000..7cb8c076c
--- /dev/null
+++ b/lib/Migration/Version050300Date20260303000000.php
@@ -0,0 +1,41 @@
+getTable('forms_v2_forms');
+
+ if (!$table->hasColumn('max_submissions')) {
+ $table->addColumn('max_submissions', Types::INTEGER, [
+ 'notnull' => false,
+ 'default' => null,
+ 'comment' => 'Maximum number of submissions, null means unlimited',
+ ]);
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/Service/FormsService.php b/lib/Service/FormsService.php
index 5a7492329..e2ea51ec9 100644
--- a/lib/Service/FormsService.php
+++ b/lib/Service/FormsService.php
@@ -204,6 +204,10 @@ public function getForm(Form $form): array {
$result['permissions'] = $this->getPermissions($form);
// Append canSubmit, to be able to show proper EmptyContent on internal view.
$result['canSubmit'] = $this->canSubmit($form);
+ // Append isMaxSubmissionsReached to show proper message on submit view.
+ $maxSubmissions = $form->getMaxSubmissions();
+ $result['isMaxSubmissionsReached'] = $maxSubmissions !== null
+ && $this->submissionMapper->countSubmissions($form->getId()) >= $maxSubmissions;
// Append submissionCount if currentUser has permissions to see results
if (in_array(Constants::PERMISSION_RESULTS, $result['permissions'])) {
@@ -484,6 +488,12 @@ public function canDeleteResults(Form $form): bool {
* @return boolean
*/
public function canSubmit(Form $form): bool {
+ // Check if max submissions limit is reached
+ $maxSubmissions = $form->getMaxSubmissions();
+ if ($maxSubmissions !== null && $this->submissionMapper->countSubmissions($form->getId()) >= $maxSubmissions) {
+ return false;
+ }
+
// We cannot control how many time users can submit if public link available
if ($this->hasPublicLink($form)) {
return true;
diff --git a/src/components/SidebarTabs/SettingsSidebarTab.vue b/src/components/SidebarTabs/SettingsSidebarTab.vue
index 15bcea0d6..aa2a2d920 100644
--- a/src/components/SidebarTabs/SettingsSidebarTab.vue
+++ b/src/components/SidebarTabs/SettingsSidebarTab.vue
@@ -78,6 +78,25 @@
{{ t('forms', 'Show expiration date on form') }}
+
+ {{ t('forms', 'Form will be closed automatically when the limit is reached.') }} +
+