Skip to content

Commit

Permalink
Merge pull request #1925 from nextcloud/enh/allow-archive-forms
Browse files Browse the repository at this point in the history
feat: Add forms `state` to close and archive forms
  • Loading branch information
Chartman123 authored Mar 22, 2024
2 parents 0e47c54 + 8660fce commit 7d6ce87
Show file tree
Hide file tree
Showing 21 changed files with 839 additions and 269 deletions.
10 changes: 7 additions & 3 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ Returns condensed objects of all Forms beeing owned by the authenticated user.
"results",
"submit"
],
"partial": true
"partial": true,
"state": 0
},
{
"id": 3,
Expand All @@ -70,7 +71,8 @@ Returns condensed objects of all Forms beeing owned by the authenticated user.
"results",
"submit"
],
"partial": true
"partial": true,
"state": 0
}
]
```
Expand Down Expand Up @@ -103,7 +105,8 @@ Returns a single partial form object, corresponding to owned/shared form-listing
"permissions": [
"submit"
],
"partial": true
"partial": true,
"state": 0
}
```

Expand Down Expand Up @@ -146,6 +149,7 @@ Returns the full-depth object of the requested form (without submissions).
"submitMultiple": true,
"showExpiration": false,
"canSubmit": true,
"state": 0,
"permissions": [
"edit",
"results",
Expand Down
13 changes: 12 additions & 1 deletion docs/DataStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This document describes the Object-Structure, that is used within the Forms App
| access | [Access-Object](#access-object) | | Describing access-settings of the form |
| expires | unix-timestamp | | When the form should expire. Timestamp `0` indicates _never_ |
| isAnonymous | Boolean | | If Answers will be stored anonymously |
| state | Integer | [Form state](#form-state)| The state of the form |
| submitMultiple | Boolean | | If users are allowed to submit multiple times to the form |
| showExpiration | Boolean | | If the expiration date will be shown on the form |
| canSubmit | Boolean | | If the user can Submit to the form, i.e. calculated information out of `submitMultiple` and existing submissions. |
Expand Down Expand Up @@ -45,11 +46,21 @@ This document describes the Object-Structure, that is used within the Forms App
"submit"
],
"questions": [],
"submissions": [],
"state": 0,
"shares": []
"submissions": [],
}
```

#### Form state
The form state is used for additional states, currently following states are defined:

| Value | Meaning |
|----------------|---------------------------------------------|
| 0 | Form is active and open for new submissions |
| 1 | Form is closed and does not allow new submissions |
| 2 | Form is archived, it does not allow new submissions and can also not be modified anymore |

### Question
| Property | Type | Restrictions | Description |
|----------------|-----------------|--------------|-------------|
Expand Down
7 changes: 7 additions & 0 deletions lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ class Constants {
'answerText' => 4096,
];

/**
* State flags of a form
*/
public const FORM_STATE_ACTIVE = 0;
public const FORM_STATE_CLOSED = 1;
public const FORM_STATE_ARCHIVED = 2;

/**
* !! Keep in sync with src/models/AnswerTypes.js !!
*/
Expand Down
46 changes: 44 additions & 2 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
*
* @author affan98 <affan98@gmail.com>
* @author Ferdinand Thiessen <opensource@fthiessen.de>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
* @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
Expand Down Expand Up @@ -471,6 +472,11 @@ public function newQuestion(int $formId, string $type, string $text = ''): DataR
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

// Retrieve all active questions sorted by Order. Takes the order of the last array-element and adds one.
$questions = $this->questionMapper->findByForm($formId);
$lastQuestion = array_pop($questions);
Expand Down Expand Up @@ -530,6 +536,11 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse {
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

// Check if array contains duplicates
if (array_unique($newOrder) !== $newOrder) {
$this->logger->debug('The given Array contains duplicates');
Expand Down Expand Up @@ -627,6 +638,11 @@ public function updateQuestion(int $id, array $keyValuePairs): DataResponse {
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

// Don't allow empty array
if (sizeof($keyValuePairs) === 0) {
$this->logger->info('Empty keyValuePairs, will not update.');
Expand Down Expand Up @@ -690,6 +706,11 @@ public function deleteQuestion(int $id): DataResponse {
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

// Store Order of deleted Question
$deletedOrder = $question->getOrder();

Expand Down Expand Up @@ -741,6 +762,11 @@ public function cloneQuestion(int $id): DataResponse {
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

$allQuestions = $this->questionMapper->findByForm($form->getId());

$questionData = $sourceQuestion->read();
Expand Down Expand Up @@ -797,6 +823,11 @@ public function newOption(int $questionId, string $text): DataResponse {
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

$option = new Option();

$option->setQuestionId($questionId);
Expand Down Expand Up @@ -841,6 +872,11 @@ public function updateOption(int $id, array $keyValuePairs): DataResponse {
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

// Don't allow empty array
if (sizeof($keyValuePairs) === 0) {
$this->logger->info('Empty keyValuePairs, will not update.');
Expand Down Expand Up @@ -895,6 +931,11 @@ public function deleteOption(int $id): DataResponse {
throw new OCSForbiddenException();
}

if ($this->formsService->isFormArchived($form)) {
$this->logger->debug('This form is archived and can not be modified');
throw new OCSForbiddenException();
}

$this->optionMapper->delete($option);

$this->formsService->setLastUpdatedTimestamp($form->getId());
Expand Down Expand Up @@ -1180,8 +1221,9 @@ public function deleteAllSubmissions(int $formId): DataResponse {
throw new OCSBadRequestException();
}

if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
// The current user has permissions to remove submissions
if (!$this->formsService->canDeleteResults($form)) {
$this->logger->debug('This form is not owned by the current user and user has no `results_delete` permission');
throw new OCSForbiddenException();
}

Expand Down
24 changes: 15 additions & 9 deletions lib/Db/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*
* @author affan98 <affan98@gmail.com>
* @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later
*
Expand Down Expand Up @@ -44,20 +45,22 @@
* @method void setFileFormat(string|null $value)
* @method array getAccess()
* @method void setAccess(array $value)
* @method integer getCreated()
* @method void setCreated(integer $value)
* @method integer getExpires()
* @method void setExpires(integer $value)
* @method integer getIsAnonymous()
* @method int getCreated()
* @method void setCreated(int $value)
* @method int getExpires()
* @method void setExpires(int $value)
* @method int getIsAnonymous()
* @method void setIsAnonymous(bool $value)
* @method integer getSubmitMultiple()
* @method int getSubmitMultiple()
* @method void setSubmitMultiple(bool $value)
* @method integer getShowExpiration()
* @method int getShowExpiration()
* @method void setShowExpiration(bool $value)
* @method integer getLastUpdated()
* @method void setLastUpdated(integer $value)
* @method int getLastUpdated()
* @method void setLastUpdated(int $value)
* @method ?string getSubmissionMessage()
* @method void setSubmissionMessage(?string $value)
* @method int getState()
* @method void setState(?int $value)
*/
class Form extends Entity {
protected $hash;
Expand All @@ -74,6 +77,7 @@ class Form extends Entity {
protected $showExpiration;
protected $submissionMessage;
protected $lastUpdated;
protected $state;

/**
* Form constructor.
Expand All @@ -85,6 +89,7 @@ public function __construct() {
$this->addType('submitMultiple', 'bool');
$this->addType('showExpiration', 'bool');
$this->addType('lastUpdated', 'integer');
$this->addType('state', 'integer');
}

// JSON-Decoding of access-column.
Expand Down Expand Up @@ -115,6 +120,7 @@ public function read() {
'showExpiration' => (bool)$this->getShowExpiration(),
'lastUpdated' => (int)$this->getLastUpdated(),
'submissionMessage' => $this->getSubmissionMessage(),
'state' => $this->getState(),
];
}
}
78 changes: 78 additions & 0 deletions lib/Migration/Version040200Date20240219201500.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
*
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Forms\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Types;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

/**
* This migration adds the `state` property to forms for closed, archived or active forms
*/
class Version040200Date20240219201500 extends SimpleMigrationStep {

public function __construct(
protected IDBConnection $db,
) {
}

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('forms_v2_forms');

if (!$table->hasColumn('state')) {
$table->addColumn('state', Types::SMALLINT, [
'notnull' => true,
'default' => 0,
'unsigned' => true,
]);
}

return $schema;
}

/**
* Set all old forms to active state
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
$query = $this->db->getQueryBuilder();
$query->update('forms_v2_forms')
->set('state', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
->executeStatement();
}
}
Loading

0 comments on commit 7d6ce87

Please sign in to comment.