@@ -841,9 +841,11 @@ public function isBroken()
841841 *
842842 * Nodes with invalid parent are saved as roots.
843843 *
844- * @return int The number of fixed nodes
844+ * @param null|NodeTrait|Model $root
845+ *
846+ * @return int The number of changed nodes
845847 */
846- public function fixTree ()
848+ public function fixTree ($ root = null )
847849 {
848850 $ columns = [
849851 $ this ->model ->getKeyName (),
@@ -852,48 +854,78 @@ public function fixTree()
852854 $ this ->model ->getRgtName (),
853855 ];
854856
855- $ dictionary = $ this ->model ->newNestedSetQuery ()
856- ->defaultOrder ()
857- ->get ($ columns )
858- ->groupBy ($ this ->model ->getParentIdName ())
859- ->all ();
857+ $ dictionary = $ this ->model
858+ ->newNestedSetQuery ()
859+ ->when ($ root , function (self $ query , $ root ) {
860+ $ query ->whereDescendantOf ($ root );
861+ })
862+ ->defaultOrder ()
863+ ->get ($ columns )
864+ ->groupBy ($ this ->model ->getParentIdName ())
865+ ->all ();
866+
867+ return $ this ->fixNodes ($ dictionary , $ root );
868+ }
860869
861- return self ::fixNodes ($ dictionary );
870+ /**
871+ * @param NodeTrait|Model $root
872+ *
873+ * @return int
874+ */
875+ public function fixSubtree ($ root )
876+ {
877+ return $ this ->fixTree ($ root );
862878 }
863879
864880 /**
865881 * @param array $dictionary
882+ * @param NodeTrait|Model|null $parent
866883 *
867884 * @return int
868885 */
869- protected static function fixNodes (array &$ dictionary )
886+ protected function fixNodes (array &$ dictionary, $ parent = null )
870887 {
871- $ fixed = 0 ;
888+ $ parentId = $ parent ? $ parent ->getKey () : null ;
889+ $ cut = $ parent ? $ parent ->getLft () + 1 : 1 ;
890+
891+ $ updated = [];
892+ $ moved = 0 ;
872893
873- $ cut = self ::reorderNodes ($ dictionary , $ fixed );
894+ $ cut = self ::reorderNodes ($ dictionary , $ updated , $ parentId , $ cut );
874895
875896 // Save nodes that have invalid parent as roots
876897 while ( ! empty ($ dictionary )) {
877898 $ dictionary [null ] = reset ($ dictionary );
878899
879900 unset($ dictionary [key ($ dictionary )]);
880901
881- $ cut = self ::reorderNodes ($ dictionary , $ fixed , null , $ cut );
902+ $ cut = self ::reorderNodes ($ dictionary , $ updated , $ parentId , $ cut );
903+ }
904+
905+ if ($ parent && ($ grown = $ cut - $ parent ->getRgt ()) != 0 ) {
906+ $ moved = $ this ->model ->newScopedQuery ()->makeGap ($ parent ->getRgt () + 1 , $ grown );
907+
908+ $ updated [] = $ parent ->rawNode ($ parent ->getLft (), $ cut , $ parent ->getParentId ());
909+ }
910+
911+ foreach ($ updated as $ model ) {
912+ $ model ->save ();
882913 }
883914
884- return $ fixed ;
915+ return count ( $ updated ) + $ moved ;
885916 }
886917
887918 /**
888919 * @param array $dictionary
889- * @param int $fixed
920+ * @param array $updated
890921 * @param $parentId
891922 * @param int $cut
892923 *
893924 * @return int
925+ * @internal param int $fixed
894926 */
895- protected static function reorderNodes (array & $ dictionary , & $ fixed ,
896- $ parentId = null , $ cut = 1
927+ protected static function reorderNodes (
928+ array & $ dictionary , array & $ updated , $ parentId = null , $ cut = 1
897929 ) {
898930 if ( ! isset ($ dictionary [$ parentId ])) {
899931 return $ cut ;
@@ -903,17 +935,10 @@ protected static function reorderNodes(array &$dictionary, &$fixed,
903935 foreach ($ dictionary [$ parentId ] as $ model ) {
904936 $ lft = $ cut ;
905937
906- $ cut = self ::reorderNodes ($ dictionary ,
907- $ fixed ,
908- $ model ->getKey (),
909- $ cut + 1 );
938+ $ cut = self ::reorderNodes ($ dictionary , $ updated , $ model ->getKey (), $ cut + 1 );
910939
911- $ rgt = $ cut ;
912-
913- if ($ model ->rawNode ($ lft , $ rgt , $ parentId )->isDirty ()) {
914- $ model ->save ();
915-
916- $ fixed ++;
940+ if ($ model ->rawNode ($ lft , $ cut , $ parentId )->isDirty ()) {
941+ $ updated [] = $ model ;
917942 }
918943
919944 ++$ cut ;
@@ -932,20 +957,29 @@ protected static function reorderNodes(array &$dictionary, &$fixed,
932957 * @param array $data
933958 * @param bool $delete Whether to delete nodes that exists but not in the data
934959 * array
960+ * @param null $root
935961 *
936962 * @return int
937963 */
938- public function rebuildTree (array $ data , $ delete = false )
964+ public function rebuildTree (array $ data , $ delete = false , $ root = null )
939965 {
940966 if ($ this ->model ->usesSoftDelete ()) {
941967 $ this ->withTrashed ();
942968 }
943969
944- $ existing = $ this ->get ()->getDictionary ();
970+ $ existing = $ this
971+ ->when ($ root , function (self $ query , $ root ) {
972+ $ query ->whereDescendantOf ($ root );
973+ })
974+ ->get ()
975+ ->getDictionary ();
976+
945977 $ dictionary = [];
978+ $ parentId = $ root ? $ root ->getKey () : null ;
946979
947- $ this ->buildRebuildDictionary ($ dictionary , $ data , $ existing );
980+ $ this ->buildRebuildDictionary ($ dictionary , $ data , $ existing, $ parentId );
948981
982+ /** @var Model|NodeTrait $model */
949983 if ( ! empty ($ existing )) {
950984 if ($ delete && ! $ this ->model ->usesSoftDelete ()) {
951985 $ this ->model
@@ -967,7 +1001,19 @@ public function rebuildTree(array $data, $delete = false)
9671001 }
9681002 }
9691003
970- return $ this ->fixNodes ($ dictionary );
1004+ return $ this ->fixNodes ($ dictionary , $ root );
1005+ }
1006+
1007+ /**
1008+ * @param $root
1009+ * @param array $data
1010+ * @param bool $delete
1011+ *
1012+ * @return int
1013+ */
1014+ public function rebuildSubtree ($ root , array $ data , $ delete = false )
1015+ {
1016+ return $ this ->rebuildTree ($ data , $ delete , $ root );
9711017 }
9721018
9731019 /**
@@ -984,10 +1030,12 @@ protected function buildRebuildDictionary(array &$dictionary,
9841030 $ keyName = $ this ->model ->getKeyName ();
9851031
9861032 foreach ($ data as $ itemData ) {
1033+ /** @var NodeTrait|Model $model */
1034+
9871035 if ( ! isset ($ itemData [$ keyName ])) {
9881036 $ model = $ this ->model ->newInstance ($ this ->model ->getAttributes ());
9891037
990- // We will save it as raw node since tree will be fixed
1038+ // Set some values that will be fixed later
9911039 $ model ->rawNode (0 , 0 , $ parentId );
9921040 } else {
9931041 if ( ! isset ($ existing [$ key = $ itemData [$ keyName ]])) {
@@ -996,6 +1044,9 @@ protected function buildRebuildDictionary(array &$dictionary,
9961044
9971045 $ model = $ existing [$ key ];
9981046
1047+ // Disable any tree actions
1048+ $ model ->rawNode ($ model ->getLft (), $ model ->getRgt (), $ parentId );
1049+
9991050 unset($ existing [$ key ]);
10001051 }
10011052
0 commit comments