Signed-off-by: Christian Hartmann <chris-hartmann@gmx.de>
This commit is contained in:
Christian Hartmann 2024-10-05 12:15:16 +02:00
Родитель cc5eadc534
Коммит 80fa593094
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 01CF79F7199D2C63
7 изменённых файлов: 6 добавлений и 4014 удалений

Просмотреть файл

@ -59,7 +59,7 @@ return [
// CORS Preflight
['name' => 'api#preflightedCors', 'url' => $apiBase . '{path}', 'verb' => 'OPTIONS', 'requirements' => [
'path' => '.+',
'apiVersion' => 'v2(\.[1-5])?|v3'
'apiVersion' => 'v3'
]],
// API routes v3
@ -103,138 +103,5 @@ return [
['name' => 'api#deleteSubmission', 'url' => $apiBase . 'forms/{formId}/submissions/{submissionId}', 'verb' => 'DELETE', 'requirements' => $requirements_v3],
['name' => 'api#exportSubmissionsToCloud', 'url' => $apiBase . 'forms/{formId}/submissions/export', 'verb' => 'POST', 'requirements' => $requirements_v3],
['name' => 'api#uploadFiles', 'url' => $apiBase . 'forms/{formId}/submissions/files/{questionId}', 'verb' => 'POST', 'requirements' => $requirements_v3],
// Legacy v2 routes (TODO: remove with Forms v5)
// Forms
['name' => 'api#getFormsLegacy', 'url' => $apiBase . 'forms', 'verb' => 'GET', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'api#newFormLegacy', 'url' => $apiBase . 'form', 'verb' => 'POST', 'requirements' => [
'apiVersion_path' => 'v2(\.[1-5])?'
]],
['name' => 'api#getFormLegacy', 'url' => $apiBase . 'form/{id}', 'verb' => 'GET', 'requirements' => [
'apiVersion_path' => 'v2(\.[1-5])?',
'id' => '\d+'
]],
['name' => 'api#cloneFormLegacy', 'url' => $apiBase . 'form/clone/{id}', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'id' => '\d+'
]],
['name' => 'api#updateFormLegacy', 'url' => $apiBase . 'form/update', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'api#updateFormLegacy', 'url' => $apiBase . 'form/update', 'verb' => 'PATCH', 'requirements' => [
'apiVersion' => 'v2\.[2-5]'
]],
['name' => 'api#transferOwnerLegacy', 'url' => $apiBase . 'form/transfer', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2\.[2-5]'
]],
['name' => 'api#deleteFormLegacy', 'url' => $apiBase . 'form/{id}', 'verb' => 'DELETE', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'id' => '\d+'
]],
['name' => 'api#getPartialFormLegacy', 'url' => $apiBase . 'partial_form/{hash}', 'verb' => 'GET', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'hash' => '[a-zA-Z0-9]{16}'
]],
['name' => 'api#getSharedFormsLegacy', 'url' => $apiBase . 'shared_forms', 'verb' => 'GET', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
// Questions
['name' => 'api#newQuestionLegacy', 'url' => $apiBase . 'question', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
// TODO: Remove POST in next API release
['name' => 'api#updateQuestionLegacy', 'url' => $apiBase . 'question/update', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'api#updateQuestionLegacy', 'url' => $apiBase . 'question/update', 'verb' => 'PATCH', 'requirements' => [
'apiVersion' => 'v2\.[2-5]'
]],
// TODO: Remove POST in next API release
['name' => 'api#reorderQuestionsLegacy', 'url' => $apiBase . 'question/reorder', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'api#reorderQuestionsLegacy', 'url' => $apiBase . 'question/reorder', 'verb' => 'PUT', 'requirements' => [
'apiVersion' => 'v2\.[2-5]'
]],
['name' => 'api#deleteQuestionLegacy', 'url' => $apiBase . 'question/{id}', 'verb' => 'DELETE', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'id' => '\d+'
]],
['name' => 'api#cloneQuestionLegacy', 'url' => $apiBase . 'question/clone/{id}', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2\.[3-5]',
'id' => '\d+'
]],
// Options
['name' => 'api#newOptionLegacy', 'url' => $apiBase . 'option', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
// TODO: Remove POST in next API release
['name' => 'api#updateOptionLegacy', 'url' => $apiBase . 'option/update', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'api#updateOptionLegacy', 'url' => $apiBase . 'option/update', 'verb' => 'PATCH', 'requirements' => [
'apiVersion' => 'v2\.[2-5]'
]],
['name' => 'api#deleteOptionLegacy', 'url' => $apiBase . 'option/{id}', 'verb' => 'DELETE', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'id' => '\d+'
]],
// Shares
['name' => 'shareApi#newShareLegacy', 'url' => $apiBase . 'share', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'shareApi#deleteShareLegacy', 'url' => $apiBase . 'share/{id}', 'verb' => 'DELETE', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'id' => '\d+'
]],
// TODO: Remove POST in next API release
['name' => 'shareApi#updateShareLegacy', 'url' => $apiBase . 'share/update', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2\.[1-5]'
]],
['name' => 'shareApi#updateShareLegacy', 'url' => $apiBase . 'share/update', 'verb' => 'PATCH', 'requirements' => [
'apiVersion' => 'v2\.[2-5]'
]],
// Submissions
['name' => 'api#getSubmissionsLegacy', 'url' => $apiBase . 'submissions/{hash}', 'verb' => 'GET', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'hash' => '[a-zA-Z0-9]{16}'
]],
['name' => 'api#exportSubmissionsLegacy', 'url' => $apiBase . 'submissions/export/{hash}', 'verb' => 'GET', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'hash' => '[a-zA-Z0-9]{16}'
]],
['name' => 'api#exportSubmissionsToCloudLegacy', 'url' => $apiBase . 'submissions/export', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'api#deleteAllSubmissionsLegacy', 'url' => $apiBase . 'submissions/{formId}', 'verb' => 'DELETE', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'formId' => '\d+'
]],
['name' => 'api#uploadFilesLegacy', 'url' => $apiBase . 'uploadFiles/{formId}/{questionId}', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2.5',
'formId' => '\d+',
'questionId' => '\d+'
]],
['name' => 'api#insertSubmissionLegacy', 'url' => $apiBase . 'submission/insert', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?'
]],
['name' => 'api#deleteSubmissionLegacy', 'url' => $apiBase . 'submission/{id}', 'verb' => 'DELETE', 'requirements' => [
'apiVersion' => 'v2(\.[1-5])?',
'id' => '\d+'
]],
// Submissions linking with file in cloud
['name' => 'api#linkFileLegacy', 'url' => $apiBase . 'form/link/{fileFormat}', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2.[4-5]',
'fileFormat' => 'csv|ods|xlsx'
]],
['name' => 'api#unlinkFileLegacy', 'url' => $apiBase . 'form/unlink', 'verb' => 'POST', 'requirements' => [
'apiVersion' => 'v2.[4-5]',
]]
]
];

