@@ -152,23 +152,39 @@ public function getForms(string $type = 'owned'): DataResponse {
152152 * Return a copy of the form if the parameter $fromId is set
153153 *
154154 * @param ?int $fromId (optional) Id of the form that should be cloned
155+ * @param ?bool $import (optional) If it should import the form from post body
156+ * @param ?array<string, mixed> $formData (optional) The formdata to import
155157 * @return DataResponse<Http::STATUS_CREATED, FormsForm, array{}>
156158 * @throws OCSForbiddenException The user is not allowed to create forms
159+ * @throws OCSBadRequestException Cannot use both fromId and import parameters
160+ * @throws OCSBadRequestException Invalid form data: missing questions
161+ * @throws OCSBadRequestException Invalid form data: unknown properties
162+ * @throws OCSBadRequestException Invalid question data: missing id
163+ * @throws OCSBadRequestException Invalid question data: unknown properties
164+ * @throws OCSBadRequestException Invalid question data: invalid type
165+ * @throws OCSBadRequestException Invalid question data: datetime type no longer supported
166+ * @throws OCSBadRequestException Invalid question data: invalid extraSettings
167+ * @throws OCSBadRequestException Invalid option data: unknown properties
157168 *
158169 * 201: the created form
159170 */
160171 #[CORS ()]
161172 #[NoAdminRequired()]
162173 #[BruteForceProtection(action: 'form ' )]
163174 #[ApiRoute(verb: 'POST ' , url: '/api/v3/forms ' )]
164- public function newForm (?int $ fromId = null ): DataResponse {
175+ public function newForm (?int $ fromId = null , ? bool $ import = false , ? array $ formData = [] ): DataResponse {
165176 // Check if user is allowed
166177 if (!$ this ->configService ->canCreateForms ()) {
167178 $ this ->logger ->debug ('This user is not allowed to create Forms. ' );
168179 throw new OCSForbiddenException ('This user is not allowed to create Forms. ' );
169180 }
170181
171- if ($ fromId === null ) {
182+ // Validate mutually exclusive parameters
183+ if ($ fromId !== null && $ import === true ) {
184+ throw new OCSBadRequestException ('Cannot use both fromId and import parameters ' );
185+ }
186+
187+ if ($ fromId === null && $ import !== true ) {
172188 // Create Form
173189 $ form = new Form ();
174190 $ form ->setOwnerId ($ this ->currentUser ->getUID ());
@@ -186,33 +202,104 @@ public function newForm(?int $fromId = null): DataResponse {
186202 $ form ->setIsAnonymous (false );
187203
188204 $ this ->formMapper ->insert ($ form );
205+ } elseif ($ import === true ) {
206+ if (!isset ($ formData ['questions ' ]) || !\is_array ($ formData ['questions ' ])) {
207+ throw new OCSBadRequestException ('Invalid form data: missing questions ' );
208+ }
209+ $ questions = $ formData ['questions ' ];
210+ $ oldConfirmationEmailQuestionId = $ formData ['confirmationEmailQuestionId ' ] ?? null ;
211+ unset($ formData ['questions ' ]);
212+
213+ // Validate form data whitelist
214+ $ allowedFormProperties = [
215+ 'title ' , 'description ' , 'access ' , 'expires ' , 'isAnonymous ' ,
216+ 'submitMultiple ' , 'allowEditSubmissions ' , 'showExpiration ' ,
217+ 'submissionMessage ' , 'maxSubmissions ' , 'confirmationEmailEnabled ' ,
218+ 'confirmationEmailSubject ' , 'confirmationEmailBody ' , 'confirmationEmailQuestionId ' ,
219+ 'allowComments ' ,
220+ ];
221+ $ invalidKeys = array_diff (array_keys ($ formData ), $ allowedFormProperties );
222+ if (!empty ($ invalidKeys )) {
223+ throw new OCSBadRequestException ('Invalid form data: unknown properties: ' . implode (', ' , $ invalidKeys ));
224+ }
225+
226+ $ formData = $ this ->setDefaults ($ formData );
227+
228+ $ form = Form::fromParams ($ formData );
229+ $ this ->formMapper ->insert ($ form );
230+
231+ foreach ($ questions as $ oldQuestion ) {
232+ if (!isset ($ oldQuestion ['id ' ])) {
233+ throw new OCSBadRequestException ('Invalid question data: missing id ' );
234+ }
235+
236+ // Validate question property whitelist
237+ $ allowedQuestionProperties = ['id ' , 'order ' , 'type ' , 'isRequired ' , 'text ' , 'name ' , 'description ' , 'extraSettings ' , 'options ' ];
238+ $ invalidQuestionKeys = array_diff (array_keys ($ oldQuestion ), $ allowedQuestionProperties );
239+ if (!empty ($ invalidQuestionKeys )) {
240+ throw new OCSBadRequestException ('Invalid question data: unknown properties: ' . implode (', ' , $ invalidQuestionKeys ));
241+ }
242+
243+ // Validate question type
244+ $ type = $ oldQuestion ['type ' ] ?? null ;
245+ if ($ type === null || array_search ($ type , Constants::ANSWER_TYPES ) === false ) {
246+ throw new OCSBadRequestException ('Invalid question data: invalid type ' );
247+ }
248+
249+ // Block datetime questions
250+ if ($ type === 'datetime ' ) {
251+ throw new OCSBadRequestException ('Invalid question data: datetime type no longer supported ' );
252+ }
253+
254+ // Validate extraSettings
255+ if (!empty ($ oldQuestion ['extraSettings ' ] ?? [])) {
256+ if (!$ this ->formsService ->areExtraSettingsValid ($ oldQuestion ['extraSettings ' ], $ type )) {
257+ throw new OCSBadRequestException ('Invalid question data: invalid extraSettings ' );
258+ }
259+ }
260+
261+ $ questionData = $ oldQuestion ;
262+ $ oldQuestionId = $ oldQuestion ['id ' ];
263+ $ options = $ oldQuestion ['options ' ] ?? [];
264+
265+ unset($ questionData ['id ' ]);
266+ unset($ questionData ['options ' ]);
267+ unset($ questionData ['accept ' ]);
268+
269+ $ questionData ['formId ' ] = $ form ->getId ();
270+ $ newQuestion = Question::fromParams ($ questionData );
271+ $ this ->questionMapper ->insert ($ newQuestion );
272+
273+ if (isset ($ oldConfirmationEmailQuestionId ) && $ oldConfirmationEmailQuestionId === $ oldQuestionId ) {
274+ $ form ->setConfirmationEmailQuestionId ($ newQuestion ->getId ());
275+ }
276+
277+ foreach ($ options as $ oldOption ) {
278+ $ optionData = $ oldOption ;
279+
280+ // Validate option property whitelist
281+ $ allowedOptionProperties = ['text ' , 'order ' , 'optionType ' ];
282+ $ invalidOptionKeys = array_diff (array_keys ($ optionData ), $ allowedOptionProperties );
283+ if (!empty ($ invalidOptionKeys )) {
284+ throw new OCSBadRequestException ('Invalid option data: unknown properties: ' . implode (', ' , $ invalidOptionKeys ));
285+ }
286+
287+ unset($ optionData ['id ' ]);
288+ $ optionData ['questionId ' ] = $ newQuestion ->getId ();
289+ $ newOption = Option::fromParams ($ optionData );
290+ $ this ->optionMapper ->insert ($ newOption );
291+ }
292+ }
293+ $ this ->formMapper ->update ($ form );
294+
189295 } else {
190296 $ oldForm = $ this ->formsService ->getFormIfAllowed ($ fromId , Constants::PERMISSION_EDIT );
191297
192298 // Read old form, (un)set new form specific data, extend title
193299 $ formData = $ oldForm ->read ();
194- unset($ formData ['id ' ]);
195- unset($ formData ['created ' ]);
196- unset($ formData ['lastUpdated ' ]);
197- unset($ formData ['state ' ]);
198- unset($ formData ['fileId ' ]);
199- unset($ formData ['fileFormat ' ]);
200- unset($ formData ['lockedBy ' ]);
201- unset($ formData ['lockedUntil ' ]);
202- unset($ formData ['confirmationEmailQuestionId ' ]);
203- $ formData ['ownerId ' ] = $ this ->currentUser ->getUID ();
204- $ formData ['hash ' ] = $ this ->formsService ->generateFormHash ();
300+ $ formData = $ this ->setDefaults ($ formData );
205301 // TRANSLATORS Appendix to the form Title of a duplicated/copied form.
206302 $ formData ['title ' ] .= ' - ' . $ this ->l10n ->t ('Copy ' );
207- $ formData ['access ' ] = [
208- 'permitAllUsers ' => false ,
209- 'showToAllUsers ' => false ,
210- ];
211- $ formData ['submitMultiple ' ] = false ;
212- $ formData ['allowEditSubmissions ' ] = false ;
213- $ formData ['showExpiration ' ] = false ;
214- $ formData ['expires ' ] = 0 ;
215- $ formData ['isAnonymous ' ] = false ;
216303
217304 $ form = Form::fromParams ($ formData );
218305 $ this ->formMapper ->insert ($ form );
@@ -251,6 +338,32 @@ public function newForm(?int $fromId = null): DataResponse {
251338 return new DataResponse ($ this ->formsService ->getForm ($ form ), Http::STATUS_CREATED );
252339 }
253340
341+ private function setDefaults (array $ formData ) {
342+ // Remove unused data
343+ unset($ formData ['id ' ]);
344+ unset($ formData ['created ' ]);
345+ unset($ formData ['lastUpdated ' ]);
346+ unset($ formData ['state ' ]);
347+ unset($ formData ['fileId ' ]);
348+ unset($ formData ['fileFormat ' ]);
349+ unset($ formData ['lockedBy ' ]);
350+ unset($ formData ['lockedUntil ' ]);
351+ unset($ formData ['confirmationEmailQuestionId ' ]);
352+ $ formData ['ownerId ' ] = $ this ->currentUser ->getUID ();
353+ $ formData ['hash ' ] = $ this ->formsService ->generateFormHash ();
354+
355+ $ formData ['access ' ] = [
356+ 'permitAllUsers ' => false ,
357+ 'showToAllUsers ' => false ,
358+ ];
359+ $ formData ['submitMultiple ' ] = false ;
360+ $ formData ['allowEditSubmissions ' ] = false ;
361+ $ formData ['showExpiration ' ] = false ;
362+ $ formData ['expires ' ] = 0 ;
363+ $ formData ['isAnonymous ' ] = false ;
364+ return $ formData ;
365+ }
366+
254367 /**
255368 * Read all information to edit a Form (form, questions, options, except submissions/answers)
256369 *
@@ -730,8 +843,10 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair
730843 throw new OCSBadRequestException ('Invalid extraSettings, will not update. ' );
731844 }
732845
733- if ($ form ->getConfirmationEmailQuestionId () === $ question ->getId ()
734- && !$ question ->isEmailType ($ keyValuePairs ['type ' ] ?? null , $ keyValuePairs ['extraSettings ' ] ?? null )) {
846+ if (
847+ $ form ->getConfirmationEmailQuestionId () === $ question ->getId ()
848+ && !$ question ->isEmailType ($ keyValuePairs ['type ' ] ?? null , $ keyValuePairs ['extraSettings ' ] ?? null )
849+ ) {
735850 $ form ->setConfirmationEmailQuestionId (null );
736851 }
737852
0 commit comments