@@ -78,15 +78,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7878 {
7979 $ id = $ input ->getOption ('list-id ' );
8080 $ batch = $ input ->getOption ('batch-limit ' );
81- $ max = $ input ->getOption ('max-contacts ' );
81+ $ max = $ input ->getOption ('max-contacts ' ) ? ( int ) $ input -> getOption ( ' max-contacts ' ) : null ;
8282 $ enableTimeMeasurement = (bool ) $ input ->getOption ('timing ' );
8383 $ output = ($ input ->getOption ('quiet ' )) ? new NullOutput () : $ output ;
8484 $ excludeSegments = $ input ->getOption ('exclude ' );
8585
86- if (is_numeric ($ max )) {
87- $ max = (int ) $ max ;
88- }
89-
9086 if (!$ this ->checkRunStatus ($ input , $ output , $ id )) {
9187 return \Symfony \Component \Console \Command \Command::SUCCESS ;
9288 }
@@ -104,7 +100,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
104100 return \Symfony \Component \Console \Command \Command::FAILURE ;
105101 }
106102
107- $ this ->rebuildSegment ($ list , $ batch , $ max , $ output );
103+ // Track already rebuilt lists to avoid rebuilding them multiple times
104+ $ rebuiltLists = [];
105+
106+ // First check if this segment has dependencies and rebuild them
107+ if ($ list ->hasFilterTypeOf ('leadlist ' )) {
108+ $ this ->rebuildDependentSegments ($ list , $ rebuiltLists , $ batch , $ max , $ output , $ enableTimeMeasurement , [], $ excludeSegments );
109+ }
110+
111+ // Add the current list ID to the rebuilt lists to avoid rebuilding it again
112+ $ rebuiltLists [] = (int ) $ list ->getId ();
113+
114+ $ this ->rebuildSegment ($ list , $ batch , $ max , $ output , $ enableTimeMeasurement );
108115 } else {
109116 $ filter = [
110117 'iterable_mode ' => true ,
@@ -121,18 +128,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int
121128 ],
122129 ];
123130 }
124- $ leadLists = $ this ->listModel ->getEntities ($ filter );
125131
132+ $ rebuiltLists = [];
133+ $ leadLists = $ this ->listModel ->getEntities ($ filter );
134+
135+ /** @var LeadList $leadList */
126136 foreach ($ leadLists as $ leadList ) {
127- $ startTimeForSingleSegment = time ();
128- $ this -> rebuildSegment ( $ leadList , $ batch , $ max , $ output );
129- if ( $ enableTimeMeasurement ) {
130- $ totalTime = round ( microtime ( true ) - $ startTimeForSingleSegment , 2 );
131- $ output -> writeln ( ' <fg=cyan> ' . $ this -> translator -> trans ( ' mautic.lead.list.rebuild.contacts.time ' , [ ' %time% ' => $ totalTime ]). ' </> ' . "\n" ) ;
137+ $ listId = $ leadList -> getId ();
138+
139+ // Skip if already rebuilt
140+ if ( in_array ( $ listId , $ rebuiltLists )) {
141+ continue ;
132142 }
133- unset($ leadList );
143+
144+ // Process any dependent segments first (segments that are used as filters in this segment)
145+ if ($ leadList ->hasFilterTypeOf ('leadlist ' )) {
146+ $ this ->rebuildDependentSegments ($ leadList , $ rebuiltLists , $ batch , $ max , $ output , $ enableTimeMeasurement , [], $ excludeSegments );
147+ }
148+
149+ // Add the current list ID to the rebuilt lists to avoid rebuilding it again
150+ $ rebuiltLists [] = $ listId ;
151+
152+ // Rebuild the current segment
153+ $ this ->rebuildSegment ($ leadList , $ batch , $ max , $ output , $ enableTimeMeasurement );
134154 }
135- unset($ leadLists );
136155 }
137156
138157 $ this ->completeRun ();
@@ -145,23 +164,103 @@ protected function execute(InputInterface $input, OutputInterface $output): int
145164 return \Symfony \Component \Console \Command \Command::SUCCESS ;
146165 }
147166
148- private function rebuildSegment (LeadList $ segment , int $ batch , int $ max , OutputInterface $ output ): void
149- {
150- if ($ segment ->isPublished ()) {
151- $ output ->writeln ('<info> ' .$ this ->translator ->trans ('mautic.lead.list.rebuild.rebuilding ' , ['%id% ' => $ segment ->getId ()]).'</info> ' );
152- $ startTime = microtime (true );
153- $ processed = $ this ->listModel ->rebuildListLeads ($ segment , $ batch , $ max , $ output );
154- $ rebuildTime = round (microtime (true ) - $ startTime , 2 );
155- if (0 >= (int ) $ max ) {
156- // Only full segment rebuilds count
157- $ segment ->setLastBuiltDateToCurrentDatetime ();
158- $ segment ->setLastBuiltTime ($ rebuildTime );
159- $ this ->listModel ->saveEntity ($ segment );
167+ /**
168+ * @param array<int> $rebuiltLists List of segment IDs that have already been rebuilt
169+ * @param array<int> $dependencyChain Chain of segment IDs to detect circular dependencies
170+ * @param array<int|string> $excludeSegments List of segment IDs to exclude from rebuilding
171+ *
172+ * @param-out array<int> $rebuiltLists Updated list of segment IDs that have been rebuilt
173+ */
174+ private function rebuildDependentSegments (
175+ LeadList $ leadList ,
176+ array &$ rebuiltLists ,
177+ int $ batch ,
178+ ?int $ max ,
179+ OutputInterface $ output ,
180+ bool $ enableTimeMeasurement ,
181+ array $ dependencyChain = [],
182+ array $ excludeSegments = [],
183+ ): void {
184+ // Track the current segment in our dependency chain
185+ $ currentId = $ leadList ->getId ();
186+ $ dependencyChain [] = $ currentId ;
187+
188+ foreach ($ leadList ->getFilters () as $ filter ) {
189+ if ('leadlist ' === $ filter ['type ' ]) {
190+ foreach ($ filter ['filter ' ] ?? [] as $ dependentListId ) {
191+ $ dependentListId = (int ) $ dependentListId ;
192+
193+ // Skip if already rebuilt or in exclude list
194+ if (in_array ($ dependentListId , $ rebuiltLists ) || in_array ($ dependentListId , $ excludeSegments )) {
195+ continue ;
196+ }
197+
198+ // Check for circular dependency
199+ if (in_array ($ dependentListId , $ dependencyChain )) {
200+ $ output ->writeln (
201+ '<error> ' .$ this ->translator ->trans (
202+ 'Circular dependency detected in segment chain: %chain% ' ,
203+ ['%chain% ' => implode (' → ' , array_merge ($ dependencyChain , [$ dependentListId ]))]
204+ ).'</error> '
205+ );
206+ continue ; // Skip this dependency to prevent infinite recursion
207+ }
208+
209+ $ dependentLeadList = $ this ->listModel ->getEntity ($ dependentListId );
210+ if (!$ dependentLeadList ) {
211+ continue ; // Skip if the dependent segment doesn't exist - it may have been deleted
212+ }
213+
214+ // Check if this dependent segment has its own dependencies
215+ if ($ dependentLeadList ->hasFilterTypeOf ('leadlist ' )) {
216+ // Recursively process this segment's dependencies first, passing the current chain
217+ $ this ->rebuildDependentSegments (
218+ $ dependentLeadList ,
219+ $ rebuiltLists ,
220+ $ batch ,
221+ $ max ,
222+ $ output ,
223+ $ enableTimeMeasurement ,
224+ $ dependencyChain ,
225+ $ excludeSegments
226+ );
227+ }
228+
229+ // Now rebuild this dependent segment
230+ $ this ->rebuildSegment ($ dependentLeadList , $ batch , $ max , $ output , $ enableTimeMeasurement );
231+ $ rebuiltLists [] = $ dependentListId ;
232+ }
160233 }
234+ }
235+ }
161236
162- $ output ->writeln (
163- '<comment> ' .$ this ->translator ->trans ('mautic.lead.list.rebuild.leads_affected ' , ['%leads% ' => $ processed ]).'</comment> '
164- );
237+ private function rebuildSegment (LeadList $ segment , int $ batch , ?int $ max , OutputInterface $ output , bool $ enableTimeMeasurement = false ): void
238+ {
239+ if (!$ segment ->isPublished ()) {
240+ return ;
241+ }
242+
243+ $ output ->writeln ('<info> ' .$ this ->translator ->trans ('mautic.lead.list.rebuild.rebuilding ' , ['%id% ' => $ segment ->getId ()]).'</info> ' );
244+ $ startTime = microtime (true );
245+ $ processed = $ this ->listModel ->rebuildListLeads ($ segment , $ batch , $ max , $ output );
246+ $ rebuildTime = round (microtime (true ) - $ startTime , 2 );
247+
248+ if (0 >= (int ) $ max ) {
249+ // Only full segment rebuilds count
250+ $ segment ->setLastBuiltDateToCurrentDatetime ();
251+ $ segment ->setLastBuiltTime ($ rebuildTime );
252+ $ this ->listModel ->saveEntity ($ segment );
253+ }
254+
255+ $ output ->writeln (
256+ '<comment> ' .$ this ->translator ->trans ('mautic.lead.list.rebuild.leads_affected ' , ['%leads% ' => $ processed ]).'</comment> '
257+ );
258+
259+ if ($ enableTimeMeasurement ) {
260+ $ output ->writeln ('<fg=cyan> ' .$ this ->translator ->trans (
261+ 'mautic.lead.list.rebuild.contacts.time ' ,
262+ ['%time% ' => $ rebuildTime ]
263+ ).'</> ' ."\n" );
165264 }
166265 }
167266}
0 commit comments