@@ -487,6 +487,7 @@ public function getQuestion(int $formId, int $questionId): DataResponse {
487487 * @param FormsQuestionGridCellType $subtype the new question subtype
488488 * @param string $text the new question title
489489 * @param ?int $fromId (optional) id of the question that should be cloned
490+ * @param ?int $position (optional) the position of the new question
490491 * @return DataResponse<Http::STATUS_CREATED, FormsQuestion, array{}>
491492 * @throws OCSBadRequestException Invalid type
492493 * @throws OCSBadRequestException Datetime question type no longer supported
@@ -501,7 +502,7 @@ public function getQuestion(int $formId, int $questionId): DataResponse {
501502 #[NoAdminRequired()]
502503 #[BruteForceProtection(action: 'form ' )]
503504 #[ApiRoute(verb: 'POST ' , url: '/api/v3/forms/{formId}/questions ' )]
504- public function newQuestion (int $ formId , ?string $ type = null , ?string $ subtype = null , string $ text = '' , ?int $ fromId = null ): DataResponse {
505+ public function newQuestion (int $ formId , ?string $ type = null , ?string $ subtype = null , string $ text = '' , ?int $ fromId = null , ? int $ position = null ): DataResponse {
505506 $ form = $ this ->formsService ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
506507 $ this ->formsService ->obtainFormLock ($ form );
507508
@@ -528,13 +529,20 @@ public function newQuestion(int $formId, ?string $type = null, ?string $subtype
528529 throw new OCSBadRequestException ('Datetime question type no longer supported ' );
529530 }
530531
531- // Retrieve all active questions sorted by Order. Takes the order of the last array-element and adds one.
532+ // Retrieve all active questions sorted by Order.
532533 $ questions = $ this ->questionMapper ->findByForm ($ formId );
533- $ lastQuestion = array_pop ($ questions );
534- if ($ lastQuestion ) {
535- $ questionOrder = $ lastQuestion ->getOrder () + 1 ;
534+
535+ if ($ position !== null ) {
536+ $ position = $ this ->shiftQuestionsForInsert ($ questions , $ position );
537+ $ questionOrder = $ position ;
536538 } else {
537- $ questionOrder = 1 ;
539+ // Append at the end
540+ $ lastQuestion = array_pop ($ questions );
541+ if ($ lastQuestion ) {
542+ $ questionOrder = $ lastQuestion ->getOrder () + 1 ;
543+ } else {
544+ $ questionOrder = 1 ;
545+ }
538546 }
539547
540548 $ question = new Question ();
@@ -572,7 +580,13 @@ public function newQuestion(int $formId, ?string $type = null, ?string $subtype
572580
573581 $ questionData = $ sourceQuestion ->read ();
574582 unset($ questionData ['id ' ]);
575- $ questionData ['order ' ] = end ($ allQuestions )->getOrder () + 1 ;
583+
584+ if ($ position !== null ) {
585+ $ position = $ this ->shiftQuestionsForInsert ($ allQuestions , $ position );
586+ $ questionData ['order ' ] = $ position ;
587+ } else {
588+ $ questionData ['order ' ] = end ($ allQuestions )->getOrder () + 1 ;
589+ }
576590
577591 $ newQuestion = Question::fromParams ($ questionData );
578592 $ this ->questionMapper ->insert ($ newQuestion );
@@ -1992,4 +2006,36 @@ private function handleOwnerTransfer(Form $form, int $formId, string $currentUse
19922006 $ this ->formMapper ->update ($ form );
19932007 return new DataResponse ($ form ->getOwnerId ());
19942008 }
2009+
2010+ /**
2011+ * Shift existing question orders to make room for an insertion at $position.
2012+ * Normalizes the given $position to the valid range and updates all questions
2013+ * with order >= $position by incrementing their order by one.
2014+ *
2015+ * @param Question[] $questions
2016+ * @param int $position 1-based desired position
2017+ * @return int normalized position
2018+ */
2019+ private function shiftQuestionsForInsert (array $ questions , int $ position ): int {
2020+ $ maxOrder = 0 ;
2021+ if (count ($ questions ) > 0 ) {
2022+ $ maxOrder = end ($ questions )->getOrder ();
2023+ }
2024+ if ($ position < 1 ) {
2025+ $ position = 1 ;
2026+ }
2027+ if ($ position > $ maxOrder + 1 ) {
2028+ $ position = $ maxOrder + 1 ;
2029+ }
2030+
2031+ for ($ i = count ($ questions ) - 1 ; $ i >= 0 ; $ i --) {
2032+ $ q = $ questions [$ i ];
2033+ if ($ q ->getOrder () >= $ position ) {
2034+ $ q ->setOrder ($ q ->getOrder () + 1 );
2035+ $ this ->questionMapper ->update ($ q );
2036+ }
2037+ }
2038+
2039+ return $ position ;
2040+ }
19952041}
0 commit comments