diff --git a/appinfo/routes.php b/appinfo/routes.php
index 0c186a7..eaeb20d 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -34,17 +34,24 @@ return [
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
['name' => 'page#insert_submission', 'url' => '/insert/submission', 'verb' => 'POST'],
+ // Forms
['name' => 'api#getForms', 'url' => '/api/v1/forms', 'verb' => 'GET'],
+ ['name' => 'api#newForm', 'url' => '/api/v1/form', 'verb' => 'POST'],
['name' => 'api#getForm', 'url' => '/api/v1/form/{id}', 'verb' => 'GET'],
['name' => 'api#updateForm', 'url' => '/api/v1/form/update', 'verb' => 'POST'],
- ['name' => 'api#newForm', 'url' => '/api/v1/form', 'verb' => 'POST'],
['name' => 'api#deleteForm', 'url' => '/api/v1/form/{id}', 'verb' => 'DELETE'],
+
+ // Questions
+ ['name' => 'api#newQuestion', 'url' => '/api/v1/question', 'verb' => 'POST'],
['name' => 'api#updateQuestion', 'url' => '/api/v1/question/update', 'verb' => 'POST'],
['name' => 'api#reorderQuestions', 'url' => '/api/v1/question/reorder', 'verb' => 'POST'],
- ['name' => 'api#newQuestion', 'url' => '/api/v1/question', 'verb' => 'POST'],
['name' => 'api#deleteQuestion', 'url' => '/api/v1/question/{id}', 'verb' => 'DELETE'],
+
+ // Answers
['name' => 'api#newOption', 'url' => '/api/v1/option', 'verb' => 'POST'],
+ ['name' => 'api#updateOption', 'url' => '/api/v1/option/update', 'verb' => 'POST'],
['name' => 'api#deleteOption', 'url' => '/api/v1/option/{id}', 'verb' => 'DELETE'],
+
['name' => 'api#getSubmissions', 'url' => '/api/v1/submissions/{hash}', 'verb' => 'GET'],
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php
index cba80b6..be8b82d 100644
--- a/lib/Controller/ApiController.php
+++ b/lib/Controller/ApiController.php
@@ -473,16 +473,15 @@ class ApiController extends Controller {
/**
* @NoAdminRequired
*/
- public function newOption(int $formId, int $questionId, string $text): Http\JSONResponse {
- $this->logger->debug('Adding new option: formId: {formId}, questionId: {questionId}, text: {text}', [
- 'formId' => $formId,
+ public function newOption(int $questionId, string $text): Http\JSONResponse {
+ $this->logger->debug('Adding new option: questionId: {questionId}, text: {text}', [
'questionId' => $questionId,
'text' => $text,
]);
try {
- $form = $this->formMapper->findById($formId);
$question = $this->questionMapper->findById($questionId);
+ $form = $this->formMapper->findById($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question so option can\'t be added');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
@@ -493,11 +492,6 @@ class ApiController extends Controller {
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
- if ($question->getFormId() !== $formId) {
- $this->logger->debug('This question is not part of the current form');
- return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
- }
-
$option = new Option();
$option->setQuestionId($questionId);
@@ -505,6 +499,45 @@ class ApiController extends Controller {
$option = $this->optionMapper->insert($option);
+ return new Http\JSONResponse([
+ 'id' => $option->getId()
+ ]);
+ }
+
+ /**
+ * @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.
+ */
+ public function updateOption(int $id, array $keyValuePairs): Http\JSONResponse {
+ $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');
+ return new Http\JSONResponse(['message' => 'Could not find option, question or form'], Http::STATUS_BAD_REQUEST);
+ }
+
+ if ($form->getOwnerId() !== $this->userId) {
+ $this->logger->debug('This form is not owned by the current user');
+ return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
+ }
+
+ // Create OptionEntity with given Params & Id.
+ $option = Option::fromParams($keyValuePairs);
+ $option->setId($id);
+
+ // Update changed Columns in Db.
+ $this->optionMapper->update($option);
+
return new Http\JSONResponse($option->getId());
}
@@ -532,7 +565,6 @@ class ApiController extends Controller {
$this->optionMapper->delete($option);
- //TODO useful response
return new Http\JSONResponse($id);
}
diff --git a/package-lock.json b/package-lock.json
index 21e96ad..c5d0632 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4623,6 +4623,11 @@
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
+ "eventemitter3": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
+ "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg=="
+ },
"events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
@@ -8209,6 +8214,11 @@
"os-tmpdir": "^1.0.0"
}
},
+ "p-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-2.1.0.tgz",
+ "integrity": "sha512-M9bMt62TTnozdZhqFgs+V7XD2MnuKCaz+7fZdlu2/T7xruI3uIE5CicQ0vx1hV7HIUYF0jF+4/R1AgfOkl74Qw=="
+ },
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@@ -8218,8 +8228,7 @@
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
- "dev": true
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-is-promise": {
"version": "2.1.0",
@@ -8245,6 +8254,23 @@
"p-limit": "^2.0.0"
}
},
+ "p-queue": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.3.0.tgz",
+ "integrity": "sha512-fg5dJlFpd5+3CgG3/0ogpVZUeJbjiyXFg0nu53hrOYsybqSiDyxyOpad0Rm6tAiGjgztAwkyvhlYHC53OiAJOA==",
+ "requires": {
+ "eventemitter3": "^4.0.0",
+ "p-timeout": "^3.1.0"
+ }
+ },
+ "p-timeout": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+ "requires": {
+ "p-finally": "^1.0.0"
+ }
+ },
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
diff --git a/package.json b/package.json
index 9496808..174e955 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,8 @@
"crypto-js": "^4.0.0",
"debounce": "^1.2.0",
"json2csv": "5.0.0",
+ "p-debounce": "^2.1.0",
+ "p-queue": "^6.3.0",
"regenerator-runtime": "^0.13.5",
"v-click-outside": "^3.0.1",
"vue": "^2.6.11",
diff --git a/src/components/Questions/AnswerInput.vue b/src/components/Questions/AnswerInput.vue
new file mode 100644
index 0000000..424e74b
--- /dev/null
+++ b/src/components/Questions/AnswerInput.vue
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+ {{ t('forms', 'Delete answer') }}
+
+
+
+
+
+
+
+
diff --git a/src/components/Questions/QuestionMultiple.vue b/src/components/Questions/QuestionMultiple.vue
index 66a3ef6..05306e2 100644
--- a/src/components/Questions/QuestionMultiple.vue
+++ b/src/components/Questions/QuestionMultiple.vue
@@ -22,6 +22,7 @@