@@ -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 );
@@ -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