diff --git a/appinfo/routes.php b/appinfo/routes.php index bf9caf49b..aaa60596a 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -59,7 +59,7 @@ // 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 @@ ['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]', - ]] ] ]; diff --git a/docs/API.md b/docs/API.md deleted file mode 100644 index ca09a6ea5..000000000 --- a/docs/API.md +++ /dev/null @@ -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 `/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": -} -``` - -## 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" -} - -``` - -``` diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 94b4db1d1..62d739a5b 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -40,7 +40,7 @@ public function getCapabilities() { return [ 'forms' => [ 'version' => $this->appManager->getAppVersion('forms'), - 'apiVersions' => ['v2','v2.1','v2.2','v2.3','v2.4','v2.5','v3'] + 'apiVersions' => ['v3'] ] ]; } diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index f3b0584a6..fcec61636 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -1401,1294 +1401,6 @@ public function uploadFiles(int $formId, int $questionId, string $shareHash = '' return new DataResponse($response); } - /* - * - * Legacy API v2 methods (TODO: remove with Forms v5) - * - */ - - /** - * @CORS - * @NoAdminRequired - * - * Read Form-List of owned forms - * Return only with necessary information for Listing. - * @return DataResponse - */ - public function getFormsLegacy(): DataResponse { - $forms = $this->formMapper->findAllByOwnerId($this->currentUser->getUID()); - - $result = []; - foreach ($forms as $form) { - $result[] = $this->formsService->getPartialFormArray($form); - } - - return new DataResponse($result); - } - - /** - * @CORS - * @NoAdminRequired - * - * Read List of forms shared with current user - * Return only with necessary information for Listing. - * @return DataResponse - */ - public function getSharedFormsLegacy(): DataResponse { - $forms = $this->formsService->getSharedForms($this->currentUser); - $result = array_values(array_map(fn (Form $form): array => $this->formsService->getPartialFormArray($form), $forms)); - - return new DataResponse($result); - } - - /** - * @CORS - * @NoAdminRequired - * - * Get a partial form by its hash. Implicitely checks, if the user has access. - * - * @param string $hash The form hash - * @return DataResponse - * @throws OCSBadRequestException if forbidden or not found - */ - public function getPartialFormLegacy(string $hash): DataResponse { - try { - $form = $this->formMapper->findByHash($hash); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); - } - - if (!$this->formsService->hasUserAccess($form)) { - $this->logger->debug('User has no permissions to get this form'); - throw new OCSForbiddenException(); - } - - return new DataResponse($this->formsService->getPartialFormArray($form)); - } - - /** - * @CORS - * @NoAdminRequired - * - * Read all information to edit a Form (form, questions, options, except submissions/answers). - * - * @param int $id FormId - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function getFormLegacy(int $id): DataResponse { - try { - $form = $this->formMapper->findById($id); - $formData = $this->formsService->getForm($form); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); - } - - if (!$this->formsService->hasUserAccess($form)) { - $this->logger->debug('User has no permissions to get this form'); - throw new OCSForbiddenException(); - } - - return new DataResponse($formData); - } - - /** - * @CORS - * @NoAdminRequired - * - * Create a new Form and return the Form to edit. - * - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function newFormLegacy(): DataResponse { - // Check if user is allowed - if (!$this->configService->canCreateForms()) { - $this->logger->debug('This user is not allowed to create Forms.'); - throw new OCSForbiddenException(); - } - - // Create Form - $form = new Form(); - $form->setOwnerId($this->currentUser->getUID()); - $form->setHash($this->formsService->generateFormHash()); - $form->setTitle(''); - $form->setDescription(''); - $form->setAccess([ - 'permitAllUsers' => false, - 'showToAllUsers' => false, - ]); - $form->setSubmitMultiple(false); - $form->setShowExpiration(false); - $form->setExpires(0); - $form->setIsAnonymous(false); - - $this->formMapper->insert($form); - - // Return like getForm() - return $this->getForm($form->getId()); - } - - /** - * @CORS - * @NoAdminRequired - * - * Clones a form - * - * @param int $id FormId of the form to clone - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function cloneFormLegacy(int $id): DataResponse { - $this->logger->debug('Cloning Form: {id}', [ - 'id' => $id - ]); - - // Check if user can create forms - if (!$this->configService->canCreateForms()) { - $this->logger->debug('This user is not allowed to create Forms.'); - throw new OCSForbiddenException(); - } - - $oldForm = $this->getFormIfAllowed($id); - - // Read Form, set new Form specific data, extend Title. - $formData = $oldForm->read(); - unset($formData['id']); - $formData['created'] = time(); - $formData['lastUpdated'] = time(); - $formData['hash'] = $this->formsService->generateFormHash(); - // TRANSLATORS Appendix to the form Title of a duplicated/copied form. - $formData['title'] .= ' - ' . $this->l10n->t('Copy'); - - $newForm = Form::fromParams($formData); - $this->formMapper->insert($newForm); - - // Get Questions, set new formId, reinsert - $questions = $this->questionMapper->findByForm($oldForm->getId()); - foreach ($questions as $oldQuestion) { - $questionData = $oldQuestion->read(); - - unset($questionData['id']); - $questionData['formId'] = $newForm->getId(); - $newQuestion = Question::fromParams($questionData); - $this->questionMapper->insert($newQuestion); - - // Get Options, set new QuestionId, reinsert - $options = $this->optionMapper->findByQuestion($oldQuestion->getId()); - foreach ($options as $oldOption) { - $optionData = $oldOption->read(); - - unset($optionData['id']); - $optionData['questionId'] = $newQuestion->getId(); - $newOption = Option::fromParams($optionData); - $this->optionMapper->insert($newOption); - } - } - - // Return just like getForm does. Returns the full form. - return $this->getForm($newForm->getId()); - } - - /** - * @CORS - * @NoAdminRequired - * - * Writes the given key-value pairs into Database. - * - * @param int $id FormId of form to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function updateFormLegacy(int $id, array $keyValuePairs): DataResponse { - $this->logger->debug('Updating form: FormId: {id}, values: {keyValuePairs}', [ - 'id' => $id, - 'keyValuePairs' => $keyValuePairs - ]); - - $form = $this->getFormIfAllowed($id); - - // 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 params id, hash, ownerId, created, lastUpdated - if ( - key_exists('id', $keyValuePairs) || key_exists('hash', $keyValuePairs) || - key_exists('ownerId', $keyValuePairs) || key_exists('created', $keyValuePairs) || - key_exists('lastUpdated', $keyValuePairs) - ) { - $this->logger->info('Not allowed to update id, hash, ownerId or created'); - throw new OCSForbiddenException(); - } - - // Create FormEntity with given Params & Id. - foreach ($keyValuePairs as $key => $value) { - $method = 'set' . ucfirst($key); - $form->$method($value); - } - - // Update changed Columns in Db. - $this->formMapper->update($form); - - return new DataResponse($form->getId()); - } - - /** - * @NoAdminRequired - * - * Transfer ownership of a form to another user - * - * @param int $formId id of the form to update - * @param string $uid id of the new owner - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function transferOwnerLegacy(int $formId, string $uid): DataResponse { - $this->logger->debug('Updating owner: formId: {formId}, userId: {uid}', [ - 'formId' => $formId, - 'uid' => $uid - ]); - - $form = $this->getFormIfAllowed($formId); - $user = $this->userManager->get($uid); - if ($user == null) { - $this->logger->debug('Could not find new form owner'); - throw new OCSBadRequestException('Could not find new form owner'); - } - - // update form owner - $form->setOwnerId($uid); - - // Update changed Columns in Db. - $this->formMapper->update($form); - - return new DataResponse($form->getOwnerId()); - } - - /** - * @CORS - * @NoAdminRequired - * - * Delete a form - * - * @param int $id the form id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function deleteFormLegacy(int $id): DataResponse { - $this->logger->debug('Delete Form: {id}', [ - 'id' => $id, - ]); - - $form = $this->getFormIfAllowed($id); - $this->formMapper->deleteForm($form); - - return new DataResponse($id); - } - - /** - * @CORS - * @NoAdminRequired - * - * Add a new question - * - * @param int $formId the form id - * @param string $type the new question type - * @param string $text the new question title - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function newQuestionLegacy(int $formId, string $type, string $text = ''): DataResponse { - $this->logger->debug('Adding new question: formId: {formId}, type: {type}, text: {text}', [ - 'formId' => $formId, - 'type' => $type, - 'text' => $text, - ]); - - if (array_search($type, Constants::ANSWER_TYPES) === false) { - $this->logger->debug('Invalid type'); - throw new OCSBadRequestException('Invalid type'); - } - - // Block creation of datetime questions - if ($type === 'datetime') { - $this->logger->debug('Datetime question type no longer supported'); - throw new OCSBadRequestException('Datetime question type no longer supported'); - } - - $form = $this->getFormIfAllowed($formId); - 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); - if ($lastQuestion) { - $questionOrder = $lastQuestion->getOrder() + 1; - } else { - $questionOrder = 1; - } - - $question = new Question(); - - $question->setFormId($formId); - $question->setOrder($questionOrder); - $question->setType($type); - $question->setText($text); - $question->setDescription(''); - $question->setIsRequired(false); - $question->setExtraSettings([]); - - $question = $this->questionMapper->insert($question); - - $response = $question->read(); - $response['options'] = []; - $response['accept'] = []; - - $this->formMapper->update($form); - - return new DataResponse($response); - } - - /** - * @CORS - * @NoAdminRequired - * - * Updates the Order of all Questions of a Form. - * - * @param int $formId Id of the form to reorder - * @param Array $newOrder Array of Question-Ids in new order. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function reorderQuestionsLegacy(int $formId, array $newOrder): DataResponse { - $this->logger->debug('Reordering Questions on Form {formId} as Question-Ids {newOrder}', [ - 'formId' => $formId, - 'newOrder' => $newOrder - ]); - - $form = $this->getFormIfAllowed($formId); - 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'); - throw new OCSBadRequestException('The given Array contains duplicates'); - } - - // Check if all questions are given in Array. - $questions = $this->questionMapper->findByForm($formId); - if (sizeof($questions) !== sizeof($newOrder)) { - $this->logger->debug('The length of the given array does not match the number of stored questions'); - throw new OCSBadRequestException('The length of the given array does not match the number of stored questions'); - } - - $questions = []; // Clear Array of Entities - $response = []; // Array of ['questionId' => ['order' => newOrder]] - - // Store array of Question-Entities and check the Questions FormId & old Order. - foreach ($newOrder as $arrayKey => $questionId) { - try { - $questions[$arrayKey] = $this->questionMapper->findById($questionId); - } catch (IMapperException $e) { - $this->logger->debug('Could not find question. Id:{id}', [ - 'id' => $questionId - ]); - throw new OCSBadRequestException(); - } - - // Abort if a question is not part of the Form. - if ($questions[$arrayKey]->getFormId() !== $formId) { - $this->logger->debug('This Question is not part of the given Form: questionId: {questionId}', [ - 'questionId' => $questionId - ]); - throw new OCSBadRequestException(); - } - - // Abort if a question is already marked as deleted (order==0) - $oldOrder = $questions[$arrayKey]->getOrder(); - if ($oldOrder === 0) { - $this->logger->debug('This Question has already been marked as deleted: Id: {id}', [ - 'id' => $questions[$arrayKey]->getId() - ]); - throw new OCSBadRequestException(); - } - - // Only set order, if it changed. - if ($oldOrder !== $arrayKey + 1) { - // Set Order. ArrayKey counts from zero, order counts from 1. - $questions[$arrayKey]->setOrder($arrayKey + 1); - } - } - - // Write to Database - foreach ($questions as $question) { - $this->questionMapper->update($question); - - $response[$question->getId()] = [ - 'order' => $question->getOrder() - ]; - } - - $this->formMapper->update($form); - - return new DataResponse($response); - } - - /** - * @CORS - * @NoAdminRequired - * - * Writes the given key-value pairs into Database. - * Key 'order' should only be changed by reorderQuestions() and is not allowed here. - * - * @param int $id QuestionId of question to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function updateQuestionLegacy(int $id, array $keyValuePairs): DataResponse { - $this->logger->debug('Updating question: questionId: {id}, values: {keyValuePairs}', [ - 'id' => $id, - 'keyValuePairs' => $keyValuePairs - ]); - - try { - $question = $this->questionMapper->findById($id); - } catch (IMapperException $e) { - $this->logger->debug('Could not find question'); - throw new OCSBadRequestException('Could not find question'); - } - - $form = $this->getFormIfAllowed($question->getFormId()); - 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.'); - throw new OCSForbiddenException(); - } - - //Don't allow to change id or formId - if (key_exists('id', $keyValuePairs) || key_exists('formId', $keyValuePairs)) { - $this->logger->debug('Not allowed to update id or formId'); - throw new OCSForbiddenException(); - } - - // Don't allow to reorder here - if (key_exists('order', $keyValuePairs)) { - $this->logger->debug('Key \'order\' is not allowed on updateQuestion. Please use reorderQuestions() to change order.'); - throw new OCSForbiddenException('Please use reorderQuestions() to change order'); - } - - if (key_exists('extraSettings', $keyValuePairs) && !$this->formsService->areExtraSettingsValid($keyValuePairs['extraSettings'], $question->getType())) { - throw new OCSBadRequestException('Invalid extraSettings, will not update.'); - } - - // Create QuestionEntity with given Params & Id. - $question = Question::fromParams($keyValuePairs); - $question->setId($id); - - // Update changed Columns in Db. - $this->questionMapper->update($question); - $this->formMapper->update($form); - - return new DataResponse($question->getId()); - } - - /** - * @CORS - * @NoAdminRequired - * - * Delete a question - * - * @param int $id the question id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function deleteQuestionLegacy(int $id): DataResponse { - $this->logger->debug('Mark question as deleted: {id}', [ - 'id' => $id, - ]); - - try { - $question = $this->questionMapper->findById($id); - } catch (IMapperException $e) { - $this->logger->debug('Could not find question'); - throw new OCSBadRequestException('Could not find question'); - } - - $form = $this->getFormIfAllowed($question->getFormId()); - - 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(); - - // Mark question as deleted - $question->setOrder(0); - $this->questionMapper->update($question); - - // Update all question-order > deleted order. - $formQuestions = $this->questionMapper->findByForm($form->getId()); - foreach ($formQuestions as $question) { - $questionOrder = $question->getOrder(); - if ($questionOrder > $deletedOrder) { - $question->setOrder($questionOrder - 1); - $this->questionMapper->update($question); - } - } - - $this->formMapper->update($form); - - return new DataResponse($id); - } - - /** - * @CORS - * @NoAdminRequired - * - * Clone a question - * - * @param int $id the question id - * @return DataResponse - * @throws OCSBadRequestException|OCSForbiddenException - */ - public function cloneQuestionLegacy(int $id): DataResponse { - $this->logger->debug('Question to be cloned: {id}', [ - 'id' => $id - ]); - - try { - $sourceQuestion = $this->questionMapper->findById($id); - $sourceOptions = $this->optionMapper->findByQuestion($id); - $form = $this->formMapper->findById($sourceQuestion->getFormId()); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form or question'); - throw new OCSNotFoundException('Could not find form or question'); - } - - if ($form->getOwnerId() !== $this->currentUser->getUID()) { - $this->logger->debug('This form is not owned by the current user'); - 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(); - unset($questionData['id']); - $questionData['order'] = end($allQuestions)->getOrder() + 1; - - $newQuestion = Question::fromParams($questionData); - $this->questionMapper->insert($newQuestion); - - $response = $newQuestion->read(); - $response['options'] = []; - $response['accept'] = []; - - foreach ($sourceOptions as $sourceOption) { - $optionData = $sourceOption->read(); - - unset($optionData['id']); - $optionData['questionId'] = $newQuestion->getId(); - $newOption = Option::fromParams($optionData); - $insertedOption = $this->optionMapper->insert($newOption); - - $response['options'][] = $insertedOption->read(); - } - - return new DataResponse($response); - } - - /** - * @NoAdminRequired - * - * Add a new option to a question - * - * @param int $questionId the question id - * @param string $text the new option text - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function newOptionLegacy(int $questionId, string $text): DataResponse { - $this->logger->debug('Adding new option: questionId: {questionId}, text: {text}', [ - 'questionId' => $questionId, - 'text' => $text, - ]); - - try { - $question = $this->questionMapper->findById($questionId); - $form = $this->formMapper->findById($question->getFormId()); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form or question'); - throw new OCSBadRequestException('Could not find form or question'); - } - - if ($form->getOwnerId() !== $this->currentUser->getUID()) { - $this->logger->debug('This form is not owned by the current user'); - 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 options sorted by 'order'. Takes the order of the last array-element and adds one. - $options = $this->optionMapper->findByQuestion($questionId); - $lastOption = array_pop($options); - if ($lastOption) { - $optionOrder = $lastOption->getOrder() + 1; - } else { - $optionOrder = 1; - } - - $option = new Option(); - - $option->setQuestionId($questionId); - $option->setText($text); - $option->setOrder($optionOrder); - - $option = $this->optionMapper->insert($option); - $this->formMapper->update($form); - - return new DataResponse($option->read()); - } - - /** - * @CORS - * @NoAdminRequired - * - * Writes the given key-value pairs into Database. - * - * @param int $id OptionId of option to update - * @param array $keyValuePairs Array of key=>value pairs to update. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function updateOptionLegacy(int $id, array $keyValuePairs): DataResponse { - $this->logger->debug('Updating option: option: {id}, values: {keyValuePairs}', [ - 'id' => $id, - 'keyValuePairs' => $keyValuePairs - ]); - - try { - $option = $this->optionMapper->findById($id); - $question = $this->questionMapper->findById($option->getQuestionId()); - $form = $this->formMapper->findById($question->getFormId()); - } catch (IMapperException $e) { - $this->logger->debug('Could not find option, question or form'); - throw new OCSBadRequestException('Could not find option, question or form'); - } - - if ($form->getOwnerId() !== $this->currentUser->getUID()) { - $this->logger->debug('This form is not owned by the current user'); - 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.'); - throw new OCSForbiddenException(); - } - - //Don't allow to change id or questionId - if (key_exists('id', $keyValuePairs) || key_exists('questionId', $keyValuePairs)) { - $this->logger->debug('Not allowed to update id or questionId'); - throw new OCSForbiddenException(); - } - - // Create OptionEntity with given Params & Id. - $option = Option::fromParams($keyValuePairs); - $option->setId($id); - - // Update changed Columns in Db. - $this->optionMapper->update($option); - $this->formMapper->update($form); - - return new DataResponse($option->getId()); - } - - /** - * @CORS - * @NoAdminRequired - * - * Delete an option - * - * @param int $id the option id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function deleteOptionLegacy(int $id): DataResponse { - $this->logger->debug('Deleting option: {id}', [ - 'id' => $id - ]); - - try { - $option = $this->optionMapper->findById($id); - $question = $this->questionMapper->findById($option->getQuestionId()); - $form = $this->formMapper->findById($question->getFormId()); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form or option'); - throw new OCSBadRequestException('Could not find form or option'); - } - - if ($form->getOwnerId() !== $this->currentUser->getUID()) { - $this->logger->debug('This form is not owned by the current user'); - 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); - - // Reorder the remaining options - $options = array_values($this->optionMapper->findByQuestion($option->getQuestionId())); - foreach ($options as $order => $option) { - // Always start order with 1 - $option->setOrder($order + 1); - $this->optionMapper->update($option); - } - - $this->formMapper->update($form); - - return new DataResponse($id); - } - - /** - * @CORS - * @NoAdminRequired - * - * Get all the submissions of a given form - * - * @param string $hash the form hash - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function getSubmissionsLegacy(string $hash): DataResponse { - try { - $form = $this->formMapper->findByHash($hash); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); - } - - if (!$this->formsService->canSeeResults($form)) { - $this->logger->debug('The current user has no permission to get the results for this form'); - throw new OCSForbiddenException(); - } - - // Load submissions and currently active questions - $submissions = $this->submissionService->getSubmissions($form->getId()); - $questions = $this->formsService->getQuestions($form->getId()); - - // Append Display Names - foreach ($submissions as $key => $submission) { - if (substr($submission['userId'], 0, 10) === 'anon-user-') { - // Anonymous User - // TRANSLATORS On Results when listing the single Responses to the form, this text is shown as heading of the Response. - $submissions[$key]['userDisplayName'] = $this->l10n->t('Anonymous response'); - } else { - $userEntity = $this->userManager->get($submission['userId']); - - if ($userEntity instanceof IUser) { - $submissions[$key]['userDisplayName'] = $userEntity->getDisplayName(); - } else { - // Fallback, should not occur regularly. - $submissions[$key]['userDisplayName'] = $submission['userId']; - } - } - } - - $response = [ - 'submissions' => $submissions, - 'questions' => $questions, - ]; - - return new DataResponse($response); - } - - /** - * @CORS - * @NoAdminRequired - * @PublicPage - * - * Uploads a temporary files to the server during form filling - * - * @return Response - */ - public function uploadFilesLegacy(int $formId, int $questionId, string $shareHash = ''): Response { - $this->logger->debug( - 'Uploading files for formId: {formId}, questionId: {questionId}', - ['formId' => $formId, 'questionId' => $questionId] - ); - - $uploadedFiles = []; - foreach ($this->request->getUploadedFile('files') as $key => $files) { - foreach ($files as $i => $value) { - $uploadedFiles[$i][$key] = $value; - } - } - - if (!count($uploadedFiles)) { - throw new OCSBadRequestException('No files provided'); - } - - $form = $this->loadFormForSubmission($formId, $shareHash); - - if (!$this->formsService->canSubmit($form)) { - throw new OCSForbiddenException('Already submitted'); - } - - try { - $question = $this->questionMapper->findById($questionId); - } catch (IMapperException $e) { - $this->logger->debug('Could not find question with id {questionId}', ['questionId' => $questionId]); - throw new OCSBadRequestException(previous: $e instanceof \Exception ? $e : null); - } - - $path = $this->formsService->getTemporaryUploadedFilePath($form, $question); - - $response = []; - foreach ($uploadedFiles as $uploadedFile) { - $error = $uploadedFile['error'] ?? 0; - if ($error !== UPLOAD_ERR_OK) { - $this->logger->error( - 'Failed to get the uploaded file. PHP file upload error code: ' . $error, - ['file_name' => $uploadedFile['name']] - ); - - throw new OCSBadRequestException(sprintf('Failed to upload the file "%s".', $uploadedFile['name'])); - } - - if (!is_uploaded_file($uploadedFile['tmp_name'])) { - throw new OCSBadRequestException('Invalid file provided'); - } - - $userFolder = $this->rootFolder->getUserFolder($form->getOwnerId()); - $userFolder->getStorage()->verifyPath($path, $uploadedFile['name']); - - $extraSettings = $question->getExtraSettings(); - if (($extraSettings['maxFileSize'] ?? 0) > 0 && $uploadedFile['size'] > $extraSettings['maxFileSize']) { - throw new OCSBadRequestException(sprintf('File size exceeds the maximum allowed size of %s bytes.', $extraSettings['maxFileSize'])); - } - - if (!empty($extraSettings['allowedFileTypes']) || !empty($extraSettings['allowedFileExtensions'])) { - $mimeType = $this->mimeTypeDetector->detectContent($uploadedFile['tmp_name']); - $aliases = $this->mimeTypeDetector->getAllAliases(); - - $valid = false; - foreach ($extraSettings['allowedFileTypes'] ?? [] as $allowedFileType) { - if (str_starts_with($aliases[$mimeType] ?? '', $allowedFileType)) { - $valid = true; - break; - } - } - - if (!$valid && !empty($extraSettings['allowedFileExtensions'])) { - $mimeTypesPerExtension = method_exists($this->mimeTypeDetector, 'getAllMappings') - ? $this->mimeTypeDetector->getAllMappings() : []; - foreach ($extraSettings['allowedFileExtensions'] as $allowedFileExtension) { - if ( - isset($mimeTypesPerExtension[$allowedFileExtension]) - && in_array($mimeType, $mimeTypesPerExtension[$allowedFileExtension]) - ) { - $valid = true; - break; - } - } - } - - if (!$valid) { - throw new OCSBadRequestException(sprintf( - 'File type is not allowed. Allowed file types: %s', - implode(', ', array_merge($extraSettings['allowedFileTypes'] ?? [], $extraSettings['allowedFileExtensions'] ?? [])) - )); - } - } - - if ($userFolder->nodeExists($path)) { - $folder = $userFolder->get($path); - } else { - $folder = $userFolder->newFolder($path); - } - /** @var \OCP\Files\Folder $folder */ - - $fileName = $folder->getNonExistingName($uploadedFile['name']); - $file = $folder->newFile($fileName, file_get_contents($uploadedFile['tmp_name'])); - - $uploadedFileEntity = new UploadedFile(); - $uploadedFileEntity->setFormId($formId); - $uploadedFileEntity->setOriginalFileName($fileName); - $uploadedFileEntity->setFileId($file->getId()); - $uploadedFileEntity->setCreated(time()); - $this->uploadedFileMapper->insert($uploadedFileEntity); - - $response[] = [ - 'uploadedFileId' => $uploadedFileEntity->getId(), - 'fileName' => $fileName, - ]; - } - - return new DataResponse($response); - } - - /** - * @CORS - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - * - * Process a new submission - * - * @param int $formId the form id - * @param array $answers [question_id => arrayOfString] - * @param string $shareHash public share-hash -> Necessary to submit on public link-shares. - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function insertSubmissionLegacy(int $formId, array $answers, string $shareHash = ''): DataResponse { - $this->logger->debug('Inserting submission: formId: {formId}, answers: {answers}, shareHash: {shareHash}', [ - 'formId' => $formId, - 'answers' => $answers, - 'shareHash' => $shareHash, - ]); - - $form = $this->loadFormForSubmission($formId, $shareHash); - - $questions = $this->formsService->getQuestions($formId); - // Is the submission valid - $isSubmissionValid = $this->submissionService->validateSubmission($questions, $answers, $form->getOwnerId()); - if (is_string($isSubmissionValid)) { - throw new OCSBadRequestException($isSubmissionValid); - } - if ($isSubmissionValid === false) { - throw new OCSBadRequestException('At least one submitted answer is not valid'); - } - - // Create Submission - $submission = new Submission(); - $submission->setFormId($formId); - $submission->setTimestamp(time()); - - // If not logged in, anonymous, or embedded use anonID - if (!$this->currentUser || $form->getIsAnonymous()) { - $anonID = 'anon-user-' . hash('md5', strval(time() + rand())); - $submission->setUserId($anonID); - } else { - $submission->setUserId($this->currentUser->getUID()); - } - - // Does the user have permissions to submit - // This is done right before insert so we minimize race conditions for submitting on unique-submission forms - if (!$this->formsService->canSubmit($form)) { - throw new OCSForbiddenException('Already submitted'); - } - - // Insert new submission - $this->submissionMapper->insert($submission); - - // Ensure the form is unique if needed. - // If we can not submit anymore then the submission must be unique - if (!$this->formsService->canSubmit($form) && $this->submissionMapper->hasMultipleFormSubmissionsByUser($form, $submission->getUserId())) { - $this->submissionMapper->delete($submission); - throw new OCSForbiddenException('Already submitted'); - } - - // Process Answers - foreach ($answers as $questionId => $answerArray) { - // Search corresponding Question, skip processing if not found - $questionIndex = array_search($questionId, array_column($questions, 'id')); - if ($questionIndex === false) { - continue; - } - - $this->storeAnswersForQuestion($form, $submission->getId(), $questions[$questionIndex], $answerArray); - } - - $this->formMapper->update($form); - - //Create Activity - $this->formsService->notifyNewSubmission($form, $submission); - - if ($form->getFileId() !== null) { - try { - $filePath = $this->formsService->getFilePath($form); - $fileFormat = $form->getFileFormat(); - $ownerId = $form->getOwnerId(); - - $this->submissionService->writeFileToCloud($form, $filePath, $fileFormat, $ownerId); - } catch (NotFoundException $e) { - $this->logger->notice('Form {formId} linked to a file that doesn\'t exist anymore', ['formId' => $formId]); - } - } - - return new DataResponse(); - } - - /** - * @CORS - * @NoAdminRequired - * - * Delete a specific submission - * - * @param int $id the submission id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function deleteSubmissionLegacy(int $id): DataResponse { - $this->logger->debug('Delete Submission: {id}', [ - 'id' => $id, - ]); - - try { - $submission = $this->submissionMapper->findById($id); - $form = $this->formMapper->findById($submission->getFormId()); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form or submission'); - throw new OCSBadRequestException(); - } - - // 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(); - } - - // Delete submission (incl. Answers) - $this->submissionMapper->deleteById($id); - $this->formMapper->update($form); - - return new DataResponse($id); - } - - /** - * @CORS - * @NoAdminRequired - * - * Delete all submissions of a specified form - * - * @param int $formId the form id - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function deleteAllSubmissionsLegacy(int $formId): DataResponse { - $this->logger->debug('Delete all submissions to form: {formId}', [ - 'formId' => $formId, - ]); - - try { - $form = $this->formMapper->findById($formId); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSBadRequestException(); - } - - // 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(); - } - - // Delete all submissions (incl. Answers) - $this->submissionMapper->deleteByForm($formId); - $this->formMapper->update($form); - - return new DataResponse($formId); - } - - /** - * @CORS - * @NoAdminRequired - * - * Export submissions of a specified form - * - * @param string $hash the form hash - * @param string $fileFormat File format used for export - * @return DataDownloadResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function exportSubmissionsLegacy(string $hash, string $fileFormat = Constants::DEFAULT_FILE_FORMAT): DataDownloadResponse { - $this->logger->debug('Export submissions for form: {hash}', [ - 'hash' => $hash, - ]); - - try { - $form = $this->formMapper->findByHash($hash); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSNotFoundException(); - } - - if (!$this->formsService->canSeeResults($form)) { - $this->logger->debug('The current user has no permission to get the results for this form'); - throw new OCSForbiddenException(); - } - - $submissionsData = $this->submissionService->getSubmissionsData($form, $fileFormat); - $fileName = $this->formsService->getFileName($form, $fileFormat); - - return new DataDownloadResponse($submissionsData, $fileName, Constants::SUPPORTED_EXPORT_FORMATS[$fileFormat]); - } - - /** - * @CORS - * @NoAdminRequired - * - * Export Submissions to the Cloud - * - * @param string $hash of the form - * @param string $path The Cloud-Path to export to - * @param string $fileFormat File format used for export - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function exportSubmissionsToCloudLegacy(string $hash, string $path, string $fileFormat = Constants::DEFAULT_FILE_FORMAT) { - try { - $form = $this->formMapper->findByHash($hash); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSNotFoundException(); - } - - if (!$this->formsService->canSeeResults($form)) { - $this->logger->debug('The current user has no permission to get the results for this form'); - throw new OCSForbiddenException(); - } - - $file = $this->submissionService->writeFileToCloud($form, $path, $fileFormat); - - return new DataResponse($file->getName()); - } - - /** - * @NoAdminRequired - * - * @param string $hash of the form - */ - public function unlinkFileLegacy(string $hash): DataResponse { - try { - $form = $this->formMapper->findByHash($hash); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSNotFoundException(); - } - - if (!$this->formsService->canEditForm($form)) { - $this->logger->debug('User has no permissions to unlink this form from files'); - throw new OCSForbiddenException(); - } - - if (!$form->getFileId()) { - $this->logger->debug('Form not linked to file'); - throw new OCSBadRequestException(); - } - - $form->setFileId(null); - $form->setFileFormat(null); - - $this->formMapper->update($form); - - return new DataResponse($hash); - } - - /** - * @NoAdminRequired - * - * Export Submissions to the Cloud and Link the FileId to the form - * - * @param string $hash of the form - * @param string $path The Cloud-Path to export to - * @param string $fileFormat File format used for export - * @return DataResponse - * @throws OCSBadRequestException - * @throws OCSForbiddenException - */ - public function linkFileLegacy(string $hash, string $path, string $fileFormat): DataResponse { - $this->logger->debug('Linking form {hash} to file at /{path} in format {fileFormat}', [ - 'hash' => $hash, - 'path' => $path, - 'fileFormat' => $fileFormat, - ]); - - try { - $form = $this->formMapper->findByHash($hash); - } catch (IMapperException $e) { - $this->logger->debug('Could not find form'); - throw new OCSNotFoundException(); - } - - if (!$this->formsService->canEditForm($form)) { - $this->logger->debug('User has no permissions to link this form with files'); - throw new OCSForbiddenException(); - } - - $file = $this->submissionService->writeFileToCloud($form, $path, $fileFormat); - - $form->setFileId($file->getId()); - $form->setFileFormat($fileFormat); - - $this->formMapper->update($form); - - $filePath = $this->formsService->getFilePath($form); - - return new DataResponse([ - 'fileId' => $file->getId(), - 'fileFormat' => $fileFormat, - 'fileName' => $file->getName(), - 'filePath' => $filePath, - ]); - } - // private functions /** diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php index a5550cd9e..e3ee72478 100644 --- a/lib/Controller/ShareApiController.php +++ b/lib/Controller/ShareApiController.php @@ -336,258 +336,6 @@ public function deleteShare(int $formId, int $shareId): DataResponse { 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 @@ public function updateShareLegacy(int $id, array $keyValuePairs): DataResponse { * @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; } diff --git a/tests/Integration/Api/ApiV2Test.php b/tests/Integration/Api/ApiV2Test.php deleted file mode 100644 index d7a558042..000000000 --- a/tests/Integration/Api/ApiV2Test.php +++ /dev/null @@ -1,1444 +0,0 @@ - - * - * @author Jonas Rittershofer - * - * @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 . - * - */ -namespace OCA\Forms\Tests\Integration\Api; - -use GuzzleHttp\Client; -use GuzzleHttp\Exception\ClientException; - -use OCA\Forms\Constants; -use OCA\Forms\Tests\Integration\IntegrationBase; - -/** - * @group DB - */ -class ApiV2Test extends IntegrationBase { - /** @var GuzzleHttp\Client */ - private $http; - - protected array $users = [ - 'test' => 'Test user', - ]; - - /** - * Store Test Forms Array. - * Necessary as function due to object type-casting. - */ - private function setTestForms() { - $this->testForms = [ - [ - 'hash' => '0123456789abcdef', - 'title' => 'Title of a Form', - 'description' => 'Just a simple form.', - 'owner_id' => 'test', - 'access_enum' => 0, - 'created' => 12345, - 'expires' => 0, - 'state' => 0, - 'is_anonymous' => false, - 'submit_multiple' => false, - 'show_expiration' => false, - 'last_updated' => 123456789, - 'submission_message' => 'Back to website', - 'file_id' => null, - 'file_format' => null, - 'questions' => [ - [ - 'type' => 'short', - 'text' => 'First Question?', - 'description' => 'Please answer this.', - 'isRequired' => true, - 'name' => '', - 'order' => 1, - 'options' => [], - 'accept' => [], - 'extraSettings' => [] - ], - [ - 'type' => 'multiple_unique', - 'text' => 'Second Question?', - 'description' => '', - 'isRequired' => false, - 'name' => 'city', - 'order' => 2, - 'options' => [ - [ - 'text' => 'Option 1', - 'order' => 1 - ], - [ - 'text' => 'Option 2', - 'order' => 2 - ], - [ - 'text' => '', - 'order' => 3 - ] - ], - 'accept' => [], - 'extraSettings' => [ - 'shuffleOptions' => true - ] - ], - [ - 'type' => 'file', - 'text' => 'File Question?', - 'description' => '', - 'isRequired' => false, - 'name' => 'file', - 'order' => 3, - 'options' => [], - 'accept' => ['.txt'], - 'extraSettings' => [ - 'allowedFileExtensions' => ['txt'], - 'maxAllowedFilesCount' => 1, - 'maxFileSize' => 1024, - ], - ], - ], - 'shares' => [ - [ - 'shareType' => 0, - 'shareWith' => 'user1', - 'permissions' => ['submit', 'results'], - ], - [ - 'shareType' => 3, - 'shareWith' => 'shareHash', - 'permissions' => ['submit'], - ], - ], - 'submissions' => [ - [ - 'userId' => 'user1', - 'timestamp' => 123456, - 'answers' => [ - [ - 'questionIndex' => 0, - 'text' => 'This is a short answer.' - ], - [ - 'questionIndex' => 1, - 'text' => 'Option 1' - ] - ] - ], - [ - 'userId' => 'user2', - 'timestamp' => 12345, - 'answers' => [ - [ - 'questionIndex' => 0, - 'text' => 'This is another short answer.' - ], - [ - 'questionIndex' => 1, - 'text' => 'Option 2' - ] - ] - ], - [ - 'userId' => 'user3', - 'timestamp' => 1234, - 'answers' => [ - [ - 'questionIndex' => 0, - 'text' => '' - ] - ] - ] - ] - ], - [ - 'hash' => 'abcdefghij123456', - 'title' => 'Title of a second Form', - 'description' => '', - 'owner_id' => 'someUser', - 'access_enum' => 2, - 'created' => 12345, - 'expires' => 0, - 'state' => 0, - 'is_anonymous' => false, - 'submit_multiple' => false, - 'show_expiration' => false, - 'last_updated' => 123456789, - 'submission_message' => '', - 'file_id' => null, - 'file_format' => null, - 'questions' => [ - [ - 'type' => 'short', - 'text' => 'Third Question?', - 'description' => '', - 'isRequired' => false, - 'name' => '', - 'order' => 1, - 'options' => [], - 'accept' => [], - 'extraSettings' => [] - ], - ], - 'shares' => [ - [ - 'shareType' => 0, - 'shareWith' => 'user2', - ], - ], - 'submissions' => [] - ], - [ - 'hash' => 'zyxwvutsrq654321', - 'title' => 'Title of a third Form', - 'description' => '', - 'owner_id' => 'test', - 'access_enum' => 2, - 'created' => 12345, - 'expires' => 0, - 'state' => 0, - 'is_anonymous' => false, - 'submit_multiple' => false, - 'show_expiration' => false, - 'last_updated' => 123456789, - 'submission_message' => '', - 'file_id' => 12, - 'file_format' => 'csv', - 'questions' => [ - [ - 'type' => 'short', - 'text' => 'Third Question?', - 'description' => '', - 'isRequired' => false, - 'name' => '', - 'order' => 1, - 'options' => [], - 'accept' => [], - 'extraSettings' => [] - ], - ], - 'shares' => [ - [ - 'shareType' => 0, - 'shareWith' => 'user2', - ], - ], - 'submissions' => [] - ], - ]; - } - - /** - * Set up test environment. - * Writing testforms into db, preparing http request - */ - public function setUp(): void { - $this->setTestForms(); - $this->users = [ - 'test' => 'Test Displayname', - 'user1' => 'User No. 1', - ]; - - parent::setUp(); - - // Set up http Client - $this->http = new Client([ - 'base_uri' => 'http://localhost:8080/ocs/v2.php/apps/forms/', - 'auth' => ['test', 'test'], - 'headers' => [ - 'OCS-ApiRequest' => 'true', - 'Accept' => 'application/json' - ], - ]); - } - - public function tearDown(): void { - parent::tearDown(); - } - - // Small Wrapper for OCS-Response - private function OcsResponse2Data($resp) { - $arr = json_decode($resp->getBody()->getContents(), true); - return $arr['ocs']['data']; - } - - // Unset Id, as we can not control it on the tests. - private function arrayUnsetId(array $arr): array { - foreach ($arr as $index => $elem) { - unset($arr[$index]['id']); - } - return $arr; - } - - public function dataGetForms() { - return [ - 'getTestforms' => [ - 'expected' => [ - [ - 'hash' => '0123456789abcdef', - 'title' => 'Title of a Form', - 'expires' => 0, - 'state' => 0, - 'lastUpdated' => 123456789, - 'permissions' => Constants::PERMISSION_ALL, - 'partial' => true, - 'submissionCount' => 3, - ], - [ - 'hash' => 'zyxwvutsrq654321', - 'title' => 'Title of a third Form', - 'expires' => 0, - 'state' => 0, - 'lastUpdated' => 123456789, - 'permissions' => Constants::PERMISSION_ALL, - 'partial' => true, - 'submissionCount' => 0, - ] - ] - ] - ]; - } - /** - * @dataProvider dataGetForms - * - * @param array $expected - */ - public function testGetForms(array $expected): void { - $resp = $this->http->request('GET', 'api/v2.4/forms'); - - $data = $this->OcsResponse2Data($resp); - $data = $this->arrayUnsetId($data); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataGetSharedForms() { - return [ - 'getTestforms' => [ - 'expected' => [ - [ - 'hash' => 'abcdefghij123456', - 'title' => 'Title of a second Form', - 'expires' => 0, - 'state' => 0, - 'lastUpdated' => 123456789, - 'permissions' => [ - 'submit' - ], - 'partial' => true - ], - ] - ] - ]; - } - /** - * @dataProvider dataGetSharedForms - * - * @param array $expected - */ - public function testGetSharedForms(array $expected): void { - $resp = $this->http->request('GET', 'api/v2.4/shared_forms'); - - $data = $this->OcsResponse2Data($resp); - $data = $this->arrayUnsetId($data); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataGetPartialForm() { - return [ - 'getPartialForm' => [ - 'expected' => [ - 'hash' => 'abcdefghij123456', - 'title' => 'Title of a second Form', - 'expires' => 0, - 'state' => 0, - 'lastUpdated' => 123456789, - 'permissions' => [ - 'submit' - ], - 'partial' => true - ] - ] - ]; - } - /** - * @dataProvider dataGetPartialForm - * - * @param array $expected - */ - public function testGetPartialForm(array $expected): void { - $resp = $this->http->request('GET', "api/v2.1/partial_form/{$this->testForms[1]['hash']}"); - - $data = $this->OcsResponse2Data($resp); - unset($data['id']); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataGetNewForm() { - return [ - 'getNewForm' => [ - 'expected' => [ - // 'hash' => Some random, cannot be checked. - 'title' => '', - 'description' => '', - 'ownerId' => 'test', - // 'created' => time() can not be checked exactly - 'access' => [ - 'permitAllUsers' => false, - 'showToAllUsers' => false - ], - 'expires' => 0, - 'state' => 0, - 'isAnonymous' => false, - 'submitMultiple' => false, - 'showExpiration' => false, - // 'lastUpdated' => time() can not be checked exactly - 'canSubmit' => true, - 'permissions' => Constants::PERMISSION_ALL, - 'questions' => [], - 'shares' => [], - 'submissionCount' => 0, - 'submissionMessage' => null, - 'fileId' => null, - 'fileFormat' => null, - ] - ] - ]; - } - /** - * @dataProvider dataGetNewForm - * - * @param array $expected - */ - public function testGetNewForm(array $expected): void { - $resp = $this->http->request('POST', 'api/v2.4/form'); - $data = $this->OcsResponse2Data($resp); - - // Store for deletion on tearDown - $this->testForms[] = $data; - - // Cannot control id - unset($data['id']); - // Check general behaviour of hash - $this->assertMatchesRegularExpression('/^[a-zA-Z0-9]{16}$/', $data['hash']); - unset($data['hash']); - // Check general behaviour of created (Created in the last 10 seconds) - $this->assertEqualsWithDelta(time(), $data['created'], 10); - unset($data['created']); - // Check general behaviour of lastUpdated (Last update in the last 10 seconds) - $this->assertEqualsWithDelta(time(), $data['lastUpdated'], 10); - unset($data['lastUpdated']); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataGetFullForm() { - return [ - 'getFullForm' => [ - 'expected' => [ - 'hash' => '0123456789abcdef', - 'title' => 'Title of a Form', - 'description' => 'Just a simple form.', - 'ownerId' => 'test', - 'created' => 12345, - 'access' => [ - 'permitAllUsers' => false, - 'showToAllUsers' => false - ], - 'expires' => 0, - 'state' => 0, - 'isAnonymous' => false, - 'submitMultiple' => false, - 'showExpiration' => false, - 'lastUpdated' => 123456789, - 'canSubmit' => true, - 'permissions' => Constants::PERMISSION_ALL, - 'submissionMessage' => 'Back to website', - 'questions' => [ - [ - 'type' => 'short', - 'text' => 'First Question?', - 'isRequired' => true, - 'name' => '', - 'order' => 1, - 'options' => [], - 'accept' => [], - 'description' => 'Please answer this.', - 'extraSettings' => [] - ], - [ - 'type' => 'multiple_unique', - 'text' => 'Second Question?', - 'isRequired' => false, - 'name' => 'city', - 'order' => 2, - 'options' => [ - [ - 'text' => 'Option 1', - 'order' => 1, - ], - [ - 'text' => 'Option 2', - 'order' => 2, - ], - [ - 'text' => '', - 'order' => 3, - ] - ], - 'accept' => [], - 'description' => '', - 'extraSettings' => [ - 'shuffleOptions' => true, - ] - ], - [ - 'type' => 'file', - 'text' => 'File Question?', - 'isRequired' => false, - 'name' => 'file', - 'order' => 3, - 'options' => [], - 'accept' => ['.txt'], - 'description' => '', - 'extraSettings' => [ - 'allowedFileExtensions' => ['txt'], - 'maxAllowedFilesCount' => 1, - 'maxFileSize' => 1024, - ], - ], - ], - 'shares' => [ - [ - 'shareType' => 0, - 'shareWith' => 'user1', - 'permissions' => ['submit', 'results'], - 'displayName' => 'User No. 1' - ], - [ - 'shareType' => 3, - 'shareWith' => 'shareHash', - 'permissions' => ['submit'], - 'displayName' => '' - ], - ], - 'submissionCount' => 3, - 'fileId' => null, - 'fileFormat' => null, - ] - ] - ]; - } - /** - * @dataProvider dataGetFullForm - * - * @param array $expected - */ - public function testGetFullForm(array $expected): void { - $resp = $this->http->request('GET', "api/v2.1/form/{$this->testForms[0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - // Cannot control ids, but check general consistency. - foreach ($data['questions'] as $qIndex => $question) { - $this->assertEquals($data['id'], $question['formId']); - unset($data['questions'][$qIndex]['formId']); - - foreach ($question['options'] as $oIndex => $option) { - $this->assertEquals($question['id'], $option['questionId']); - unset($data['questions'][$qIndex]['options'][$oIndex]['questionId']); - unset($data['questions'][$qIndex]['options'][$oIndex]['id']); - } - unset($data['questions'][$qIndex]['id']); - } - foreach ($data['shares'] as $sIndex => $share) { - $this->assertEquals($data['id'], $share['formId']); - unset($data['shares'][$sIndex]['formId']); - unset($data['shares'][$sIndex]['id']); - } - unset($data['id']); - - // Allow a 10 second diff for lastUpdated between expectation and data - $this->assertEqualsWithDelta($expected['lastUpdated'], $data['lastUpdated'], 10); - unset($data['lastUpdated']); - unset($expected['lastUpdated']); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataCloneForm() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - // Compared to full form expected, update changed properties - $fullFormExpected['title'] = 'Title of a Form - Copy'; - $fullFormExpected['shares'] = []; - $fullFormExpected['submissionCount'] = 0; - // Compared to full form expected, unset unpredictable properties. These will be checked logically. - unset($fullFormExpected['id']); - unset($fullFormExpected['hash']); - unset($fullFormExpected['created']); - unset($fullFormExpected['lastUpdated']); - foreach ($fullFormExpected['questions'] as $qIndex => $question) { - unset($fullFormExpected['questions'][$qIndex]['formId']); - } - - return [ - 'updateFormProps' => [ - 'expected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataCloneForm - * - * @param array $expected - */ - public function testCloneForm(array $expected): void { - $resp = $this->http->request('POST', "api/v2.1/form/clone/{$this->testForms[0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - // Store for deletion on tearDown - $this->testForms[] = $data; - - // Cannot control ids, but check general consistency. - foreach ($data['questions'] as $qIndex => $question) { - $this->assertEquals($data['id'], $question['formId']); - unset($data['questions'][$qIndex]['formId']); - - foreach ($question['options'] as $oIndex => $option) { - $this->assertEquals($question['id'], $option['questionId']); - unset($data['questions'][$qIndex]['options'][$oIndex]['questionId']); - unset($data['questions'][$qIndex]['options'][$oIndex]['id']); - } - unset($data['questions'][$qIndex]['id']); - } - foreach ($data['shares'] as $sIndex => $share) { - $this->assertEquals($data['id'], $share['formId']); - unset($data['shares'][$sIndex]['formId']); - unset($data['shares'][$sIndex]['id']); - } - // Check not just returning source-form (id must differ). - $this->assertGreaterThan($this->testForms[0]['id'], $data['id']); - unset($data['id']); - - // Check general behaviour of hash - $this->assertMatchesRegularExpression('/^[a-zA-Z0-9]{16}$/', $data['hash']); - unset($data['hash']); - // Check general behaviour of created (Created in the last 10 seconds) - $this->assertTrue(time() - $data['created'] < 10); - unset($data['created']); - // Check general behaviour of lastUpdated (Last update in the last 10 seconds) - $this->assertTrue(time() - $data['lastUpdated'] < 10); - unset($data['lastUpdated']); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataUpdateFormProperties() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - $fullFormExpected['title'] = 'This is my NEW Title!'; - $fullFormExpected['access'] = [ - 'permitAllUsers' => true, - 'showToAllUsers' => true - ]; - return [ - 'updateFormProps' => [ - 'expected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataUpdateFormProperties - * - * @param array $expected - */ - public function testUpdateFormProperties(array $expected): void { - $resp = $this->http->request('PATCH', 'api/v2.4/form/update', [ - 'json' => [ - 'id' => $this->testForms[0]['id'], - 'keyValuePairs' => [ - 'title' => 'This is my NEW Title!', - 'access' => [ - 'permitAllUsers' => true, - 'showToAllUsers' => true - ] - ] - ] - ]); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['id'], $data); - - $expected['lastUpdated'] = time(); - - // Check if form equals updated form. - $this->testGetFullForm($expected); - } - - public function testDeleteForm() { - $resp = $this->http->request('DELETE', "api/v2.1/form/{$this->testForms[0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['id'], $data); - - // Check if not existent anymore. - try { - $this->http->request('GET', "api/v2.1/form/{$this->testForms[0]['id']}"); - } catch (ClientException $e) { - $resp = $e->getResponse(); - } - $this->assertEquals(400, $resp->getStatusCode()); - } - - public function dataCreateNewQuestion() { - return [ - 'newQuestion' => [ - 'expected' => [ - // 'formId' => 3, // Checked during test - // 'order' => 3, // Checked during test - 'type' => 'short', - 'isRequired' => false, - 'text' => 'Already some Question?', - 'name' => '', - 'options' => [], - 'accept' => [], - 'description' => '', - 'extraSettings' => [], - ] - ], - 'emptyQuestion' => [ - 'expected' => [ - // 'formId' => 3, // Checked during test - // 'order' => 3, // Checked during test - 'type' => 'short', - 'isRequired' => false, - 'text' => '', - 'name' => '', - 'options' => [], - 'accept' => [], - 'description' => '', - 'extraSettings' => [], - ] - ] - ]; - } - /** - * @dataProvider dataCreateNewQuestion - * - * @param array $expected - */ - public function testCreateNewQuestion(array $expected): void { - $resp = $this->http->request('POST', 'api/v2.4/question', [ - 'json' => [ - 'formId' => $this->testForms[0]['id'], - 'type' => 'short', - 'text' => $expected['text'] - ] - ]); - $data = $this->OcsResponse2Data($resp); - - // Store for deletion on tearDown - $this->testForms[0]['questions'][] = $data; - - // Check formId & order - $this->assertEquals($this->testForms[0]['id'], $data['formId']); - unset($data['formId']); - $this->assertEquals(sizeof($this->testForms[0]['questions']), $data['order']); - unset($data['order']); - unset($data['id']); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataUpdateQuestionProperties() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - $fullFormExpected['questions'][0]['text'] = 'Still first Question!'; - $fullFormExpected['questions'][0]['isRequired'] = false; - - return [ - 'updateQuestionProps' => [ - 'fullFormExpected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataUpdateQuestionProperties - * - * @param array $fullFormExpected - */ - public function testUpdateQuestionProperties(array $fullFormExpected): void { - $resp = $this->http->request('PATCH', 'api/v2.4/question/update', [ - 'json' => [ - 'id' => $this->testForms[0]['questions'][0]['id'], - 'keyValuePairs' => [ - 'isRequired' => false, - 'text' => 'Still first Question!' - ] - ] - ]); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['questions'][0]['id'], $data); - - $fullFormExpected['lastUpdated'] = time(); - - // Check if form equals updated form. - $this->testGetFullForm($fullFormExpected); - } - - public function dataReorderQuestions() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - $fullFormExpected['questions'][0]['order'] = 2; - $fullFormExpected['questions'][1]['order'] = 1; - - // Exchange questions, as they will be returned in new order. - $tmp = $fullFormExpected['questions'][0]; - $fullFormExpected['questions'][0] = $fullFormExpected['questions'][1]; - $fullFormExpected['questions'][1] = $tmp; - - return [ - 'updateQuestionProps' => [ - 'fullFormExpected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataReorderQuestions - * - * @param array $fullFormExpected - */ - public function testReorderQuestions(array $fullFormExpected): void { - $resp = $this->http->request('PUT', 'api/v2.4/question/reorder', [ - 'json' => [ - 'formId' => $this->testForms[0]['id'], - 'newOrder' => [ - $this->testForms[0]['questions'][1]['id'], - $this->testForms[0]['questions'][0]['id'], - $this->testForms[0]['questions'][2]['id'], - ] - ] - ]); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals([ - $this->testForms[0]['questions'][0]['id'] => [ 'order' => 2 ], - $this->testForms[0]['questions'][1]['id'] => [ 'order' => 1 ], - $this->testForms[0]['questions'][2]['id'] => [ 'order' => 3 ], - ], $data); - - $fullFormExpected['lastUpdated'] = time(); - - // Check if form equals updated form. - $this->testGetFullForm($fullFormExpected); - } - - public function dataDeleteQuestion() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - array_splice($fullFormExpected['questions'], 0, 1); - $fullFormExpected['questions'][0]['order'] = 1; - $fullFormExpected['questions'][1]['order'] = 2; - - return [ - 'deleteQuestion' => [ - 'fullFormExpected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataDeleteQuestion - * - * @param array $fullFormExpected - */ - public function testDeleteQuestion(array $fullFormExpected) { - $resp = $this->http->request('DELETE', "api/v2.1/question/{$this->testForms[0]['questions'][0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['questions'][0]['id'], $data); - - $fullFormExpected['lastUpdated'] = time(); - - $this->testGetFullForm($fullFormExpected); - } - - public function testCloneQuestion() { - $resp = $this->http->request('POST', 'api/v2.4/question/clone/' . $this->testForms[0]['questions'][0]['id']); - $data = $this->OcsResponse2Data($resp); - $this->testForms[0]['questions'][] = $data; - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertNotEquals($data['id'], $this->testForms[0]['questions'][0]['id']); - - $copy = $this->testForms[0]['questions'][0]; - unset($copy['id']); - unset($copy['order']); - foreach ($copy as $key => $value) { - $this->assertEquals($value, $data[$key]); - } - } - - public function dataCreateNewOption() { - return [ - 'newOption' => [ - 'expected' => [ - // 'questionId' => Done dynamically below. - 'text' => 'A new Option.', - 'order' => 4, - ] - ] - ]; - } - /** - * @dataProvider dataCreateNewOption - * - * @param array $expected - */ - public function testCreateNewOption(array $expected): void { - $resp = $this->http->request('POST', 'api/v2.4/option', [ - 'json' => [ - 'questionId' => $this->testForms[0]['questions'][1]['id'], - 'text' => 'A new Option.' - ] - ]); - $data = $this->OcsResponse2Data($resp); - - // Store for deletion on tearDown - $this->testForms[0]['questions'][1]['options'][] = $data; - - // Check questionId - $this->assertEquals($this->testForms[0]['questions'][1]['id'], $data['questionId']); - unset($data['questionId']); - unset($data['id']); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataUpdateOptionProperties() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - $fullFormExpected['questions'][1]['options'][0]['text'] = 'New option Text.'; - - return [ - 'updateOptionProps' => [ - 'fullFormExpected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataUpdateOptionProperties - * - * @param array $fullFormExpected - */ - public function testUpdateOptionProperties(array $fullFormExpected): void { - $resp = $this->http->request('PATCH', 'api/v2.4/option/update', [ - 'json' => [ - 'id' => $this->testForms[0]['questions'][1]['options'][0]['id'], - 'keyValuePairs' => [ - 'text' => 'New option Text.' - ] - ] - ]); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['questions'][1]['options'][0]['id'], $data); - - $fullFormExpected['lastUpdated'] = time(); - - // Check if form equals updated form. - $this->testGetFullForm($fullFormExpected); - } - - public function dataDeleteOption() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - array_splice($fullFormExpected['questions'][1]['options'], 0, 1); - - return [ - 'deleteOption' => [ - 'fullFormExpected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataDeleteOption - * - * @param array $fullFormExpected - */ - public function testDeleteOption(array $fullFormExpected) { - $resp = $this->http->request('DELETE', "api/v2.1/option/{$this->testForms[0]['questions'][1]['options'][0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['questions'][1]['options'][0]['id'], $data); - - $fullFormExpected['lastUpdated'] = time(); - $fullFormExpected['questions'][1]['options'][0]['order'] = 1; - $fullFormExpected['questions'][1]['options'][1]['order'] = 2; - - $this->testGetFullForm($fullFormExpected); - } - - public function dataAddShare() { - return [ - 'addAShare' => [ - 'expected' => [ - // 'formId' => Checked dynamically - 'shareType' => 0, - 'shareWith' => 'test', - 'permissions' => ['submit'], - 'displayName' => 'Test Displayname' - ] - ] - ]; - } - /** - * @dataProvider dataAddShare - * - * @param array $expected - */ - public function testAddShare(array $expected) { - $resp = $this->http->request('POST', 'api/v2.4/share', [ - 'json' => [ - 'formId' => $this->testForms[0]['id'], - 'shareType' => 0, - 'shareWith' => 'test', - 'permissions' => ['submit'] - ] - ]); - $data = $this->OcsResponse2Data($resp); - - // Store for cleanup - $this->testForms[0]['shares'][] = $data; - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['id'], $data['formId']); - unset($data['formId']); - unset($data['id']); - $this->assertEquals($expected, $data); - } - - public function dataUpdateShare() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - $fullFormExpected['shares'][0]['permissions'] = [ Constants::PERMISSION_SUBMIT ]; - - return [ - 'deleteShare' => [ - 'fullFormExpected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataUpdateShare - * - * @param array $fullFormExpected - */ - public function testUpdateShare(array $fullFormExpected) { - $resp = $this->http->request('PATCH', 'api/v2.4/share/update', [ - 'json' => [ - 'id' => $this->testForms[0]['shares'][0]['id'], - 'keyValuePairs' => [ - 'permissions' => [ Constants::PERMISSION_SUBMIT ], - ], - ], - ]); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['shares'][0]['id'], $data); - - $fullFormExpected['lastUpdated'] = time(); - - $this->testGetFullForm($fullFormExpected); - } - - public function dataDeleteShare() { - $fullFormExpected = $this->dataGetFullForm()['getFullForm']['expected']; - array_splice($fullFormExpected['shares'], 0, 1); - - return [ - 'deleteShare' => [ - 'fullFormExpected' => $fullFormExpected - ] - ]; - } - /** - * @dataProvider dataDeleteShare - * - * @param array $fullFormExpected - */ - public function testDeleteShare(array $fullFormExpected) { - $resp = $this->http->request('DELETE', "api/v2.1/share/{$this->testForms[0]['shares'][0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['shares'][0]['id'], $data); - - $fullFormExpected['lastUpdated'] = time(); - - $this->testGetFullForm($fullFormExpected); - } - - public function dataGetSubmissions() { - return [ - 'getSubmissions' => [ - 'expected' => [ - 'submissions' => [ - [ - // 'formId' => Checked dynamically - 'userId' => 'user1', - 'userDisplayName' => 'User No. 1', - 'timestamp' => 123456, - 'answers' => [ - [ - // 'submissionId' => Checked dynamically - // 'questionId' => Checked dynamically - 'text' => 'This is a short answer.', - 'fileId' => null, - ], - [ - // 'submissionId' => Checked dynamically - // 'questionId' => Checked dynamically - 'text' => 'Option 1', - 'fileId' => null, - ] - ] - ], - [ - // 'formId' => Checked dynamically - 'userId' => 'user2', - 'userDisplayName' => 'user2', - 'timestamp' => 12345, - 'answers' => [ - [ - // 'submissionId' => Checked dynamically - // 'questionId' => Checked dynamically - 'text' => 'This is another short answer.', - 'fileId' => null, - ], - [ - // 'submissionId' => Checked dynamically - // 'questionId' => Checked dynamically - 'text' => 'Option 2', - 'fileId' => null, - ] - ] - ], - [ - // 'formId' => Checked dynamically - 'userId' => 'user3', - 'userDisplayName' => 'user3', - 'timestamp' => 1234, - 'answers' => [ - [ - // 'submissionId' => Checked dynamically - // 'questionId' => Checked dynamically - 'text' => '', - 'fileId' => null, - ] - ] - ] - ], - 'questions' => $this->dataGetFullForm()['getFullForm']['expected']['questions'] - ] - ] - ]; - } - /** - * @dataProvider dataGetSubmissions - * - * @param array $expected - */ - public function testGetSubmissions(array $expected) { - $resp = $this->http->request('GET', "api/v2.1/submissions/{$this->testForms[0]['hash']}"); - $data = $this->OcsResponse2Data($resp); - - // Cannot control ids, but check general consistency. - foreach ($data['submissions'] as $sIndex => $submission) { - $this->assertEquals($this->testForms[0]['id'], $submission['formId']); - unset($data['submissions'][$sIndex]['formId']); - - foreach ($submission['answers'] as $aIndex => $answer) { - $this->assertEquals($submission['id'], $answer['submissionId']); - $this->assertEquals($this->testForms[0]['questions'][ - $this->testForms[0]['submissions'][$sIndex]['answers'][$aIndex]['questionIndex'] - ]['id'], $answer['questionId']); - unset($data['submissions'][$sIndex]['answers'][$aIndex]['submissionId']); - unset($data['submissions'][$sIndex]['answers'][$aIndex]['questionId']); - unset($data['submissions'][$sIndex]['answers'][$aIndex]['id']); - } - unset($data['submissions'][$sIndex]['id']); - } - foreach ($data['questions'] as $qIndex => $question) { - $this->assertEquals($this->testForms[0]['id'], $question['formId']); - unset($data['questions'][$qIndex]['formId']); - - foreach ($question['options'] as $oIndex => $option) { - $this->assertEquals($question['id'], $option['questionId']); - unset($data['questions'][$qIndex]['options'][$oIndex]['questionId']); - unset($data['questions'][$qIndex]['options'][$oIndex]['id']); - } - unset($data['questions'][$qIndex]['id']); - } - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($expected, $data); - } - - public function dataExportSubmissions() { - return [ - 'exportSubmissions' => [ - 'expected' => <<<'CSV' - "User ID","User display name","Timestamp","First Question?","Second Question?","File Question?" - "","Anonymous user","1970-01-01T00:20:34+00:00","","","" - "","Anonymous user","1970-01-01T03:25:45+00:00","This is another short answer.","Option 2","" - "user1","User No. 1","1970-01-02T10:17:36+00:00","This is a short answer.","Option 1","" -CSV - ] - ]; - } - /** - * @dataProvider dataExportSubmissions - * - * @param array $expected - */ - public function testExportSubmissions(string $expected) { - $resp = $this->http->request('GET', "api/v2.4/submissions/export/{$this->testForms[0]['hash']}"); - $data = substr($resp->getBody()->getContents(), 3); // Some strange Character removed at the beginning - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals('attachment; filename="Title of a Form (responses).csv"', $resp->getHeaders()['Content-Disposition'][0]); - $this->assertEquals('text/csv;charset=UTF-8', $resp->getHeaders()['Content-type'][0]); - $arr_txt_expected = preg_split('/,/', str_replace(["\t", "\n"], '', $expected)); - $arr_txt_data = preg_split('/,/', str_replace(["\t", "\n"], '', $data)); - $this->assertEquals($arr_txt_expected, $arr_txt_data); - } - - public function testLinkFile() { - $resp = $this->http->request('POST', 'api/v2.4/form/link/csv', [ - 'json' => [ - 'hash' => $this->testForms[0]['hash'], - 'path' => '' - ]] - ); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals('csv', $data['fileFormat']); - } - - public function testUnlinkFile() { - $resp = $this->http->request('POST', 'api/v2.4/form/unlink', [ - 'json' => [ - 'hash' => $this->testForms[2]['hash'], - 'path' => '' - ]] - ); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[2]['hash'], $data); - } - - public function testExportToCloud() { - $resp = $this->http->request('POST', 'api/v2.4/submissions/export', [ - 'json' => [ - 'hash' => $this->testForms[0]['hash'], - 'path' => '' - ]] - ); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals('Title of a Form (responses).csv', $data); - } - - public function dataDeleteSubmissions() { - $submissionsExpected = $this->dataGetSubmissions()['getSubmissions']['expected']; - $submissionsExpected['submissions'] = []; - - return [ - 'deleteSubmissions' => [ - 'submissionsExpected' => $submissionsExpected - ] - ]; - } - /** - * @dataProvider dataDeleteSubmissions - * - * @param array $submissionsExpected - */ - public function testDeleteSubmissions(array $submissionsExpected) { - $resp = $this->http->request('DELETE', "api/v2.1/submissions/{$this->testForms[0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['id'], $data); - - $this->testGetSubmissions($submissionsExpected); - } - - public function dataInsertSubmission() { - $submissionsExpected = $this->dataGetSubmissions()['getSubmissions']['expected']; - $submissionsExpected['submissions'][] = [ - 'userId' => 'test' - ]; - - return [ - 'insertSubmission' => [ - 'submissionsExpected' => $submissionsExpected - ] - ]; - } - /** - * @dataProvider dataInsertSubmission - * - * @param array $submissionsExpected - */ - public function testInsertSubmission(array $submissionsExpected) { - - $uploadedFileResponse = $this->http->request('POST', - 'api/v2.5/uploadFiles/' . $this->testForms[0]['id'] . '/' . $this->testForms[0]['questions'][2]['id'], - [ - 'multipart' => [ - [ - 'name' => 'files[]', - 'contents' => 'hello world', - 'filename' => 'test.txt' - ] - ] - ]); - - $data = $this->OcsResponse2Data($uploadedFileResponse); - $uploadedFileId = $data[0]['uploadedFileId']; - - $resp = $this->http->request('POST', 'api/v2.4/submission/insert', [ - 'json' => [ - 'formId' => $this->testForms[0]['id'], - 'answers' => [ - $this->testForms[0]['questions'][0]['id'] => ['ShortAnswer!'], - $this->testForms[0]['questions'][1]['id'] => [ - $this->testForms[0]['questions'][1]['options'][0]['id'] - ], - $this->testForms[0]['questions'][2]['id'] => [['uploadedFileId' => $uploadedFileId]] - ] - ] - ]); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - - // Check stored submissions - $resp = $this->http->request('GET', "api/v2.1/submissions/{$this->testForms[0]['hash']}"); - $data = $this->OcsResponse2Data($resp); - - // Store for deletion - $this->testForms[0]['submissions'][] = $data['submissions'][0]; - - // Check Ids - foreach ($data['submissions'][0]['answers'] as $aIndex => $answer) { - $this->assertEquals($data['submissions'][0]['id'], $answer['submissionId']); - unset($data['submissions'][0]['answers'][$aIndex]['id']); - unset($data['submissions'][0]['answers'][$aIndex]['submissionId']); - } - unset($data['submissions'][0]['id']); - // Check general behaviour of timestamp (Insert in the last 10 seconds) - $this->assertTrue(time() - $data['submissions'][0]['timestamp'] < 10); - unset($data['submissions'][0]['timestamp']); - - $this->assertEquals([ - 'userId' => 'test', - 'userDisplayName' => 'Test Displayname', - 'formId' => $this->testForms[0]['id'], - 'answers' => [ - [ - 'questionId' => $this->testForms[0]['questions'][0]['id'], - 'text' => 'ShortAnswer!', - 'fileId' => null, - ], - [ - 'questionId' => $this->testForms[0]['questions'][1]['id'], - 'text' => 'Option 1', - 'fileId' => null, - ], - [ - 'questionId' => $this->testForms[0]['questions'][2]['id'], - 'text' => 'test.txt', - 'fileId' => 28, - ], - ] - ], $data['submissions'][0]); - } - - public function dataDeleteSingleSubmission() { - $submissionsExpected = $this->dataGetSubmissions()['getSubmissions']['expected']; - array_splice($submissionsExpected['submissions'], 0, 1); - - return [ - 'deleteSingleSubmission' => [ - 'submissionsExpected' => $submissionsExpected - ] - ]; - } - /** - * @dataProvider dataDeleteSingleSubmission - * - * @param array $submissionsExpected - */ - public function testDeleteSingleSubmission(array $submissionsExpected) { - $resp = $this->http->request('DELETE', "api/v2.1/submission/{$this->testForms[0]['submissions'][0]['id']}"); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals($this->testForms[0]['submissions'][0]['id'], $data); - - $this->testGetSubmissions($submissionsExpected); - } - - /** - * Test transfer owner endpoint for form - * - * Keep this test at the end as it might break other tests - */ - public function testTransferOwner() { - $resp = $this->http->request('POST', 'api/v2.4/form/transfer', [ - 'json' => [ - 'formId' => $this->testForms[0]['id'], - 'uid' => 'user1' - ], - ]); - $data = $this->OcsResponse2Data($resp); - - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals('user1', $data); - } -}; diff --git a/tests/Unit/Controller/ApiControllerTest.php b/tests/Unit/Controller/ApiControllerTest.php index 0bc7b36d6..fa27ba42d 100644 --- a/tests/Unit/Controller/ApiControllerTest.php +++ b/tests/Unit/Controller/ApiControllerTest.php @@ -63,7 +63,6 @@ function is_uploaded_file(string|bool|null $filename) { 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 @@ public function testExportSubmissionsToCloud_invalidForm() { $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 @@ private function formAccess(bool $hasUserAccess = true, bool $hasFormExpired = f ->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 @@ public function testTransferOwnerNotOwner() { ->willReturn($form); $this->expectException(OCSForbiddenException::class); - $this->apiController->transferOwnerLegacy(1, 'newOwner'); + $this->apiController->updateForm(1, ['ownerId' => 'newOwner']); } public function testTransferNewOwnerNotFound() { @@ -1059,7 +1019,7 @@ public function testTransferNewOwnerNotFound() { ->willReturn(null); $this->expectException(OCSBadRequestException::class); - $this->apiController->transferOwnerLegacy(1, 'newOwner'); + $this->apiController->updateForm(1, ['ownerId' => 'newOwner']); } public function testTransferOwner() { @@ -1079,7 +1039,7 @@ public function testTransferOwner() { ->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()); } }