4444use OCP \AppFramework \OCS \OCSForbiddenException ;
4545use OCP \AppFramework \OCS \OCSNotFoundException ;
4646use OCP \AppFramework \OCSController ;
47+ use OCP \App \IAppManager ;
4748use OCP \BackgroundJob \IJobList ;
4849use OCP \Files \Folder ;
4950use OCP \Files \IMimeTypeDetector ;
@@ -86,6 +87,7 @@ public function __construct(
8687 private readonly SubmissionService $ submissionService ,
8788 private readonly IL10N $ l10n ,
8889 private readonly LoggerInterface $ logger ,
90+ private readonly IAppManager $ appManager ,
8991 private readonly IUserManager $ userManager ,
9092 private readonly IRootFolder $ rootFolder ,
9193 private readonly UploadedFileMapper $ uploadedFileMapper ,
@@ -148,23 +150,39 @@ public function getForms(string $type = 'owned'): DataResponse {
148150 * Return a copy of the form if the parameter $fromId is set
149151 *
150152 * @param ?int $fromId (optional) Id of the form that should be cloned
153+ * @param ?bool $import (optional) If it should import the form from post body
154+ * @param ?array<string, mixed> $formData (optional) The formdata to import
151155 * @return DataResponse<Http::STATUS_CREATED, FormsForm, array{}>
152156 * @throws OCSForbiddenException The user is not allowed to create forms
157+ * @throws OCSBadRequestException Cannot use both fromId and import parameters
158+ * @throws OCSBadRequestException Invalid form data: missing questions
159+ * @throws OCSBadRequestException Invalid form data: unknown properties
160+ * @throws OCSBadRequestException Invalid question data: missing id
161+ * @throws OCSBadRequestException Invalid question data: unknown properties
162+ * @throws OCSBadRequestException Invalid question data: invalid type
163+ * @throws OCSBadRequestException Invalid question data: datetime type no longer supported
164+ * @throws OCSBadRequestException Invalid question data: invalid extraSettings
165+ * @throws OCSBadRequestException Invalid option data: unknown properties
153166 *
154167 * 201: the created form
155168 */
156169 #[CORS ()]
157170 #[NoAdminRequired()]
158171 #[BruteForceProtection(action: 'form ' )]
159172 #[ApiRoute(verb: 'POST ' , url: '/api/v3/forms ' )]
160- public function newForm (?int $ fromId = null ): DataResponse {
173+ public function newForm (?int $ fromId = null , ? bool $ import = false , ? array $ formData = [] ): DataResponse {
161174 // Check if user is allowed
162175 if (!$ this ->configService ->canCreateForms ()) {
163176 $ this ->logger ->debug ('This user is not allowed to create Forms. ' );
164177 throw new OCSForbiddenException ('This user is not allowed to create Forms. ' );
165178 }
166179
167- if ($ fromId === null ) {
180+ // Validate mutually exclusive parameters
181+ if ($ fromId !== null && $ import === true ) {
182+ throw new OCSBadRequestException ('Cannot use both fromId and import parameters ' );
183+ }
184+
185+ if ($ fromId === null && $ import === false ) {
168186 // Create Form
169187 $ form = new Form ();
170188 $ form ->setOwnerId ($ this ->currentUser ->getUID ());
@@ -183,10 +201,37 @@ public function newForm(?int $fromId = null): DataResponse {
183201
184202 $ this ->formMapper ->insert ($ form );
185203 } else {
186- $ oldForm = $ this ->formsService ->getFormIfAllowed ($ fromId , Constants::PERMISSION_EDIT );
204+ // Fill variables from json or database
205+ if ($ import ) {
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+ } else {
226+ $ oldForm = $ this ->formsService ->getFormIfAllowed ($ fromId , Constants::PERMISSION_EDIT );
187227
188- // Read old form, (un)set new form specific data, extend title
189- $ formData = $ oldForm ->read ();
228+ // Read old form, (un)set new form specific data, extend title
229+ $ formData = $ oldForm ->read ();
230+ // Get Questions, set new formId, reinsert
231+ $ questions = $ this ->questionMapper ->findByForm ($ oldForm ->getId ());
232+ $ oldConfirmationEmailQuestionId = $ oldForm ->getConfirmationEmailQuestionId ();
233+ }
234+ // Remove unused data
190235 unset($ formData ['id ' ]);
191236 unset($ formData ['created ' ]);
192237 unset($ formData ['lastUpdated ' ]);
@@ -199,7 +244,9 @@ public function newForm(?int $fromId = null): DataResponse {
199244 $ formData ['ownerId ' ] = $ this ->currentUser ->getUID ();
200245 $ formData ['hash ' ] = $ this ->formsService ->generateFormHash ();
201246 // TRANSLATORS Appendix to the form Title of a duplicated/copied form.
202- $ formData ['title ' ] .= ' - ' . $ this ->l10n ->t ('Copy ' );
247+ if (!$ import ) {
248+ $ formData ['title ' ] .= ' - ' . $ this ->l10n ->t ('Copy ' );
249+ }
203250 $ formData ['access ' ] = [
204251 'permitAllUsers ' => false ,
205252 'showToAllUsers ' => false ,
@@ -213,26 +260,70 @@ public function newForm(?int $fromId = null): DataResponse {
213260 $ form = Form::fromParams ($ formData );
214261 $ this ->formMapper ->insert ($ form );
215262
216- // Get Questions, set new formId, reinsert
217- $ questions = $ this ->questionMapper ->findByForm ($ oldForm ->getId ());
218- $ oldConfirmationEmailQuestionId = $ oldForm ->getConfirmationEmailQuestionId ();
219-
220263 foreach ($ questions as $ oldQuestion ) {
221- $ questionData = $ oldQuestion ->read ();
264+ if ($ import ) {
265+ if (!isset ($ oldQuestion ['id ' ])) {
266+ throw new OCSBadRequestException ('Invalid question data: missing id ' );
267+ }
268+
269+ // Validate question property whitelist
270+ $ allowedQuestionProperties = ['id ' , 'order ' , 'type ' , 'isRequired ' , 'text ' , 'name ' , 'description ' , 'extraSettings ' , 'options ' ];
271+ $ invalidQuestionKeys = array_diff (array_keys ($ oldQuestion ), $ allowedQuestionProperties );
272+ if (!empty ($ invalidQuestionKeys )) {
273+ throw new OCSBadRequestException ('Invalid question data: unknown properties: ' . implode (', ' , $ invalidQuestionKeys ));
274+ }
275+
276+ // Validate question type
277+ $ type = $ oldQuestion ['type ' ] ?? null ;
278+ if ($ type === null || array_search ($ type , Constants::ANSWER_TYPES ) === false ) {
279+ throw new OCSBadRequestException ('Invalid question data: invalid type ' );
280+ }
281+
282+ // Block datetime questions
283+ if ($ type === 'datetime ' ) {
284+ throw new OCSBadRequestException ('Invalid question data: datetime type no longer supported ' );
285+ }
286+
287+ // Validate extraSettings
288+ if (!empty ($ oldQuestion ['extraSettings ' ] ?? [])) {
289+ if (!$ this ->formsService ->areExtraSettingsValid ($ oldQuestion ['extraSettings ' ], $ type )) {
290+ throw new OCSBadRequestException ('Invalid question data: invalid extraSettings ' );
291+ }
292+ }
293+
294+ $ questionData = $ oldQuestion ;
295+ $ oldQuestionId = $ oldQuestion ['id ' ];
296+ $ options = $ oldQuestion ['options ' ] ?? [];
297+ } else {
298+ $ questionData = $ oldQuestion ->read ();
299+ $ oldQuestionId = $ oldQuestion ->getId ();
300+ // Get Options, set new QuestionId, reinsert
301+ $ options = $ this ->optionMapper ->findByQuestion ($ oldQuestionId );
302+ }
222303
223304 unset($ questionData ['id ' ]);
305+ unset($ questionData ['options ' ]);
306+ unset($ questionData ['accept ' ]);
307+
224308 $ questionData ['formId ' ] = $ form ->getId ();
225309 $ newQuestion = Question::fromParams ($ questionData );
226310 $ this ->questionMapper ->insert ($ newQuestion );
227311
228- if (isset ($ oldConfirmationEmailQuestionId ) && $ oldConfirmationEmailQuestionId === $ oldQuestion -> getId () ) {
312+ if (isset ($ oldConfirmationEmailQuestionId ) && $ oldConfirmationEmailQuestionId === $ oldQuestionId ) {
229313 $ form ->setConfirmationEmailQuestionId ($ newQuestion ->getId ());
230314 }
231315
232- // Get Options, set new QuestionId, reinsert
233- $ options = $ this ->optionMapper ->findByQuestion ($ oldQuestion ->getId ());
234316 foreach ($ options as $ oldOption ) {
235- $ optionData = $ oldOption ->read ();
317+ $ optionData = $ import ? $ oldOption : $ oldOption ->read ();
318+
319+ if ($ import ) {
320+ // Validate option property whitelist
321+ $ allowedOptionProperties = ['text ' , 'order ' , 'optionType ' ];
322+ $ invalidOptionKeys = array_diff (array_keys ($ optionData ), $ allowedOptionProperties );
323+ if (!empty ($ invalidOptionKeys )) {
324+ throw new OCSBadRequestException ('Invalid option data: unknown properties: ' . implode (', ' , $ invalidOptionKeys ));
325+ }
326+ }
236327
237328 unset($ optionData ['id ' ]);
238329 $ optionData ['questionId ' ] = $ newQuestion ->getId ();
@@ -251,7 +342,8 @@ public function newForm(?int $fromId = null): DataResponse {
251342 * Read all information to edit a Form (form, questions, options, except submissions/answers)
252343 *
253344 * @param int $formId Id of the form
254- * @return DataResponse<Http::STATUS_OK, FormsForm, array{}>
345+ * @param ?bool $download if the form should be downloaded
346+ * @return DataResponse<Http::STATUS_OK, FormsForm, array{}>|DataDownloadResponse<Http::STATUS_OK, 'application/json', array{}>
255347 * @throws OCSBadRequestException Could not find form
256348 * @throws OCSForbiddenException User has no permissions to get this form
257349 *
@@ -261,9 +353,38 @@ public function newForm(?int $fromId = null): DataResponse {
261353 #[NoAdminRequired()]
262354 #[BruteForceProtection(action: 'form ' )]
263355 #[ApiRoute(verb: 'GET ' , url: '/api/v3/forms/{formId} ' )]
264- public function getForm (int $ formId ): DataResponse {
356+ public function getForm (int $ formId, ? bool $ download ): DataResponse | DataDownloadResponse {
265357 $ form = $ this ->formsService ->getFormIfAllowed ($ formId , Constants::PERMISSION_SUBMIT );
266358
359+ if ($ download ) {
360+ $ formData = $ this ->formsService ->getPublicForm ($ form );
361+ unset($ formData ['hash ' ]);
362+ unset($ formData ['created ' ]);
363+ unset($ formData ['lastUpdated ' ]);
364+ unset($ formData ['lockedBy ' ]);
365+ unset($ formData ['lockedUntil ' ]);
366+ unset($ formData ['permissions ' ]);
367+ unset($ formData ['canSubmit ' ]);
368+ unset($ formData ['isMaxSubmissionsReached ' ]);
369+ unset($ formData ['submissionCount ' ]);
370+ unset($ formData ['filePath ' ]);
371+ unset($ formData ['state ' ]);
372+ unset($ formData ['id ' ]);
373+
374+ foreach ($ formData ['questions ' ] as &$ question ) {
375+ unset($ question ['formId ' ]);
376+ unset($ question ['accept ' ]);
377+ foreach ($ question ['options ' ] as &$ option ) {
378+ unset($ option ['questionId ' ]);
379+ unset($ option ['id ' ]);
380+ }
381+ }
382+
383+ $ downloadFile = ["form " =>$ formData ,"appVersion " =>$ this ->appManager ->getAppVersion ('forms ' )];
384+ return new DataDownloadResponse (
385+ json_encode ($ downloadFile ), $ form ->getTitle (), 'application/json ' );
386+ }
387+
267388 return new DataResponse ($ this ->formsService ->getForm ($ form ));
268389 }
269390
@@ -694,8 +815,10 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair
694815 throw new OCSBadRequestException ('Invalid extraSettings, will not update. ' );
695816 }
696817
697- if ($ form ->getConfirmationEmailQuestionId () === $ question ->getId ()
698- && !$ question ->isEmailType ($ keyValuePairs ['type ' ] ?? null , $ keyValuePairs ['extraSettings ' ] ?? null )) {
818+ if (
819+ $ form ->getConfirmationEmailQuestionId () === $ question ->getId ()
820+ && !$ question ->isEmailType ($ keyValuePairs ['type ' ] ?? null , $ keyValuePairs ['extraSettings ' ] ?? null )
821+ ) {
699822 $ form ->setConfirmationEmailQuestionId (null );
700823 }
701824
0 commit comments