@@ -236,9 +236,11 @@ public function edit(Mod $mod, Page $page)
236236 $ this ->ensureManualPageEditingAllowed ($ mod );
237237
238238 $ page ->load (['parent ' ]);
239+ $ descendantIds = $ this ->getDescendantIds ($ page );
239240
240241 $ potentialParents = $ mod ->pages ()
241242 ->where ('id ' , '!= ' , $ page ->id )
243+ ->when ($ descendantIds !== [], fn ($ query ) => $ query ->whereNotIn ('id ' , $ descendantIds ))
242244 ->orderBy ('title ' )
243245 ->get ();
244246
@@ -282,6 +284,10 @@ public function update(Request $request, Mod $mod, Page $page)
282284 if ($ parent ->mod_id !== $ mod ->id || $ parent ->id === $ page ->id ) {
283285 abort (422 , 'Invalid parent page. ' );
284286 }
287+
288+ if (in_array ($ parent ->id , $ this ->getDescendantIds ($ page ), true )) {
289+ abort (422 , 'Invalid parent page. ' );
290+ }
285291 }
286292
287293 if (($ validated ['kind ' ] ?? $ page ->kind ) === Page::KIND_CATEGORY ) {
@@ -406,6 +412,34 @@ private function ensureManualPageEditingAllowed(Mod $mod): void
406412 }
407413 }
408414
415+ /**
416+ * Get all descendant page IDs for a page in the same mod.
417+ *
418+ * @return array<int, string>
419+ */
420+ private function getDescendantIds (Page $ page ): array
421+ {
422+ $ descendantIds = [];
423+ $ currentParentIds = [$ page ->id ];
424+
425+ while ($ currentParentIds !== []) {
426+ $ childIds = Page::query ()
427+ ->where ('mod_id ' , $ page ->mod_id )
428+ ->whereIn ('parent_id ' , $ currentParentIds )
429+ ->pluck ('id ' )
430+ ->all ();
431+
432+ if ($ childIds === []) {
433+ break ;
434+ }
435+
436+ $ descendantIds = array_merge ($ descendantIds , $ childIds );
437+ $ currentParentIds = $ childIds ;
438+ }
439+
440+ return $ descendantIds ;
441+ }
442+
409443 /**
410444 * Search pages within a mod.
411445 */
0 commit comments