@@ -485,6 +485,7 @@ public function getQuestion(int $formId, int $questionId): DataResponse {
485485 * @param FormsQuestionGridCellType $subtype the new question subtype
486486 * @param string $text the new question title
487487 * @param ?int $fromId (optional) id of the question that should be cloned
488+ * @param ?int $position (optional) the position of the new question
488489 * @return DataResponse<Http::STATUS_CREATED, FormsQuestion, array{}>
489490 * @throws OCSBadRequestException Invalid type
490491 * @throws OCSBadRequestException Datetime question type no longer supported
@@ -499,7 +500,7 @@ public function getQuestion(int $formId, int $questionId): DataResponse {
499500 #[NoAdminRequired()]
500501 #[BruteForceProtection(action: 'form ' )]
501502 #[ApiRoute(verb: 'POST ' , url: '/api/v3/forms/{formId}/questions ' )]
502- public function newQuestion (int $ formId , ?string $ type = null , ?string $ subtype = null , string $ text = '' , ?int $ fromId = null ): DataResponse {
503+ public function newQuestion (int $ formId , ?string $ type = null , ?string $ subtype = null , string $ text = '' , ?int $ fromId = null , ? int $ position = null ): DataResponse {
503504 $ form = $ this ->formsService ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
504505 $ this ->formsService ->obtainFormLock ($ form );
505506
@@ -526,13 +527,20 @@ public function newQuestion(int $formId, ?string $type = null, ?string $subtype
526527 throw new OCSBadRequestException ('Datetime question type no longer supported ' );
527528 }
528529
529- // Retrieve all active questions sorted by Order. Takes the order of the last array-element and adds one.
530+ // Retrieve all active questions sorted by Order.
530531 $ questions = $ this ->questionMapper ->findByForm ($ formId );
531- $ lastQuestion = array_pop ($ questions );
532- if ($ lastQuestion ) {
533- $ questionOrder = $ lastQuestion ->getOrder () + 1 ;
532+
533+ if ($ position !== null ) {
534+ $ position = $ this ->shiftQuestionsForInsert ($ questions , $ position );
535+ $ questionOrder = $ position ;
534536 } else {
535- $ questionOrder = 1 ;
537+ // Append at the end
538+ $ lastQuestion = array_pop ($ questions );
539+ if ($ lastQuestion ) {
540+ $ questionOrder = $ lastQuestion ->getOrder () + 1 ;
541+ } else {
542+ $ questionOrder = 1 ;
543+ }
536544 }
537545
538546 $ question = new Question ();
@@ -574,7 +582,13 @@ public function newQuestion(int $formId, ?string $type = null, ?string $subtype
574582
575583 $ questionData = $ sourceQuestion ->read ();
576584 unset($ questionData ['id ' ]);
577- $ questionData ['order ' ] = end ($ allQuestions )->getOrder () + 1 ;
585+
586+ if ($ position !== null ) {
587+ $ position = $ this ->shiftQuestionsForInsert ($ allQuestions , $ position );
588+ $ questionData ['order ' ] = $ position ;
589+ } else {
590+ $ questionData ['order ' ] = end ($ allQuestions )->getOrder () + 1 ;
591+ }
578592
579593 $ newQuestion = Question::fromParams ($ questionData );
580594 $ this ->questionMapper ->insert ($ newQuestion );
@@ -2000,4 +2014,36 @@ private function handleOwnerTransfer(Form $form, int $formId, string $currentUse
20002014 $ this ->formMapper ->update ($ form );
20012015 return new DataResponse ($ form ->getOwnerId ());
20022016 }
2017+
2018+ /**
2019+ * Shift existing question orders to make room for an insertion at $position.
2020+ * Normalizes the given $position to the valid range and updates all questions
2021+ * with order >= $position by incrementing their order by one.
2022+ *
2023+ * @param Question[] $questions
2024+ * @param int $position 1-based desired position
2025+ * @return int normalized position
2026+ */
2027+ private function shiftQuestionsForInsert (array $ questions , int $ position ): int {
2028+ $ maxOrder = 0 ;
2029+ if (count ($ questions ) > 0 ) {
2030+ $ maxOrder = end ($ questions )->getOrder ();
2031+ }
2032+ if ($ position < 1 ) {
2033+ $ position = 1 ;
2034+ }
2035+ if ($ position > $ maxOrder + 1 ) {
2036+ $ position = $ maxOrder + 1 ;
2037+ }
2038+
2039+ for ($ i = count ($ questions ) - 1 ; $ i >= 0 ; $ i --) {
2040+ $ q = $ questions [$ i ];
2041+ if ($ q ->getOrder () >= $ position ) {
2042+ $ q ->setOrder ($ q ->getOrder () + 1 );
2043+ $ this ->questionMapper ->update ($ q );
2044+ }
2045+ }
2046+
2047+ return $ position ;
2048+ }
20032049}
0 commit comments