@@ -172,6 +172,7 @@ public function newForm(?int $fromId = null): DataResponse {
172172 'showToAllUsers ' => false ,
173173 ]);
174174 $ form ->setSubmitMultiple (false );
175+ $ form ->setAllowEdit (false );
175176 $ form ->setShowExpiration (false );
176177 $ form ->setExpires (0 );
177178 $ form ->setIsAnonymous (false );
@@ -1294,7 +1295,7 @@ public function newSubmission(int $formId, array $answers, string $shareHash = '
12941295 continue ;
12951296 }
12961297
1297- $ this ->storeAnswersForQuestion ($ form , $ submission ->getId (), $ questions [$ questionIndex ], $ answerArray );
1298+ $ this ->storeAnswersForQuestion ($ form , $ submission ->getId (), $ questions [$ questionIndex ], $ answerArray, false );
12981299 }
12991300
13001301 $ this ->formMapper ->update ($ form );
@@ -1309,6 +1310,87 @@ public function newSubmission(int $formId, array $answers, string $shareHash = '
13091310 return new DataResponse (null , Http::STATUS_CREATED );
13101311 }
13111312
1313+ /**
1314+ * Update an existing submission
1315+ *
1316+ * @param int $formId the form id
1317+ * @param int $submissionId the submission id
1318+ * @param array<string, list<string>> $answers [question_id => arrayOfString]
1319+ * @param string $shareHash public share-hash -> Necessary to submit on public link-shares.
1320+ * @return DataResponse<Http::STATUS_OK, int, array{}>
1321+ * @throws OCSBadRequestException Can only update submission if AllowEdit is set and the answers are valid
1322+ * @throws OCSForbiddenException Can only update your own submission
1323+ *
1324+ * 200: the id of the updated submission
1325+ */
1326+ #[CORS ()]
1327+ #[NoAdminRequired()]
1328+ #[NoCSRFRequired()]
1329+ #[PublicPage()]
1330+ #[ApiRoute(verb: 'PUT ' , url: '/api/v3/forms/{formId}/submissions/{submissionId} ' )]
1331+ public function updateSubmission (int $ formId , int $ submissionId , array $ answers , string $ shareHash = '' ): DataResponse {
1332+ $ this ->logger ->debug ('Updating submission: formId: {formId}, answers: {answers}, shareHash: {shareHash} ' , [
1333+ 'formId ' => $ formId ,
1334+ 'answers ' => $ answers ,
1335+ 'shareHash ' => $ shareHash ,
1336+ ]);
1337+
1338+ $ form = $ this ->loadFormForSubmission ($ formId , $ shareHash );
1339+
1340+ if (!$ form ->getAllowEdit ()) {
1341+ throw new OCSBadRequestException ('Can only update if AllowEdit is set ' );
1342+ }
1343+
1344+ $ questions = $ this ->formsService ->getQuestions ($ formId );
1345+ // Is the submission valid
1346+ $ isSubmissionValid = $ this ->submissionService ->validateSubmission ($ questions , $ answers , $ form ->getOwnerId ());
1347+ if (is_string ($ isSubmissionValid )) {
1348+ throw new OCSBadRequestException ($ isSubmissionValid );
1349+ }
1350+ if ($ isSubmissionValid === false ) {
1351+ throw new OCSBadRequestException ('At least one submitted answer is not valid ' );
1352+ }
1353+
1354+ // get existing submission of this user
1355+ try {
1356+ $ submission = $ this ->submissionMapper ->findByFormAndUser ($ form ->getId (), $ this ->currentUser ->getUID ());
1357+ } catch (DoesNotExistException $ e ) {
1358+ throw new OCSBadRequestException ('Cannot update a non existing submission ' );
1359+ }
1360+
1361+ if ($ submissionId != $ submission ->getId ()) {
1362+ throw new OCSForbiddenException ('Can only update your own submissions ' );
1363+ }
1364+
1365+ $ submission ->setTimestamp (time ());
1366+ $ this ->submissionMapper ->update ($ submission );
1367+
1368+ if (empty ($ answers )) {
1369+ // Clear Answers
1370+ foreach ($ questions as $ question ) {
1371+ $ this ->storeAnswersForQuestion ($ form , $ submission ->getId (), $ question , ['' ], true );
1372+ }
1373+ } else {
1374+ // Process Answers
1375+ foreach ($ answers as $ questionId => $ answerArray ) {
1376+ // Search corresponding Question, skip processing if not found
1377+ $ questionIndex = array_search ($ questionId , array_column ($ questions , 'id ' ));
1378+ if ($ questionIndex === false ) {
1379+ continue ;
1380+ }
1381+
1382+ $ question = $ questions [$ questionIndex ];
1383+
1384+ $ this ->storeAnswersForQuestion ($ form , $ submission ->getId (), $ question , $ answerArray , true );
1385+ }
1386+ }
1387+
1388+ //Create Activity
1389+ $ this ->formsService ->notifyNewSubmission ($ form , $ submission );
1390+
1391+ return new DataResponse ($ submissionId );
1392+ }
1393+
13121394 /**
13131395 * Delete a specific submission
13141396 *
@@ -1522,14 +1604,23 @@ public function uploadFiles(int $formId, int $questionId, string $shareHash = ''
15221604 // private functions
15231605
15241606 /**
1525- * Insert answers for a question
1607+ * Insert or update answers for a question
15261608 *
15271609 * @param Form $form
15281610 * @param int $submissionId
15291611 * @param array $question
15301612 * @param string[]|array<array{uploadedFileId: string, uploadedFileName: string}> $answerArray
1613+ * @param bool $update
15311614 */
1532- private function storeAnswersForQuestion (Form $ form , $ submissionId , array $ question , array $ answerArray ): void {
1615+ private function storeAnswersForQuestion (Form $ form , int $ submissionId , array $ question , array $ answerArray , bool $ update ): void {
1616+ // get stored answers for this question
1617+ $ storedAnswers = [];
1618+ if ($ update ) {
1619+ $ storedAnswers = $ this ->answerMapper ->findBySubmissionAndQuestion ($ submissionId , $ question ['id ' ]);
1620+ }
1621+
1622+ $ newAnswerTexts = [];
1623+
15331624 foreach ($ answerArray as $ answer ) {
15341625 $ answerEntity = new Answer ();
15351626 $ answerEntity ->setSubmissionId ($ submissionId );
@@ -1546,6 +1637,33 @@ private function storeAnswersForQuestion(Form $form, $submissionId, array $quest
15461637 } elseif (!empty ($ question ['extraSettings ' ]['allowOtherAnswer ' ]) && strpos ($ answer , Constants::QUESTION_EXTRASETTINGS_OTHER_PREFIX ) === 0 ) {
15471638 $ answerText = str_replace (Constants::QUESTION_EXTRASETTINGS_OTHER_PREFIX , '' , $ answer );
15481639 }
1640+
1641+ if (!array_key_exists ($ question ['id ' ], $ newAnswerTexts )) {
1642+ $ newAnswerTexts [$ question ['id ' ]] = [];
1643+ }
1644+ $ newAnswerTexts [$ question ['id ' ]][] = $ answerText ;
1645+
1646+ // has this answer already been stored?
1647+ $ foundAnswer = false ;
1648+ foreach ($ storedAnswers as $ storedAnswer ) {
1649+ if ($ storedAnswer ->getText () == $ answerText ) {
1650+ // nothing to be changed
1651+ $ foundAnswer = true ;
1652+ break ;
1653+ }
1654+ }
1655+ if (!$ foundAnswer ) {
1656+ if ($ answerText === '' ) {
1657+ continue ;
1658+ }
1659+ // need to add answer
1660+ $ answerEntity = new Answer ();
1661+ $ answerEntity ->setSubmissionId ($ submissionId );
1662+ $ answerEntity ->setQuestionId ($ question ['id ' ]);
1663+ $ answerEntity ->setText ($ answerText );
1664+ $ this ->answerMapper ->insert ($ answerEntity );
1665+ }
1666+
15491667 } elseif ($ question ['type ' ] === Constants::ANSWER_TYPE_FILE ) {
15501668 $ uploadedFile = $ this ->uploadedFileMapper ->getByUploadedFileId ($ answer ['uploadedFileId ' ]);
15511669 $ answerEntity ->setFileId ($ uploadedFile ->getFileId ());
@@ -1565,20 +1683,43 @@ private function storeAnswersForQuestion(Form $form, $submissionId, array $quest
15651683 $ file ->move ($ folder ->getPath () . '/ ' . $ name );
15661684
15671685 $ answerText = $ name ;
1686+
1687+ $ answerEntity ->setText ($ answerText );
1688+ $ this ->answerMapper ->insert ($ answerEntity );
15681689 } else {
15691690 $ answerText = $ answer ; // Not a multiple-question, answerText is given answer
1570- }
15711691
1572- if ($ answerText === '' ) {
1573- continue ;
1692+ if (!empty ($ storedAnswers )) {
1693+ $ answerEntity = $ storedAnswers [0 ];
1694+ $ answerEntity ->setText ($ answerText );
1695+ $ this ->answerMapper ->update ($ answerEntity );
1696+ } else {
1697+ if ($ answerText === '' ) {
1698+ continue ;
1699+ }
1700+ $ answerEntity = new Answer ();
1701+ $ answerEntity ->setSubmissionId ($ submissionId );
1702+ $ answerEntity ->setQuestionId ($ question ['id ' ]);
1703+ $ answerEntity ->setText ($ answerText );
1704+ $ this ->answerMapper ->insert ($ answerEntity );
1705+ }
15741706 }
15751707
1576- $ answerEntity ->setText ($ answerText );
1577- $ this ->answerMapper ->insert ($ answerEntity );
15781708 if ($ uploadedFile ) {
15791709 $ this ->uploadedFileMapper ->delete ($ uploadedFile );
15801710 }
15811711 }
1712+
1713+ if (in_array ($ question ['type ' ], Constants::ANSWER_TYPES_PREDEFINED )) {
1714+ // drop all answers that are not in new set of answers
1715+ foreach ($ storedAnswers as $ storedAnswer ) {
1716+ $ questionId = $ storedAnswer ->getQuestionId ();
1717+
1718+ if (empty ($ newAnswerTexts [$ questionId ]) || !in_array ($ storedAnswer ->getText (), $ newAnswerTexts [$ questionId ])) {
1719+ $ this ->answerMapper ->delete ($ storedAnswer );
1720+ }
1721+ }
1722+ }
15821723 }
15831724
15841725 private function loadFormForSubmission (int $ formId , string $ shareHash ): Form {
0 commit comments