Просмотреть файл

@ -1,851 +0,0 @@
# Forms Public API
## API v2 is now deprecated and will be removed with Forms v5. Please see documentation for [API v3](API_v3.md)
This file contains the API-Documentation. For more information on the returned Data-Structures, please refer to the [corresponding Documentation](DataStructure.md).
## Generals
- Base URL for all calls to the forms API is `<nextcloud_base_url>/ocs/v2.php/apps/forms`
- All Requests need to provide some authentication information.
- All Requests to OCS-Endpoints require the Header `OCS-APIRequest: true`
- Unless otherwise specified, all parameters are mandatory.
- By default, the API returns data formatted as _xml_. If formatting as _json_ is desired, the request should contain the header `Accept: application/json`. For simple representation, the output presented in this document is all formatted as _json_.
- The OCS-Endpoint _always returns_ an object called `ocs`. This contains an object `meta` holding some meta-data, as well as an object `data` holding the actual data. In this document, the response-blocks only show the `data`, if not explicitely stated different.
```
"ocs": {
"meta": {
"status": "ok",
"statuscode": 200,
"message": "OK"
},
"data": <Actual data>
}
```
## API changes
### Deprecation info
- Starting with Forms v4.3 API v2 will be deprecated and removed with Forms v5: Please see the documentation for [API v3](API_v3.md)
- Starting with API v2.2 all endpoints that update data will use PATCH/PUT as method. POST is now deprecated and will be removed in API v3
### Breaking Changes on API v2
- The `mandatory` property of questions has been removed. It is replaced by `isRequired`.
- Completely new way of handling access & shares.
### Other API changes
- In API version 2.5 the following endpoints were introduced:
- `POST /api/2.5/uploadFiles/{formId}/{questionId}` to upload files to answer before form submitting
- In API version 2.5 the following change was made:
- Options now contain a property `order`
- In API version 2.4 the following endpoints were introduced:
- `POST /api/2.4/form/link/{fileFormat}` to link form to a file
- `POST /api/2.4/form/unlink` to unlink form from a file
- In API version 2.4 the following endpoints were changed:
- `GET /api/v2.4/submissions/export/{hash}` was extended with optional parameter `fileFormat` to export submissions in different formats
- `GET /api/v2.4/submissions/export` was extended with optional parameter `fileFormat` to export submissions to cloud in different formats
- `GET /api/v2.4/form/{id}` was extended with optional parameters `fileFormat`, `fileId`, `filePath` to link form to a file
- In API version 2.3 the endpoint `/api/v2.3/question/clone` was added to clone a question
- In API version 2.2 the endpoint `/api/v2.2/form/transfer` was added to transfer ownership of a form
- In API version 2.1 the endpoint `/api/v2.1/share/update` was added to update a Share
## Form Endpoints
### List owned Forms
Returns condensed objects of all Forms beeing owned by the authenticated user.
- Endpoint: `/api/v2.5/forms`
- Method: `GET`
- Parameters: None
- Response: Array of condensed Form Objects, sorted as newest first.
```
"data": [
{
"id": 6,
"hash": "yWeMwcwCwoqRs8T2",
"title": "Form 2",
"expires": 0,
"permissions": [
"edit",
"results",
"submit"
],
"partial": true,
"state": 0
},
{
"id": 3,
"hash": "em4djk8B9BpXnkYG",
"title": "Form 1",
"expires": 0,
"permissions": [
"edit",
"results",
"submit"
],
"partial": true,
"state": 0
}
]
```
### List shared Forms
Returns condensed objects of all Forms, that are shared & shown to the authenticated user and that have not expired yet.
- Endpoint: `/api/v2.5/shared_forms`
- Method: `GET`
- Parameters: None
- Response: Array of condensed Form Objects, sorted as newest first, similar to [List owned Forms](#list-owned-forms).
```
See above, 'List owned forms'
```
### Get a partial Form
Returns a single partial form object, corresponding to owned/shared form-listings.
- Endpoint: `/api/v2.5/partial_form/{hash}`
- Method: `GET`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _hash_ | String | Hash of the form to request |
- Response: Partial form object, similar to form-list elements.
```
"data": {
"id": 6,
"hash": "yWeMwcwCwoqRs8T2",
"title": "Form 2",
"expires": 0,
"permissions": [
"submit"
],
"partial": true,
"state": 0
}
```
### Create a new Form
- Endpoint: `/api/v2.5/form`
- Method: `POST`
- Parameters: None
- Response: The new form object, similar to requesting an existing form.
```
See next section, 'Request full data of a form'
```
### Request full data of a form
Returns the full-depth object of the requested form (without submissions).
- Endpoint: `/api/v2.5/form/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the form to request |
- Method: `GET`
- Response: A full object of the form, including access, questions and options in full depth.
```
"data": {
"id": 3,
"hash": "em4djk8B9BpXnkYG",
"title": "Form 1",
"description": "Description Text",
"ownerId": "jonas",
"submissionMessage": "Thank **you** for submitting the form."
"created": 1611240961,
"access": {
"permitAllUsers": false,
"showToAllUsers": false
},
"expires": 0,
"fileFormat": "csv",
"fileId": 157,
"filePath": "foo/bar",
"isAnonymous": false,
"submitMultiple": true,
"showExpiration": false,
"canSubmit": true,
"state": 0,
"permissions": [
"edit",
"results",
"submit"
],
"questions": [
{
"id": 1,
"formId": 3,
"order": 1,
"type": "dropdown",
"isRequired": false,
"text": "Question 1",
"name": "something",
"options": [
{
"id": 1,
"questionId": 1,
"text": "Option 1",
"order": null
},
{
"id": 2,
"questionId": 1,
"text": "Option 2",
"order": null
}
],
"accept": [],
"extraSettings": {}
},
{
"id": 2,
"formId": 3,
"order": 2,
"type": "file",
"isRequired": true,
"text": "Question 2",
"name": "something_other",
"options": [],
"extraSettings": {}
"accept": ["image/*", ".pdf"],
}
],
"shares": [
{
"id": 1,
"formId": 3,
"shareType": 0,
"shareWith": "user1",
"displayName": "User 1 Displayname"
},
{
"id": 2,
"formId": 3,
"shareType": 3,
"shareWith": "dYcTWjrSsxjMFFQzFAywzq5J",
"displayName": ""
}
]
}
```
### Clone a form
Creates a clone of a form (without submissions).
- Endpoint: `/api/v2.5/form/clone/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the form to clone |
- Method: `POST`
- Response: Returns the full object of the new form. See [Request full data of a Form](#request-full-data-of-a-form)
```
See section 'Request full data of a form'.
```
### Update form properties
Update a single or multiple properties of a form-object. Concerns **only** the Form-Object, properties of Questions, Options and Submissions, as well as their creation or deletion, are handled separately.
- Endpoint: `/api/v2.5/form/update`
- Method: `PATCH`
- _Method: `POST` deprecated_
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the form to update |
| _keyValuePairs_ | Array | Array of key-value pairs to update |
- Restrictions: It is **not allowed** to update one of the following key-value pairs: _id, hash, ownerId, created_
- Response: **Status-Code OK**, as well as the id of the updated form.
```
"data": 3
```
### Transfer form ownership
Transfer the ownership of a form to another user
- Endpoint: `/api/v2.5/form/transfer`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _formId_ | Integer | ID of the form to tranfer |
| _uid_ | Integer | ID of the new form owner |
- Restrictions: The initiator must be the current form owner.
- Response: **Status-Code OK**, as well as the id of the new owner.
```
"data": "user1"
```
### Delete a form
- Endpoint: `/api/v2.5/form/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the form to delete |
- Method: `DELETE`
- Response: **Status-Code OK**, as well as the id of the deleted form.
```
"data": 3
```
### Link a form to a file
- Endpoint: `/api/v2.5/form/link/{fileFormat}`
- Url-Parameter:
| Parameter | Type | Description |
|--------------|---------|--------------|
| _fileFormat_ | String | csv|ods|xlsx |
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|-----------|---------|--------------------------------------------|
| _hash_ | String | Hash of the form to update |
| _path_ | String | Path within User-Dir, to store the file to |
- Response: The new question object.
```
"data": {
"fileFormat": "csv",
"fileId": 157,
"filePath": "foo/bar",
"fileName": "Form 1 (responses).csv"
}
```
### Unlink file from form
- Endpoint: `/api/v2.5/form/unlink`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|-----------|---------|----------------------------|
| _hash_ | String | Hash of the form to update |
- Response: **Status-Code OK**
## Question Endpoints
Contains only manipulative question-endpoints. To retrieve questions, request the full form data.
### Create a new question
- Endpoint: `/api/v2.5/question`
- Method: `POST`
- Parameters:
| Parameter | Type | Optional | Description |
|-----------|---------|----------|-------------|
| _formId_ | Integer | | ID of the form, the new question will belong to |
| _type_ | [QuestionType](DataStructure.md#question-types) | | The question-type of the new question |
| _text_ | String | yes | _Optional_ The text of the new question. |
- Response: The new question object.
```
"data": {
"id": 3,
"formId": 3,
"order": 3,
"type": "short",
"isRequired": false,
"name": "",
"text": "",
"options": []
"extraSettings": {}
}
```
### Update question properties
Update a single or multiple properties of a question-object.
- Endpoint: `/api/v2.5/question/update`
- Method: `PATCH`
- _Method: `POST` deprecated_
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the question to update |
| _keyValuePairs_ | Array | Array of key-value pairs to update |
- Restrictions: It is **not allowed** to update one of the following key-value pairs: _id, formId, order_.
- Response: **Status-Code OK**, as well as the id of the updated question.
```
"data": 1
```
### Reorder questions
Reorders all Questions of a single form
- Endpoint: `/api/v2.5/question/reorder`
- Method: `PUT`
- _Method: `POST` deprecated_
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _formId_ | Integer | ID of the form, the questions belong to |
| _newOrder_ | Array | Array of **all** Question-IDs, ordered in the desired order |
- Restrictions: The Array **must** contain all Question-IDs corresponding to the specified form and **must not** contain any duplicates.
- Response: Array of questionIDs and their corresponding order.
```
"data": {
"1": {
"order": 1
},
"2": {
"order": 3
},
"3": {
"order": 2
}
}
```
### Delete a question
- Endpoint: `/api/v2.5/question/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the question to delete |
- Method: `DELETE`
- Response: **Status-Code OK**, as well as the id of the deleted question.
```
"data": 4
```
### Clone a question
Creates a clone of a question with all its options.
- Endpoint: `/api/v2.5/question/clone/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the question to clone |
- Method: `POST`
- Response: Returns cloned question object with the new ID set.
```
See section 'Create a new question'.
```
## Option Endpoints
Contains only manipulative question-endpoints. To retrieve options, request the full form data.
### Create a new Option
- Endpoint: `/api/v2.5/option`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _questionId_ | Integer | ID of the question, the new option will belong to |
| _text_ | String | The text of the new option |
- Response: The new option object
```
"data": {
"id": 7,
"questionId": 1,
"text": "test"
}
```
### Update option properties
Update a single or all properties of an option-object
- Endpoint: `/api/v2.5/option/update`
- Method: `PATCH`
- _Method: `POST` deprecated_
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the option to update |
| _keyValuePairs_ | Array | Array of key-value pairs to update |
- Restrictions: It is **not allowed** to update one of the following key-value pairs: _id, questionId_.
- Response: **Status-Code OK**, as well as the id of the updated option.
```
"data": 7
```
### Delete an option
- Endpoint: `/api/v2.5/option/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the option to delete |
- Method: `DELETE`
- Response: **Status-Code OK**, as well as the id of the deleted option.
```
"data": 7
```
## Sharing Endpoints
### Add a new Share
- Endpoint: `/api/v2.5/share`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|--------------|----------|-------------|
| _formId_ | Integer | Id of the form to share |
| _shareType_ | String | NC-shareType, out of the used shareTypes. |
| _shareWith_ | String | User/Group for the share. Not used for link-shares. |
| _permissions_ | String[] | Permissions of the sharees, see [DataStructure](DataStructure.md#Permissions). |
- Response: **Status-Code OK**, as well as the new share object.
```
"data": {
"id": 3,
"formId": 3,
"shareType": 0,
"shareWith": "user3",
"permissions": ["submit"],
"displayName": "User 3 Displayname"
}
```
### Delete a Share
- Endpoint: `/api/v2.5/share/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the share to delete |
- Method: `DELETE`
- Response: **Status-Code OK**, as well as the id of the deleted share.
```
"data": 5
```
### Update a Share
- Endpoint: `/api/v2.5/share/update`
- Parameters:
| Parameter | Type | Description |
|------------------|----------|-------------|
| _id_ | Integer | ID of the share to update |
| *keyValuePairs*¹ | Array | Array of key-value pairs to update |
¹Currently only the _permissions_ can be updated.
- Method: `PATCH`
- _Method: `POST` deprecated_
- Response: **Status-Code OK**, as well as the id of the share object.
```
"data": 5
```
## Submission Endpoints
### Get Form Submissions
Get all Submissions to a Form
- Endpoint: `/api/v2.5/submissions/{hash}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _hash_ | String | Hash of the form to get the submissions for |
- Method: `GET`
- Response: An Array of all submissions, sorted as newest first, as well as an array of the corresponding questions.
```
"data": {
"submissions": [
{
"id": 6,
"formId": 3,
"userId": "jonas",
"timestamp": 1611274453,
"answers": [
{
"id": 8,
"submissionId": 6,
"questionId": 1,
"text": "Option 3"
},
{
"id": 9,
"submissionId": 6,
"questionId": 2,
"text": "One more."
},
],
"userDisplayName": "jonas"
},
{
"id": 5,
"formId": 3,
"userId": "jonas",
"timestamp": 1611274433,
"answers": [
{
"id": 5,
"submissionId": 5,
"questionId": 1,
"text": "Option 2"
},
{
"id": 6,
"submissionId": 5,
"questionId": 2,
"text": "This is an answer."
},
],
"userDisplayName": "jonas"
}
],
"questions": [
{
"id": 1,
"formId": 3,
"order": 1,
"type": "dropdown",
"isRequired": false,
"text": "Question 1",
"options": [
{
"id": 1,
"questionId": 1,
"text": "Option 1",
"order": null
},
{
"id": 27,
"questionId": 1,
"text": "Option 2",
"order": null
},
{
"id": 30,
"questionId": 1,
"text": "Option 3",
"order": null
}
],
"extraSettings": {}
},
{
"id": 2,
"formId": 3,
"order": 2,
"type": "short",
"isRequired": true,
"text": "Question 2",
"options": [],
"extraSettings": {}
}
]
}
```
### Get Submissions as csv (Download)
Returns all submissions to the form in form of a csv-file.
- Endpoint: `/api/v2.5/submissions/export/{hash}`
- Url-Parameter:
| Parameter | Type | Description |
|--------------|---------|-------------|
| _hash_ | String | Hash of the form to get the submissions for |
| _fileFormat_ | String | csv|ods|xlsx |
- Method: `GET`
- Response: A Data Download Response containing the headers `Content-Disposition: attachment; filename="Form 1 (responses).csv"` and `Content-Type: text/csv;charset=UTF-8`. The actual data contains all submissions to the referred form, formatted as comma separated and escaped csv.
```
"User display name","Timestamp","Question 1","Question 2"
"jonas","Friday, January 22, 2021 at 12:47:29 AM GMT+0:00","Option 2","Answer"
"jonas","Friday, January 22, 2021 at 12:45:57 AM GMT+0:00","Option 3","NextAnswer"
```
### Export Submissions to Cloud (Files-App)
Creates a csv file and stores it to the cloud, resp. Files-App.
- Endpoint: `/api/v2.5/submissions/export`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|--------------|---------|-------------|
| _hash_ | String | Hash of the form to get the submissions for |
| _path_ | String | Path within User-Dir, to store the file to |
| _fileFormat_ | String | csv|ods|xlsx |
- Response: Stores the file to the given path and returns the fileName.
```
"data": "Form 2 (responses).csv"
```
### Delete Submissions
Delete all Submissions to a form
- Endpoint: `/api/v2.5/submissions/{formId}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _formId_ | Integer | ID of the form to delete the submissions for |
- Method: `DELETE`
- Response: **Status-Code OK**, as well as the id of the corresponding form.
```
"data": 3
```
### Upload a file
Upload a files to answer before form submitting
- Endpoint: `/api/2.5/uploadFiles/{formId}/{questionId}`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|--------------|----------------|-------------|
| _formId_ | Integer | ID of the form to upload the file to |
| _questionId_ | Integer | ID of the question to upload the file to |
| _files_ | Array of files | Files to upload |
- Response: **Status-Code OK**, as well as the id of the uploaded file and it's name.
```
"data": {"uploadedFileId": integer, "fileName": "string"}
```
### Insert a Submission
Store Submission to Database
- Endpoint: `/api/v2.5/submission/insert`
- Method: `POST`
- Parameters:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _formId_ | Integer | ID of the form to submit into |
| _answers_ | Array | Array of Answers |
| _shareHash_ | String | optional, only neccessary for submissions to a public share link |
The Array of Answers has the following structure:
- QuestionID as key
- An **array** of values as value --> Even for short Text Answers, wrapped into Array.
- For Question-Types with pre-defined answers (`multiple`, `multiple_unique`, `dropdown`), the array contains the corresponding option-IDs.
- For File-Uploads, the array contains the objects with key `uploadedFileId` (value from Upload a file endpoint).
````
{
"1":[27,32], // dropdown or multiple
"2":["ShortTextAnswer"], // All Text-Based Question-Types
"3":[ // File-Upload
{"uploadedFileId": integer},
{"uploadedFileId": integer}
],
}
```
- Response: **Status-Code OK**.
### Delete a single Submission
- Endpoint: `/api/v2.5/submission/{id}`
- Url-Parameter:
| Parameter | Type | Description |
|-----------|---------|-------------|
| _id_ | Integer | ID of the submission to delete |
- Method: `DELETE`
- Response: **Status-Code OK**, as well as the id of the deleted submission.
````
"data": 5
```
## Error Responses
All Endpoints return one of the following Error-Responses, if the request is not properly raised. This also results in a different `ocs:meta` object.
### 400 - Bad Request
This returns in case the Request is not properly set. This can e.g. include:
- The corresponding form can not be found
- Request Parameters are wrong (including formatting or type of parameters)
```
"ocs": {
"meta": {
"status": "failure",
"statuscode": 400,
"message": ""
},
"data": []
}
```
### 403 - Forbidden
This returns in case the authenticated user is not allowed to access this resource/endpoint. This can e.g. include:
- The user has no write access to the form (only form owner is allowed to edit)
- The user is not allowed to submit to the form (access-settings, form expired, already submitted)
```
"ocs": {
"meta": {
"status": "failure",
"statuscode": 403,
"message": ""
},
"data": []
}
```
### 412 - Precondition Failed
This Error is not produed by the Forms-API, but comes from Nextclouds OCS API. Typically this is the result when missing the Request-Header `OCS-APIRequest: true`.
```
{
"message": "CSRF check failed"
}
```
```

Просмотреть файл

@ -40,7 +40,7 @@ class Capabilities implements ICapability {
return [
'forms' => [
'version' => $this->appManager->getAppVersion('forms'),
'apiVersions' => ['v2','v2.1','v2.2','v2.3','v2.4','v2.5','v3']
'apiVersions' => ['v3']
]
];
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -336,258 +336,6 @@ class ShareApiController extends OCSController {
return new DataResponse($shareId);
}
/*
*
* Legacy API v2 methods (TODO: remove with Forms v5)
*
*/
/**
* @CORS
* @NoAdminRequired
*
* Add a new share
*
* @param int $formId The form to share
* @param int $shareType Nextcloud-ShareType
* @param string $shareWith ID of user/group/... to share with. For Empty shareWith and shareType Link, this will be set as RandomID.
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function newShareLegacy(int $formId, int $shareType, string $shareWith = '', array $permissions = [Constants::PERMISSION_SUBMIT]): DataResponse {
$this->logger->debug('Adding new share: formId: {formId}, shareType: {shareType}, shareWith: {shareWith}, permissions: {permissions}', [
'formId' => $formId,
'shareType' => $shareType,
'shareWith' => $shareWith,
'permissions' => $permissions,
]);
// Only accept usable shareTypes
if (array_search($shareType, Constants::SHARE_TYPES_USED) === false) {
$this->logger->debug('Invalid shareType');
throw new OCSBadRequestException('Invalid shareType');
}
// Block LinkShares if not allowed
if ($shareType === IShare::TYPE_LINK && !$this->configService->getAllowPublicLink()) {
$this->logger->debug('Link Share not allowed.');
throw new OCSForbiddenException('Link Share not allowed.');
}
try {
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form', ['exception' => $e]);
throw new OCSBadRequestException('Could not find form');
}
// Check for permission to share form
if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
throw new OCSForbiddenException();
}
if (!$this->validatePermissions($permissions, $shareType)) {
throw new OCSBadRequestException('Invalid permission given');
}
// Create public-share hash, if necessary.
if ($shareType === IShare::TYPE_LINK) {
$shareWith = $this->secureRandom->generate(
24,
ISecureRandom::CHAR_HUMAN_READABLE
);
}
// Check for valid shareWith, needs to be done separately per shareType
switch ($shareType) {
case IShare::TYPE_USER:
if (!($this->userManager->get($shareWith) instanceof IUser)) {
$this->logger->debug('Invalid user to share with.');
throw new OCSBadRequestException('Invalid user to share with.');
}
break;
case IShare::TYPE_GROUP:
if (!($this->groupManager->get($shareWith) instanceof IGroup)) {
$this->logger->debug('Invalid group to share with.');
throw new OCSBadRequestException('Invalid group to share with.');
}
break;
case IShare::TYPE_LINK:
// Check if hash already exists. (Unfortunately not possible here by unique index on db.)
try {
// Try loading a share to the hash.
$nonex = $this->shareMapper->findPublicShareByHash($shareWith);
// If we come here, a share has been found --> The share hash already exists, thus aborting.
$this->logger->debug('Share Hash already exists.');
throw new OCSException('Share Hash exists. Please retry.');
} catch (DoesNotExistException $e) {
// Just continue, this is what we expect to happen (share hash not existing yet).
}
break;
case IShare::TYPE_CIRCLE:
if (!$this->circlesService->isCirclesEnabled()) {
$this->logger->debug('Teams app is disabled, sharing to teams not possible.');
throw new OCSException('Teams app is disabled.');
}
$circle = $this->circlesService->getCircle($shareWith);
if (is_null($circle)) {
$this->logger->debug('Invalid team to share with.');
throw new OCSBadRequestException('Invalid team to share with.');
}
break;
default:
// This passed the check for used shareTypes, but has not been found here.
$this->logger->warning('Unknown, but used shareType: {shareType}. Please file an issue on GitHub.', [ 'shareType' => $shareType ]);
throw new OCSException('Unknown shareType.');
}
$share = new Share();
$share->setFormId($formId);
$share->setShareType($shareType);
$share->setShareWith($shareWith);
$share->setPermissions($permissions);
/** @var Share */
$share = $this->shareMapper->insert($share);
$this->formMapper->update($form);
// Create share-notifications (activity)
$this->formsService->notifyNewShares($form, $share);
// Append displayName for Frontend
$shareData = $share->read();
$shareData['displayName'] = $this->formsService->getShareDisplayName($shareData);
return new DataResponse($shareData);
}
/**
* @CORS
* @NoAdminRequired
*
* Delete a share
*
* @param int $id of the share to delete
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function deleteShareLegacy(int $id): DataResponse {
$this->logger->debug('Deleting share: {id}', [
'id' => $id
]);
try {
$share = $this->shareMapper->findById($id);
$form = $this->formMapper->findById($share->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find share', ['exception' => $e]);
throw new OCSBadRequestException('Could not find share');
}
if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
throw new OCSForbiddenException();
}
$this->shareMapper->delete($share);
$this->formMapper->update($form);
return new DataResponse($id);
}
/**
* @CORS
* @NoAdminRequired
*
* Update permissions of a share
*
* @param int $id of the share to update
* @param array $keyValuePairs Array of key=>value pairs to update.
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function updateShareLegacy(int $id, array $keyValuePairs): DataResponse {
$this->logger->debug('Updating share: {id}, permissions: {permissions}', [
'id' => $id,
'keyValuePairs' => $keyValuePairs
]);
try {
$formShare = $this->shareMapper->findById($id);
$form = $this->formMapper->findById($formShare->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find share', ['exception' => $e]);
throw new OCSBadRequestException('Could not find share');
}
if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
throw new OCSForbiddenException();
}
// Don't allow empty array
if (sizeof($keyValuePairs) === 0) {
$this->logger->info('Empty keyValuePairs, will not update.');
throw new OCSForbiddenException();
}
//Don't allow to change other properties than permissions
if (count($keyValuePairs) > 1 || !key_exists('permissions', $keyValuePairs)) {
$this->logger->debug('Not allowed to update other properties than permissions');
throw new OCSForbiddenException();
}
if (!$this->validatePermissions($keyValuePairs['permissions'], $formShare->getShareType())) {
throw new OCSBadRequestException('Invalid permission given');
}
$formShare->setPermissions($keyValuePairs['permissions']);
$formShare = $this->shareMapper->update($formShare);
if (in_array($formShare->getShareType(), [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_USERGROUP, IShare::TYPE_CIRCLE], true)) {
$userFolder = $this->rootFolder->getUserFolder($form->getOwnerId());
$uploadedFilesFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
if ($userFolder->nodeExists($uploadedFilesFolderPath)) {
$folder = $userFolder->get($uploadedFilesFolderPath);
} else {
$folder = $userFolder->newFolder($uploadedFilesFolderPath);
}
/** @var \OCP\Files\Folder $folder */
if (in_array(Constants::PERMISSION_RESULTS, $keyValuePairs['permissions'], true)) {
$folderShare = $this->shareManager->newShare();
$folderShare->setShareType($formShare->getShareType());
$folderShare->setSharedWith($formShare->getShareWith());
$folderShare->setSharedBy($form->getOwnerId());
$folderShare->setPermissions(\OCP\Constants::PERMISSION_READ);
$folderShare->setNode($folder);
$folderShare->setShareOwner($form->getOwnerId());
$this->shareManager->createShare($folderShare);
} else {
$folderShares = $this->shareManager->getSharesBy($form->getOwnerId(), $formShare->getShareType(), $folder);
foreach ($folderShares as $folderShare) {
if ($folderShare->getSharedWith() === $formShare->getShareWith()) {
$this->shareManager->deleteShare($folderShare);
}
}
}
}
$this->formMapper->update($form);
return new DataResponse($formShare->getId());
}
/**
* Validate user given permission array
*
@ -595,7 +343,7 @@ class ShareApiController extends OCSController {
* @return bool True if permissions are valid, False otherwise
* @throws OCSBadRequestException If invalid permission was given
*/
protected function validatePermissions(array $permissions, int $shareType): bool {
private function validatePermissions(array $permissions, int $shareType): bool {
if (count($permissions) === 0) {
return false;
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -63,7 +63,6 @@ use OCA\Forms\Service\ConfigService;
use OCA\Forms\Service\FormsService;
use OCA\Forms\Service\SubmissionService;
use OCA\Forms\Tests\Unit\MockedMapperException;
use OCP\AppFramework\Db\IMapperException;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
@ -369,30 +368,6 @@ class ApiControllerTest extends TestCase {
$this->apiController->exportSubmissionsToCloud(1, '');
}
public function testUnlinkFile() {
$form = new Form();
$form->setId(1);
$form->setHash('hash');
$form->setOwnerId('currentUser');
$form->setFileId(100);
$form->setFileFormat('csv');
$this->formMapper->expects($this->once())
->method('findByHash')
->with('hash')
->willReturn($form);
$this->formsService->expects($this->once())
->method('canEditForm')
->with($form)
->willReturn(true);
$this->apiController->unlinkFileLegacy('hash');
$this->assertNull($form->getFileId());
$this->assertNull($form->getFileFormat());
}
public function testCreateNewForm_notAllowed() {
$this->configService->expects($this->once())
->method('canCreateForms')
@ -638,21 +613,6 @@ class ApiControllerTest extends TestCase {
->willReturn($canSubmit);
}
public function testCloneQuestion_notFound() {
$this->questionMapper->method('findById')->with(42)->willThrowException($this->createMock(IMapperException::class));
$this->expectException(OCSNotFoundException::class);
$this->apiController->cloneQuestionLegacy(42);
}
public function testCloneQuestion_noPermission() {
$form = Form::fromParams(['ownerId' => 'otherUser']);
$question = Question::fromParams(['formId' => 1]);
$this->questionMapper->method('findById')->with(42)->willReturn($question);
$this->formMapper->method('findById')->with(1)->willReturn($form);
$this->expectException(OCSForbiddenException::class);
$this->apiController->cloneQuestionLegacy(42);
}
public function testUploadFiles() {
$form = new Form();
$form->setId(1);
@ -1039,7 +999,7 @@ class ApiControllerTest extends TestCase {
->willReturn($form);
$this->expectException(OCSForbiddenException::class);
$this->apiController->transferOwnerLegacy(1, 'newOwner');
$this->apiController->updateForm(1, ['ownerId' => 'newOwner']);
}
public function testTransferNewOwnerNotFound() {
@ -1059,7 +1019,7 @@ class ApiControllerTest extends TestCase {
->willReturn(null);
$this->expectException(OCSBadRequestException::class);
$this->apiController->transferOwnerLegacy(1, 'newOwner');
$this->apiController->updateForm(1, ['ownerId' => 'newOwner']);
}
public function testTransferOwner() {
@ -1079,7 +1039,7 @@ class ApiControllerTest extends TestCase {
->with('newOwner')
->willReturn($newOwner);
$this->assertEquals(new DataResponse('newOwner'), $this->apiController->transferOwnerLegacy(1, 'newOwner'));
$this->assertEquals(new DataResponse('newOwner'), $this->apiController->updateForm(1, ['ownerId' => 'newOwner']));
$this->assertEquals('newOwner', $form->getOwnerId());
}
}