зеркало из https://github.com/nextcloud/forms.git
feat: Allow to reorder options for "multiple" question type
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de> Signed-off-by: Christian Hartmann <chris-hartmann@gmx.de>
This commit is contained in:
Родитель
683a5198fe
Коммит
7aba08d095
|
@ -59,7 +59,7 @@ return [
|
||||||
// CORS Preflight
|
// CORS Preflight
|
||||||
['name' => 'api#preflightedCors', 'url' => $apiBase . '{path}', 'verb' => 'OPTIONS', 'requirements' => [
|
['name' => 'api#preflightedCors', 'url' => $apiBase . '{path}', 'verb' => 'OPTIONS', 'requirements' => [
|
||||||
'path' => '.+',
|
'path' => '.+',
|
||||||
'apiVersion' => 'v2(\.[1-4])?|v3'
|
'apiVersion' => 'v2(\.[1-5])?|v3'
|
||||||
]],
|
]],
|
||||||
|
|
||||||
// API routes v3
|
// API routes v3
|
||||||
|
@ -84,7 +84,7 @@ return [
|
||||||
// ['name' => 'api#getOption', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options/{optionId}', 'verb' => 'GET', 'requirements' => $requirements_v3],
|
// ['name' => 'api#getOption', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options/{optionId}', 'verb' => 'GET', 'requirements' => $requirements_v3],
|
||||||
['name' => 'api#updateOption', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options/{optionId}', 'verb' => 'PATCH', 'requirements' => $requirements_v3],
|
['name' => 'api#updateOption', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options/{optionId}', 'verb' => 'PATCH', 'requirements' => $requirements_v3],
|
||||||
['name' => 'api#deleteOption', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options/{optionId}', 'verb' => 'DELETE', 'requirements' => $requirements_v3],
|
['name' => 'api#deleteOption', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options/{optionId}', 'verb' => 'DELETE', 'requirements' => $requirements_v3],
|
||||||
// ['name' => 'api#reorderOptions', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options', 'verb' => 'PATCH', 'requirements' => $requirements_v3],
|
['name' => 'api#reorderOptions', 'url' => $apiBase . 'forms/{formId}/questions/{questionId}/options', 'verb' => 'PATCH', 'requirements' => $requirements_v3],
|
||||||
|
|
||||||
// Shares
|
// Shares
|
||||||
// ['name' => 'shareApi#getUserShares', 'url' => $apiBase . 'shares', 'verb' => 'GET', 'requirements' => $requirements_v3],
|
// ['name' => 'shareApi#getUserShares', 'url' => $apiBase . 'shares', 'verb' => 'GET', 'requirements' => $requirements_v3],
|
||||||
|
@ -107,113 +107,113 @@ return [
|
||||||
// Legacy v2 routes (TODO: remove with Forms v5)
|
// Legacy v2 routes (TODO: remove with Forms v5)
|
||||||
// Forms
|
// Forms
|
||||||
['name' => 'api#getFormsLegacy', 'url' => $apiBase . 'forms', 'verb' => 'GET', 'requirements' => [
|
['name' => 'api#getFormsLegacy', 'url' => $apiBase . 'forms', 'verb' => 'GET', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#newFormLegacy', 'url' => $apiBase . 'form', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#newFormLegacy', 'url' => $apiBase . 'form', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion_path' => 'v2(\.[1-4])?'
|
'apiVersion_path' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#getFormLegacy', 'url' => $apiBase . 'form/{id}', 'verb' => 'GET', 'requirements' => [
|
['name' => 'api#getFormLegacy', 'url' => $apiBase . 'form/{id}', 'verb' => 'GET', 'requirements' => [
|
||||||
'apiVersion_path' => 'v2(\.[1-4])?',
|
'apiVersion_path' => 'v2(\.[1-5])?',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#cloneFormLegacy', 'url' => $apiBase . 'form/clone/{id}', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#cloneFormLegacy', 'url' => $apiBase . 'form/clone/{id}', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#updateFormLegacy', 'url' => $apiBase . 'form/update', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#updateFormLegacy', 'url' => $apiBase . 'form/update', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#updateFormLegacy', 'url' => $apiBase . 'form/update', 'verb' => 'PATCH', 'requirements' => [
|
['name' => 'api#updateFormLegacy', 'url' => $apiBase . 'form/update', 'verb' => 'PATCH', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[2-4]'
|
'apiVersion' => 'v2\.[2-5]'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#transferOwnerLegacy', 'url' => $apiBase . 'form/transfer', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#transferOwnerLegacy', 'url' => $apiBase . 'form/transfer', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[2-4]'
|
'apiVersion' => 'v2\.[2-5]'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#deleteFormLegacy', 'url' => $apiBase . 'form/{id}', 'verb' => 'DELETE', 'requirements' => [
|
['name' => 'api#deleteFormLegacy', 'url' => $apiBase . 'form/{id}', 'verb' => 'DELETE', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#getPartialFormLegacy', 'url' => $apiBase . 'partial_form/{hash}', 'verb' => 'GET', 'requirements' => [
|
['name' => 'api#getPartialFormLegacy', 'url' => $apiBase . 'partial_form/{hash}', 'verb' => 'GET', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'hash' => '[a-zA-Z0-9]{16}'
|
'hash' => '[a-zA-Z0-9]{16}'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#getSharedFormsLegacy', 'url' => $apiBase . 'shared_forms', 'verb' => 'GET', 'requirements' => [
|
['name' => 'api#getSharedFormsLegacy', 'url' => $apiBase . 'shared_forms', 'verb' => 'GET', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
|
|
||||||
// Questions
|
// Questions
|
||||||
['name' => 'api#newQuestionLegacy', 'url' => $apiBase . 'question', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#newQuestionLegacy', 'url' => $apiBase . 'question', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
// TODO: Remove POST in next API release
|
// TODO: Remove POST in next API release
|
||||||
['name' => 'api#updateQuestionLegacy', 'url' => $apiBase . 'question/update', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#updateQuestionLegacy', 'url' => $apiBase . 'question/update', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#updateQuestionLegacy', 'url' => $apiBase . 'question/update', 'verb' => 'PATCH', 'requirements' => [
|
['name' => 'api#updateQuestionLegacy', 'url' => $apiBase . 'question/update', 'verb' => 'PATCH', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[2-4]'
|
'apiVersion' => 'v2\.[2-5]'
|
||||||
]],
|
]],
|
||||||
// TODO: Remove POST in next API release
|
// TODO: Remove POST in next API release
|
||||||
['name' => 'api#reorderQuestionsLegacy', 'url' => $apiBase . 'question/reorder', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#reorderQuestionsLegacy', 'url' => $apiBase . 'question/reorder', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#reorderQuestionsLegacy', 'url' => $apiBase . 'question/reorder', 'verb' => 'PUT', 'requirements' => [
|
['name' => 'api#reorderQuestionsLegacy', 'url' => $apiBase . 'question/reorder', 'verb' => 'PUT', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[2-4]'
|
'apiVersion' => 'v2\.[2-5]'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#deleteQuestionLegacy', 'url' => $apiBase . 'question/{id}', 'verb' => 'DELETE', 'requirements' => [
|
['name' => 'api#deleteQuestionLegacy', 'url' => $apiBase . 'question/{id}', 'verb' => 'DELETE', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#cloneQuestionLegacy', 'url' => $apiBase . 'question/clone/{id}', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#cloneQuestionLegacy', 'url' => $apiBase . 'question/clone/{id}', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[3-4]',
|
'apiVersion' => 'v2\.[3-5]',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
['name' => 'api#newOptionLegacy', 'url' => $apiBase . 'option', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#newOptionLegacy', 'url' => $apiBase . 'option', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
// TODO: Remove POST in next API release
|
// TODO: Remove POST in next API release
|
||||||
['name' => 'api#updateOptionLegacy', 'url' => $apiBase . 'option/update', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#updateOptionLegacy', 'url' => $apiBase . 'option/update', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#updateOptionLegacy', 'url' => $apiBase . 'option/update', 'verb' => 'PATCH', 'requirements' => [
|
['name' => 'api#updateOptionLegacy', 'url' => $apiBase . 'option/update', 'verb' => 'PATCH', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[2-4]'
|
'apiVersion' => 'v2\.[2-5]'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#deleteOptionLegacy', 'url' => $apiBase . 'option/{id}', 'verb' => 'DELETE', 'requirements' => [
|
['name' => 'api#deleteOptionLegacy', 'url' => $apiBase . 'option/{id}', 'verb' => 'DELETE', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
|
|
||||||
// Shares
|
// Shares
|
||||||
['name' => 'shareApi#newShareLegacy', 'url' => $apiBase . 'share', 'verb' => 'POST', 'requirements' => [
|
['name' => 'shareApi#newShareLegacy', 'url' => $apiBase . 'share', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'shareApi#deleteShareLegacy', 'url' => $apiBase . 'share/{id}', 'verb' => 'DELETE', 'requirements' => [
|
['name' => 'shareApi#deleteShareLegacy', 'url' => $apiBase . 'share/{id}', 'verb' => 'DELETE', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
// TODO: Remove POST in next API release
|
// TODO: Remove POST in next API release
|
||||||
['name' => 'shareApi#updateShareLegacy', 'url' => $apiBase . 'share/update', 'verb' => 'POST', 'requirements' => [
|
['name' => 'shareApi#updateShareLegacy', 'url' => $apiBase . 'share/update', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[1-4]'
|
'apiVersion' => 'v2\.[1-5]'
|
||||||
]],
|
]],
|
||||||
['name' => 'shareApi#updateShareLegacy', 'url' => $apiBase . 'share/update', 'verb' => 'PATCH', 'requirements' => [
|
['name' => 'shareApi#updateShareLegacy', 'url' => $apiBase . 'share/update', 'verb' => 'PATCH', 'requirements' => [
|
||||||
'apiVersion' => 'v2\.[2-4]'
|
'apiVersion' => 'v2\.[2-5]'
|
||||||
]],
|
]],
|
||||||
|
|
||||||
// Submissions
|
// Submissions
|
||||||
['name' => 'api#getSubmissionsLegacy', 'url' => $apiBase . 'submissions/{hash}', 'verb' => 'GET', 'requirements' => [
|
['name' => 'api#getSubmissionsLegacy', 'url' => $apiBase . 'submissions/{hash}', 'verb' => 'GET', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'hash' => '[a-zA-Z0-9]{16}'
|
'hash' => '[a-zA-Z0-9]{16}'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#exportSubmissionsLegacy', 'url' => $apiBase . 'submissions/export/{hash}', 'verb' => 'GET', 'requirements' => [
|
['name' => 'api#exportSubmissionsLegacy', 'url' => $apiBase . 'submissions/export/{hash}', 'verb' => 'GET', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'hash' => '[a-zA-Z0-9]{16}'
|
'hash' => '[a-zA-Z0-9]{16}'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#exportSubmissionsToCloudLegacy', 'url' => $apiBase . 'submissions/export', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#exportSubmissionsToCloudLegacy', 'url' => $apiBase . 'submissions/export', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#deleteAllSubmissionsLegacy', 'url' => $apiBase . 'submissions/{formId}', 'verb' => 'DELETE', 'requirements' => [
|
['name' => 'api#deleteAllSubmissionsLegacy', 'url' => $apiBase . 'submissions/{formId}', 'verb' => 'DELETE', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'formId' => '\d+'
|
'formId' => '\d+'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#uploadFilesLegacy', 'url' => $apiBase . 'uploadFiles/{formId}/{questionId}', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#uploadFilesLegacy', 'url' => $apiBase . 'uploadFiles/{formId}/{questionId}', 'verb' => 'POST', 'requirements' => [
|
||||||
|
@ -222,19 +222,19 @@ return [
|
||||||
'questionId' => '\d+'
|
'questionId' => '\d+'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#insertSubmissionLegacy', 'url' => $apiBase . 'submission/insert', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#insertSubmissionLegacy', 'url' => $apiBase . 'submission/insert', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?'
|
'apiVersion' => 'v2(\.[1-5])?'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#deleteSubmissionLegacy', 'url' => $apiBase . 'submission/{id}', 'verb' => 'DELETE', 'requirements' => [
|
['name' => 'api#deleteSubmissionLegacy', 'url' => $apiBase . 'submission/{id}', 'verb' => 'DELETE', 'requirements' => [
|
||||||
'apiVersion' => 'v2(\.[1-4])?',
|
'apiVersion' => 'v2(\.[1-5])?',
|
||||||
'id' => '\d+'
|
'id' => '\d+'
|
||||||
]],
|
]],
|
||||||
// Submissions linking with file in cloud
|
// Submissions linking with file in cloud
|
||||||
['name' => 'api#linkFileLegacy', 'url' => $apiBase . 'form/link/{fileFormat}', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#linkFileLegacy', 'url' => $apiBase . 'form/link/{fileFormat}', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2.4',
|
'apiVersion' => 'v2.[4-5]',
|
||||||
'fileFormat' => 'csv|ods|xlsx'
|
'fileFormat' => 'csv|ods|xlsx'
|
||||||
]],
|
]],
|
||||||
['name' => 'api#unlinkFileLegacy', 'url' => $apiBase . 'form/unlink', 'verb' => 'POST', 'requirements' => [
|
['name' => 'api#unlinkFileLegacy', 'url' => $apiBase . 'form/unlink', 'verb' => 'POST', 'requirements' => [
|
||||||
'apiVersion' => 'v2.4',
|
'apiVersion' => 'v2.[4-5]',
|
||||||
]]
|
]]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
73
docs/API.md
73
docs/API.md
|
@ -41,6 +41,8 @@ This file contains the API-Documentation. For more information on the returned D
|
||||||
|
|
||||||
- In API version 2.5 the following endpoints were introduced:
|
- 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
|
- `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:
|
- 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/link/{fileFormat}` to link form to a file
|
||||||
- `POST /api/2.4/form/unlink` to unlink form from a file
|
- `POST /api/2.4/form/unlink` to unlink form from a file
|
||||||
|
@ -58,7 +60,7 @@ This file contains the API-Documentation. For more information on the returned D
|
||||||
|
|
||||||
Returns condensed objects of all Forms beeing owned by the authenticated user.
|
Returns condensed objects of all Forms beeing owned by the authenticated user.
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/forms`
|
- Endpoint: `/api/v2.5/forms`
|
||||||
- Method: `GET`
|
- Method: `GET`
|
||||||
- Parameters: None
|
- Parameters: None
|
||||||
- Response: Array of condensed Form Objects, sorted as newest first.
|
- Response: Array of condensed Form Objects, sorted as newest first.
|
||||||
|
@ -98,7 +100,7 @@ Returns condensed objects of all Forms beeing owned by the authenticated user.
|
||||||
|
|
||||||
Returns condensed objects of all Forms, that are shared & shown to the authenticated user and that have not expired yet.
|
Returns condensed objects of all Forms, that are shared & shown to the authenticated user and that have not expired yet.
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/shared_forms`
|
- Endpoint: `/api/v2.5/shared_forms`
|
||||||
- Method: `GET`
|
- Method: `GET`
|
||||||
- Parameters: None
|
- Parameters: None
|
||||||
- Response: Array of condensed Form Objects, sorted as newest first, similar to [List owned Forms](#list-owned-forms).
|
- Response: Array of condensed Form Objects, sorted as newest first, similar to [List owned Forms](#list-owned-forms).
|
||||||
|
@ -111,7 +113,7 @@ See above, 'List owned forms'
|
||||||
|
|
||||||
Returns a single partial form object, corresponding to owned/shared form-listings.
|
Returns a single partial form object, corresponding to owned/shared form-listings.
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/partial_form/{hash}`
|
- Endpoint: `/api/v2.5/partial_form/{hash}`
|
||||||
- Method: `GET`
|
- Method: `GET`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|
@ -135,7 +137,7 @@ Returns a single partial form object, corresponding to owned/shared form-listing
|
||||||
|
|
||||||
### Create a new Form
|
### Create a new Form
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/form`
|
- Endpoint: `/api/v2.5/form`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters: None
|
- Parameters: None
|
||||||
- Response: The new form object, similar to requesting an existing form.
|
- Response: The new form object, similar to requesting an existing form.
|
||||||
|
@ -148,7 +150,7 @@ See next section, 'Request full data of a form'
|
||||||
|
|
||||||
Returns the full-depth object of the requested form (without submissions).
|
Returns the full-depth object of the requested form (without submissions).
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/form/{id}`
|
- Endpoint: `/api/v2.5/form/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -196,12 +198,14 @@ Returns the full-depth object of the requested form (without submissions).
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 1"
|
"text": "Option 1",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 2"
|
"text": "Option 2",
|
||||||
|
"order": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"accept": [],
|
"accept": [],
|
||||||
|
@ -243,7 +247,7 @@ Returns the full-depth object of the requested form (without submissions).
|
||||||
|
|
||||||
Creates a clone of a form (without submissions).
|
Creates a clone of a form (without submissions).
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/form/clone/{id}`
|
- Endpoint: `/api/v2.5/form/clone/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -259,7 +263,7 @@ See section 'Request full data of a form'.
|
||||||
|
|
||||||
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.
|
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.4/form/update`
|
- Endpoint: `/api/v2.5/form/update`
|
||||||
- Method: `PATCH`
|
- Method: `PATCH`
|
||||||
- _Method: `POST` deprecated_
|
- _Method: `POST` deprecated_
|
||||||
- Parameters:
|
- Parameters:
|
||||||
|
@ -278,7 +282,7 @@ Update a single or multiple properties of a form-object. Concerns **only** the F
|
||||||
|
|
||||||
Transfer the ownership of a form to another user
|
Transfer the ownership of a form to another user
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/form/transfer`
|
- Endpoint: `/api/v2.5/form/transfer`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|
@ -294,7 +298,7 @@ Transfer the ownership of a form to another user
|
||||||
|
|
||||||
### Delete a form
|
### Delete a form
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/form/{id}`
|
- Endpoint: `/api/v2.5/form/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -308,7 +312,7 @@ Transfer the ownership of a form to another user
|
||||||
|
|
||||||
### Link a form to a file
|
### Link a form to a file
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/form/link/{fileFormat}`
|
- Endpoint: `/api/v2.5/form/link/{fileFormat}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|--------------|---------|--------------|
|
|--------------|---------|--------------|
|
||||||
|
@ -332,7 +336,7 @@ Transfer the ownership of a form to another user
|
||||||
|
|
||||||
### Unlink file from form
|
### Unlink file from form
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/form/unlink`
|
- Endpoint: `/api/v2.5/form/unlink`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|
@ -346,7 +350,7 @@ Contains only manipulative question-endpoints. To retrieve questions, request th
|
||||||
|
|
||||||
### Create a new question
|
### Create a new question
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/question`
|
- Endpoint: `/api/v2.5/question`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Optional | Description |
|
| Parameter | Type | Optional | Description |
|
||||||
|
@ -374,7 +378,7 @@ Contains only manipulative question-endpoints. To retrieve questions, request th
|
||||||
|
|
||||||
Update a single or multiple properties of a question-object.
|
Update a single or multiple properties of a question-object.
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/question/update`
|
- Endpoint: `/api/v2.5/question/update`
|
||||||
- Method: `PATCH`
|
- Method: `PATCH`
|
||||||
- _Method: `POST` deprecated_
|
- _Method: `POST` deprecated_
|
||||||
- Parameters:
|
- Parameters:
|
||||||
|
@ -393,7 +397,7 @@ Update a single or multiple properties of a question-object.
|
||||||
|
|
||||||
Reorders all Questions of a single form
|
Reorders all Questions of a single form
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/question/reorder`
|
- Endpoint: `/api/v2.5/question/reorder`
|
||||||
- Method: `PUT`
|
- Method: `PUT`
|
||||||
- _Method: `POST` deprecated_
|
- _Method: `POST` deprecated_
|
||||||
- Parameters:
|
- Parameters:
|
||||||
|
@ -420,7 +424,7 @@ Reorders all Questions of a single form
|
||||||
|
|
||||||
### Delete a question
|
### Delete a question
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/question/{id}`
|
- Endpoint: `/api/v2.5/question/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -436,7 +440,7 @@ Reorders all Questions of a single form
|
||||||
|
|
||||||
Creates a clone of a question with all its options.
|
Creates a clone of a question with all its options.
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/question/clone/{id}`
|
- Endpoint: `/api/v2.5/question/clone/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -454,7 +458,7 @@ Contains only manipulative question-endpoints. To retrieve options, request the
|
||||||
|
|
||||||
### Create a new Option
|
### Create a new Option
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/option`
|
- Endpoint: `/api/v2.5/option`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|
@ -475,7 +479,7 @@ Contains only manipulative question-endpoints. To retrieve options, request the
|
||||||
|
|
||||||
Update a single or all properties of an option-object
|
Update a single or all properties of an option-object
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/option/update`
|
- Endpoint: `/api/v2.5/option/update`
|
||||||
- Method: `PATCH`
|
- Method: `PATCH`
|
||||||
- _Method: `POST` deprecated_
|
- _Method: `POST` deprecated_
|
||||||
- Parameters:
|
- Parameters:
|
||||||
|
@ -492,7 +496,7 @@ Update a single or all properties of an option-object
|
||||||
|
|
||||||
### Delete an option
|
### Delete an option
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/option/{id}`
|
- Endpoint: `/api/v2.5/option/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -508,7 +512,7 @@ Update a single or all properties of an option-object
|
||||||
|
|
||||||
### Add a new Share
|
### Add a new Share
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/share`
|
- Endpoint: `/api/v2.5/share`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|
@ -532,7 +536,7 @@ Update a single or all properties of an option-object
|
||||||
|
|
||||||
### Delete a Share
|
### Delete a Share
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/share/{id}`
|
- Endpoint: `/api/v2.5/share/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -546,7 +550,7 @@ Update a single or all properties of an option-object
|
||||||
|
|
||||||
### Update a Share
|
### Update a Share
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/share/update`
|
- Endpoint: `/api/v2.5/share/update`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|------------------|----------|-------------|
|
|------------------|----------|-------------|
|
||||||
|
@ -569,7 +573,7 @@ Update a single or all properties of an option-object
|
||||||
|
|
||||||
Get all Submissions to a Form
|
Get all Submissions to a Form
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/submissions/{hash}`
|
- Endpoint: `/api/v2.5/submissions/{hash}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -635,17 +639,20 @@ Get all Submissions to a Form
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 1"
|
"text": "Option 1",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 27,
|
"id": 27,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 2"
|
"text": "Option 2",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 30,
|
"id": 30,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 3"
|
"text": "Option 3",
|
||||||
|
"order": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"extraSettings": {}
|
"extraSettings": {}
|
||||||
|
@ -668,7 +675,7 @@ Get all Submissions to a Form
|
||||||
|
|
||||||
Returns all submissions to the form in form of a csv-file.
|
Returns all submissions to the form in form of a csv-file.
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/submissions/export/{hash}`
|
- Endpoint: `/api/v2.5/submissions/export/{hash}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|--------------|---------|-------------|
|
|--------------|---------|-------------|
|
||||||
|
@ -687,7 +694,7 @@ Returns all submissions to the form in form of a csv-file.
|
||||||
|
|
||||||
Creates a csv file and stores it to the cloud, resp. Files-App.
|
Creates a csv file and stores it to the cloud, resp. Files-App.
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/submissions/export`
|
- Endpoint: `/api/v2.5/submissions/export`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|
@ -705,7 +712,7 @@ Creates a csv file and stores it to the cloud, resp. Files-App.
|
||||||
|
|
||||||
Delete all Submissions to a form
|
Delete all Submissions to a form
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/submissions/{formId}`
|
- Endpoint: `/api/v2.5/submissions/{formId}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
@ -739,7 +746,7 @@ Upload a files to answer before form submitting
|
||||||
|
|
||||||
Store Submission to Database
|
Store Submission to Database
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/submission/insert`
|
- Endpoint: `/api/v2.5/submission/insert`
|
||||||
- Method: `POST`
|
- Method: `POST`
|
||||||
- Parameters:
|
- Parameters:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|
@ -771,7 +778,7 @@ Store Submission to Database
|
||||||
|
|
||||||
### Delete a single Submission
|
### Delete a single Submission
|
||||||
|
|
||||||
- Endpoint: `/api/v2.4/submission/{id}`
|
- Endpoint: `/api/v2.5/submission/{id}`
|
||||||
- Url-Parameter:
|
- Url-Parameter:
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
|
|
|
@ -46,6 +46,7 @@ This file contains the API-Documentation. For more information on the returned D
|
||||||
- `GET /api/v3/forms/{formId}/questions` to get all questions of a form
|
- `GET /api/v3/forms/{formId}/questions` to get all questions of a form
|
||||||
- `GET /api/v3/forms/{formId}/questions/{questionId}` to get a single question
|
- `GET /api/v3/forms/{formId}/questions/{questionId}` to get a single question
|
||||||
- `POST /api/v3/forms/{formId}/questions/{questionId}/options` does now accept more options at once
|
- `POST /api/v3/forms/{formId}/questions/{questionId}/options` does now accept more options at once
|
||||||
|
- `PATCH /api/v3/forms/{formId}/questions/{questionId}/options/reorder` to reorder the options
|
||||||
- `POST /api/v3/forms/{formId}/submissions/files/{questionId}` to upload a file to a file question before submitting the form
|
- `POST /api/v3/forms/{formId}/submissions/files/{questionId}` to upload a file to a file question before submitting the form
|
||||||
- In API version 2.5 the following endpoints were introduced:
|
- In API version 2.5 the following endpoints were introduced:
|
||||||
- `POST /api/v2.5/uploadFiles/{formId}/{questionId}` to upload files to answer before form submitting
|
- `POST /api/v2.5/uploadFiles/{formId}/{questionId}` to upload files to answer before form submitting
|
||||||
|
@ -178,12 +179,14 @@ Returns the full-depth object of the requested form (without submissions).
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 1"
|
"text": "Option 1",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 2"
|
"text": "Option 2",
|
||||||
|
"order": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"accept": [],
|
"accept": [],
|
||||||
|
@ -304,12 +307,14 @@ Returns the questions and options of the given form (without submissions).
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 1"
|
"text": "Option 1",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 2"
|
"text": "Option 2",
|
||||||
|
"order": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"accept": [],
|
"accept": [],
|
||||||
|
@ -385,12 +390,14 @@ Returns the requested question and options of the given form (without submission
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 1"
|
"text": "Option 1",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 2"
|
"text": "Option 2"
|
||||||
|
"order": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"accept": [],
|
"accept": [],
|
||||||
|
@ -549,6 +556,22 @@ Update a single or all properties of an option-object
|
||||||
"data": 7
|
"data": 7
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Reorder options
|
||||||
|
|
||||||
|
- Endpoint: `/api/v3/forms/{formId}/questions/{questionId}/options/reorder`
|
||||||
|
- Method: `PATCH`
|
||||||
|
- Url-Parameter:
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|---------|-------------|
|
||||||
|
| _formId_ | Integer | ID of the form containing the question and option |
|
||||||
|
| _questionId_ | Integer | ID of the question, the new option will belong to |
|
||||||
|
- Parameters:
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|---------|-------------|
|
||||||
|
| _newOrder_ | Array | Array of **all** option IDs, ordered in the desired order |
|
||||||
|
- Restrictions: The Array **must** contain all option IDs corresponding to the specified question and **must not** contain any duplicates.
|
||||||
|
- Response: Array of optionIds and their corresponding order.
|
||||||
|
|
||||||
## Sharing Endpoints
|
## Sharing Endpoints
|
||||||
|
|
||||||
### Add a new Share
|
### Add a new Share
|
||||||
|
@ -685,17 +708,20 @@ Get all Submissions to a Form
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 1"
|
"text": "Option 1",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 27,
|
"id": 27,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 2"
|
"text": "Option 2",
|
||||||
|
"order": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 30,
|
"id": 30,
|
||||||
"questionId": 1,
|
"questionId": 1,
|
||||||
"text": "Option 3"
|
"text": "Option 3",
|
||||||
|
"order": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"extraSettings": {}
|
"extraSettings": {}
|
||||||
|
|
|
@ -764,12 +764,22 @@ class ApiController extends OCSController {
|
||||||
throw new OCSBadRequestException();
|
throw new OCSBadRequestException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
$addedOptions = [];
|
$addedOptions = [];
|
||||||
foreach ($optionTexts as $text) {
|
foreach ($optionTexts as $text) {
|
||||||
$option = new Option();
|
$option = new Option();
|
||||||
|
|
||||||
$option->setQuestionId($questionId);
|
$option->setQuestionId($questionId);
|
||||||
$option->setText($text);
|
$option->setText($text);
|
||||||
|
$option->setOrder($optionOrder++);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$option = $this->optionMapper->insert($option);
|
$option = $this->optionMapper->insert($option);
|
||||||
|
@ -889,11 +899,104 @@ class ApiController extends OCSController {
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->optionMapper->delete($option);
|
$this->optionMapper->delete($option);
|
||||||
|
|
||||||
|
// Reorder the remaining options
|
||||||
|
$options = array_values($this->optionMapper->findByQuestion($questionId));
|
||||||
|
foreach ($options as $order => $option) {
|
||||||
|
// Always start order with 1
|
||||||
|
$option->setOrder($order + 1);
|
||||||
|
$this->optionMapper->update($option);
|
||||||
|
}
|
||||||
|
|
||||||
$this->formMapper->update($form);
|
$this->formMapper->update($form);
|
||||||
|
|
||||||
return new DataResponse($optionId);
|
return new DataResponse($optionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder options for a given question
|
||||||
|
* @param int $formId id of form
|
||||||
|
* @param int $questionId id of question
|
||||||
|
* @param Array<int, int> $newOrder Order to use
|
||||||
|
*/
|
||||||
|
public function reorderOptions(int $formId, int $questionId, array $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();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$question = $this->questionMapper->findById($questionId);
|
||||||
|
} catch (IMapperException $e) {
|
||||||
|
$this->logger->debug('Could not find form or question', ['exception' => $e]);
|
||||||
|
throw new OCSNotFoundException('Could not find form or question');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($question->getFormId() !== $formId) {
|
||||||
|
$this->logger->debug('The given question id doesn\'t match the form.');
|
||||||
|
throw new OCSBadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = $this->optionMapper->findByQuestion($questionId);
|
||||||
|
|
||||||
|
if (sizeof($options) !== sizeof($newOrder)) {
|
||||||
|
$this->logger->debug('The length of the given array does not match the number of stored options');
|
||||||
|
throw new OCSBadRequestException('The length of the given array does not match the number of stored options');
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = []; // Clear Array of Entities
|
||||||
|
$response = []; // Array of ['optionId' => ['order' => newOrder]]
|
||||||
|
|
||||||
|
// Store array of Option entities and check the Options questionId & old order.
|
||||||
|
foreach ($newOrder as $arrayKey => $optionId) {
|
||||||
|
try {
|
||||||
|
$options[$arrayKey] = $this->optionMapper->findById($optionId);
|
||||||
|
} catch (IMapperException $e) {
|
||||||
|
$this->logger->debug('Could not find option. Id: {optionId}', [
|
||||||
|
'optionId' => $optionId
|
||||||
|
]);
|
||||||
|
throw new OCSBadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort if a question is not part of the Form.
|
||||||
|
if ($options[$arrayKey]->getQuestionId() !== $questionId) {
|
||||||
|
$this->logger->debug('This Option is not part of the given Question: formId: {formId}', [
|
||||||
|
'formId' => $formId
|
||||||
|
]);
|
||||||
|
throw new OCSBadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort if a question is already marked as deleted (order==0)
|
||||||
|
$oldOrder = $options[$arrayKey]->getOrder();
|
||||||
|
|
||||||
|
// Only set order, if it changed.
|
||||||
|
if ($oldOrder !== $arrayKey + 1) {
|
||||||
|
// Set Order. ArrayKey counts from zero, order counts from 1.
|
||||||
|
$options[$arrayKey]->setOrder($arrayKey + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to Database
|
||||||
|
foreach ($options as $option) {
|
||||||
|
$this->optionMapper->update($option);
|
||||||
|
|
||||||
|
$response[$option->getId()] = [
|
||||||
|
'order' => $option->getOrder()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->formMapper->update($form);
|
||||||
|
|
||||||
|
return new DataResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
// Submissions
|
// Submissions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1964,10 +2067,20 @@ class ApiController extends OCSController {
|
||||||
throw new OCSForbiddenException();
|
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 = new Option();
|
||||||
|
|
||||||
$option->setQuestionId($questionId);
|
$option->setQuestionId($questionId);
|
||||||
$option->setText($text);
|
$option->setText($text);
|
||||||
|
$option->setOrder($optionOrder);
|
||||||
|
|
||||||
$option = $this->optionMapper->insert($option);
|
$option = $this->optionMapper->insert($option);
|
||||||
$this->formMapper->update($form);
|
$this->formMapper->update($form);
|
||||||
|
@ -2071,6 +2184,15 @@ class ApiController extends OCSController {
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->optionMapper->delete($option);
|
$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);
|
$this->formMapper->update($form);
|
||||||
|
|
||||||
return new DataResponse($id);
|
return new DataResponse($id);
|
||||||
|
|
|
@ -29,23 +29,29 @@ namespace OCA\Forms\Db;
|
||||||
use OCP\AppFramework\Db\Entity;
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @method int getQuestionId()
|
* @method int|float getQuestionId()
|
||||||
* @method void setQuestionId(int $value)
|
* @method void setQuestionId(int|float $value)
|
||||||
* @method string getText()
|
* @method string getText()
|
||||||
* @method void setText(string $value)
|
* @method void setText(string $value)
|
||||||
|
* @method int getOrder();
|
||||||
|
* @method void setOrder(int $value)
|
||||||
*/
|
*/
|
||||||
class Option extends Entity {
|
class Option extends Entity {
|
||||||
|
|
||||||
/** @var int */
|
// For 32bit PHP long integers, like IDs, are represented by floats
|
||||||
protected $questionId;
|
protected int|float|null $questionId;
|
||||||
/** @var string */
|
protected ?string $text;
|
||||||
protected $text;
|
protected ?int $order;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Option constructor.
|
* Option constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
$this->questionId = null;
|
||||||
|
$this->text = null;
|
||||||
|
$this->order = null;
|
||||||
$this->addType('questionId', 'integer');
|
$this->addType('questionId', 'integer');
|
||||||
|
$this->addType('order', 'integer');
|
||||||
$this->addType('text', 'string');
|
$this->addType('text', 'string');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +59,7 @@ class Option extends Entity {
|
||||||
return [
|
return [
|
||||||
'id' => $this->getId(),
|
'id' => $this->getId(),
|
||||||
'questionId' => $this->getQuestionId(),
|
'questionId' => $this->getQuestionId(),
|
||||||
|
'order' => $this->getOrder(),
|
||||||
'text' => (string)$this->getText(),
|
'text' => (string)$this->getText(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Forms\Db;
|
namespace OCA\Forms\Db;
|
||||||
|
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
|
||||||
use OCP\AppFramework\Db\QBMapper;
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
@ -45,11 +44,10 @@ class OptionMapper extends QBMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $questionId
|
* @param int|float $questionId
|
||||||
* @throws DoesNotExistException if not found
|
|
||||||
* @return Option[]
|
* @return Option[]
|
||||||
*/
|
*/
|
||||||
public function findByQuestion(int $questionId): array {
|
public function findByQuestion(int|float $questionId): array {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
|
@ -57,7 +55,8 @@ class OptionMapper extends QBMapper {
|
||||||
->where(
|
->where(
|
||||||
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId))
|
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId))
|
||||||
)
|
)
|
||||||
->orderBy('id');
|
->orderBy('order')
|
||||||
|
->addOrderBy('id');
|
||||||
|
|
||||||
return $this->findEntities($qb);
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
@ -73,7 +72,10 @@ class OptionMapper extends QBMapper {
|
||||||
$qb->executeStatement();
|
$qb->executeStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findById(int $optionId): Option {
|
/**
|
||||||
|
* @param int|float $optionId The option ID (int but for 32bit systems PHP will use float)
|
||||||
|
*/
|
||||||
|
public function findById(int|float $optionId): Option {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
|
|
|
@ -90,7 +90,11 @@ class QuestionMapper extends QBMapper {
|
||||||
$qb->executeStatement();
|
$qb->executeStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findById(int $questionId): Question {
|
/**
|
||||||
|
* Find Question by its ID
|
||||||
|
* @param int|float $questionId The question ID (int but for 32bit systems PHP uses float)
|
||||||
|
*/
|
||||||
|
public function findById(int|float $questionId): Question {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
|
||||||
$qb->select('*')
|
$qb->select('*')
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
|
||||||
|
*
|
||||||
|
* @author Ferdinand Thiessen <opensource@fthiessen.de>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Forms\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\DB\Types;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add "order" column for options
|
||||||
|
*/
|
||||||
|
class Version040300Date20240420155356 extends SimpleMigrationStep {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||||
|
* @param array $options
|
||||||
|
* @return null|ISchemaWrapper
|
||||||
|
*/
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||||
|
/** @var ISchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
$table = $schema->getTable('forms_v2_options');
|
||||||
|
|
||||||
|
// Abort if already existing.
|
||||||
|
if ($table->hasColumn('order')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new column
|
||||||
|
$table->addColumn('order', Types::INTEGER, [
|
||||||
|
'notnull' => false,
|
||||||
|
'default' => null,
|
||||||
|
'unsigned' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add index for better performance
|
||||||
|
$table->addIndex(['question_id', 'order'], 'forms_options_question_order');
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,13 +84,16 @@ class ApiV2Test extends IntegrationBase {
|
||||||
'order' => 2,
|
'order' => 2,
|
||||||
'options' => [
|
'options' => [
|
||||||
[
|
[
|
||||||
'text' => 'Option 1'
|
'text' => 'Option 1',
|
||||||
|
'order' => 1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => 'Option 2'
|
'text' => 'Option 2',
|
||||||
|
'order' => 2
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => ''
|
'text' => '',
|
||||||
|
'order' => 3
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'accept' => [],
|
'accept' => [],
|
||||||
|
@ -496,13 +499,16 @@ class ApiV2Test extends IntegrationBase {
|
||||||
'order' => 2,
|
'order' => 2,
|
||||||
'options' => [
|
'options' => [
|
||||||
[
|
[
|
||||||
'text' => 'Option 1'
|
'text' => 'Option 1',
|
||||||
|
'order' => 1,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => 'Option 2'
|
'text' => 'Option 2',
|
||||||
|
'order' => 2,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => ''
|
'text' => '',
|
||||||
|
'order' => 3,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'accept' => [],
|
'accept' => [],
|
||||||
|
@ -906,7 +912,8 @@ class ApiV2Test extends IntegrationBase {
|
||||||
'newOption' => [
|
'newOption' => [
|
||||||
'expected' => [
|
'expected' => [
|
||||||
// 'questionId' => Done dynamically below.
|
// 'questionId' => Done dynamically below.
|
||||||
'text' => 'A new Option.'
|
'text' => 'A new Option.',
|
||||||
|
'order' => 4,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
@ -995,6 +1002,8 @@ class ApiV2Test extends IntegrationBase {
|
||||||
$this->assertEquals($this->testForms[0]['questions'][1]['options'][0]['id'], $data);
|
$this->assertEquals($this->testForms[0]['questions'][1]['options'][0]['id'], $data);
|
||||||
|
|
||||||
$fullFormExpected['lastUpdated'] = time();
|
$fullFormExpected['lastUpdated'] = time();
|
||||||
|
$fullFormExpected['questions'][1]['options'][0]['order'] = 1;
|
||||||
|
$fullFormExpected['questions'][1]['options'][1]['order'] = 2;
|
||||||
|
|
||||||
$this->testGetFullForm($fullFormExpected);
|
$this->testGetFullForm($fullFormExpected);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,13 +84,16 @@ class ApiV3Test extends IntegrationBase {
|
||||||
'order' => 2,
|
'order' => 2,
|
||||||
'options' => [
|
'options' => [
|
||||||
[
|
[
|
||||||
'text' => 'Option 1'
|
'text' => 'Option 1',
|
||||||
|
'order' => 1
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => 'Option 2'
|
'text' => 'Option 2',
|
||||||
|
'order' => 2
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => ''
|
'text' => '',
|
||||||
|
'order' => 3
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'accept' => [],
|
'accept' => [],
|
||||||
|
@ -464,13 +467,16 @@ class ApiV3Test extends IntegrationBase {
|
||||||
'order' => 2,
|
'order' => 2,
|
||||||
'options' => [
|
'options' => [
|
||||||
[
|
[
|
||||||
'text' => 'Option 1'
|
'text' => 'Option 1',
|
||||||
|
'order' => 1,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => 'Option 2'
|
'text' => 'Option 2',
|
||||||
|
'order' => 2,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'text' => ''
|
'text' => '',
|
||||||
|
'order' => 3,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'accept' => [],
|
'accept' => [],
|
||||||
|
@ -870,7 +876,8 @@ class ApiV3Test extends IntegrationBase {
|
||||||
'newOption' => [
|
'newOption' => [
|
||||||
'expected' => [
|
'expected' => [
|
||||||
// 'questionId' => Done dynamically below.
|
// 'questionId' => Done dynamically below.
|
||||||
'text' => 'A new Option.'
|
'text' => 'A new Option.',
|
||||||
|
'order' => 4,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
@ -957,6 +964,8 @@ class ApiV3Test extends IntegrationBase {
|
||||||
$this->assertEquals($this->testForms[0]['questions'][1]['options'][0]['id'], $data);
|
$this->assertEquals($this->testForms[0]['questions'][1]['options'][0]['id'], $data);
|
||||||
|
|
||||||
$fullFormExpected['lastUpdated'] = time();
|
$fullFormExpected['lastUpdated'] = time();
|
||||||
|
$fullFormExpected['questions'][1]['options'][0]['order'] = 1;
|
||||||
|
$fullFormExpected['questions'][1]['options'][1]['order'] = 2;
|
||||||
|
|
||||||
$this->testGetFullForm($fullFormExpected);
|
$this->testGetFullForm($fullFormExpected);
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,8 @@ class IntegrationBase extends TestCase {
|
||||||
$qb->insert('forms_v2_options')
|
$qb->insert('forms_v2_options')
|
||||||
->values([
|
->values([
|
||||||
'question_id' => $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT),
|
'question_id' => $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT),
|
||||||
'text' => $qb->createNamedParameter($option['text'], IQueryBuilder::PARAM_STR)
|
'text' => $qb->createNamedParameter($option['text'], IQueryBuilder::PARAM_STR),
|
||||||
|
'order' => $qb->createNamedParameter($option['order'], IQueryBuilder::PARAM_INT)
|
||||||
]);
|
]);
|
||||||
$qb->executeStatement();
|
$qb->executeStatement();
|
||||||
$this->testForms[$index]['questions'][$qIndex]['options'][$oIndex]['id'] = $qb->getLastInsertId();
|
$this->testForms[$index]['questions'][$qIndex]['options'][$oIndex]['id'] = $qb->getLastInsertId();
|
||||||
|
|
|
@ -228,12 +228,14 @@ class FormsServiceTest extends TestCase {
|
||||||
[
|
[
|
||||||
'id' => 1,
|
'id' => 1,
|
||||||
'questionId' => 1,
|
'questionId' => 1,
|
||||||
'text' => 'Option 1'
|
'text' => 'Option 1',
|
||||||
|
'order' => null,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => 2,
|
'id' => 2,
|
||||||
'questionId' => 1,
|
'questionId' => 1,
|
||||||
'text' => 'Option 2'
|
'text' => 'Option 2',
|
||||||
|
'order' => null,
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'accept' => [],
|
'accept' => [],
|
||||||
|
|
Загрузка…
Ссылка в новой задаче