src/AppBundle/Service/EstateFlowManager.php line 172

Open in your IDE?
  1. <?php
  2. namespace App\AppBundle\Service;
  3. use App\AppBundle\Entity\EstateFlow;
  4. use App\AppBundle\Entity\ModelEntity;
  5. use App\AppBundle\Entity\EstateFlowScenario;
  6. use App\AppBundle\Entity\EstateFlowScenarioTimeline;
  7. use App\AppBundle\Service\BaseEntityManagerHousehold;
  8. use App\AppBundle\Service\EstateFlow\EstateFlowService;
  9. use App\Uplifted\BaseBundle\Exception\BusinessException;
  10. use App\AdminBundle\Service\AwsSesService;
  11. use App\AppBundle\Entity\EstateFlowCache\EstateFlowCache;
  12. use App\AppBundle\Entity\EstateFlowTransition;
  13. use App\AppBundle\Entity\EstateFlowTrigger;
  14. use App\AppBundle\Entity\EstateFlowTriggerEvent;
  15. use App\AppBundle\Service\EstateFlowScenarioManager;
  16. use App\AppBundle\Service\EstateFlowTransitionManager;
  17. use App\AppBundle\Entity\ValuableResource;
  18. class EstateFlowManager extends BaseEntityManagerHousehold
  19. {
  20.     protected $estateFlowActionStrategy;
  21.     protected $frontendBaseUrl;
  22.     protected $twigTemplateEngine;
  23.     protected $awsSesService;
  24.     protected $estateFlowService;
  25.     protected $estateFlowScenarioManager;
  26.     protected $estateFlowTransitionManager;
  27.     public function setFrontendBaseUrl(string $frontendBaseUrl)
  28.     {
  29.         $this->frontendBaseUrl $frontendBaseUrl;
  30.     }
  31.     public function setTwigTemplateEngine($twigTemplateEngine)
  32.     {
  33.         if ($this->twigTemplateEngine === null) {
  34.             $this->twigTemplateEngine $twigTemplateEngine;
  35.         }
  36.     }
  37.     public function setAwsSesService(AwsSesService $awsSesService)
  38.     {
  39.         $this->awsSesService $awsSesService;
  40.     }
  41.     public function setEstateFlowService(EstateFlowService $estateFlowService)
  42.     {
  43.         $this->estateFlowService $estateFlowService;
  44.     }
  45.     public function setEstateFlowScenarioManager(EstateFlowScenarioManager $estateFlowScenarioManager)
  46.     {
  47.         $this->estateFlowScenarioManager $estateFlowScenarioManager;
  48.     }
  49.     public function setEstateFlowTransitionManager(EstateFlowTransitionManager $estateFlowTransitionManager)
  50.     {
  51.         $this->estateFlowTransitionManager $estateFlowTransitionManager;
  52.     }
  53.     public function getEntityName()
  54.     {
  55.         return 'estateFlow';
  56.     }
  57.     public function getManagedEntityClass()
  58.     {
  59.         return EstateFlow::class;
  60.     }
  61.     public function getCreateRequiredFieldsNames(array $values = array())
  62.     {
  63.         $requiredFields = array('modelEntity''sequence''result''status');
  64.         return $requiredFields;
  65.     }
  66.     protected function setCreateDefaultValues(array $values, array $additionalData = array())
  67.     {
  68.         // if status is not set, set it to ok
  69.         if (!isset($values['status'])){
  70.             $values['status'] = EstateFlow::STATUS_OK;  
  71.         }
  72.         return $values;
  73.     }
  74.     public function validateValues($values$entity null)
  75.     {
  76.         $errors = array();
  77.         $this->simpleValidateValues(
  78.             $values,
  79.             $errors,
  80.             array(
  81.                 'status' => array('choices' => EstateFlow::STATUS_OPTIONS)
  82.             )
  83.         );
  84.         $this->validateEntity($errors$values'modelEntity'ModelEntity::class, true);
  85.         $this->validateEntity($errors$values'sequence'EstateFlowScenario::class, true);
  86.         $this->validateEntity($errors$values'timeline'EstateFlowScenarioTimeline::class, true);
  87.         if (isset($entity) && 
  88.             (isset($values['modelEntity']) ||
  89.             isset($values['sequence']) ||
  90.             isset($values['timeline'])
  91.         )){
  92.             throw new BusinessException("Cant change identity fields after creation");
  93.         }
  94.         if (!isset($entity)){
  95.             $exist $this->getEntityRepo()->existEstateFlow($values['modelEntity'], $values['sequence'], $values['timeline']);
  96.             if ($exist){
  97.                 throw new BusinessException("Try to save duplicated flow");
  98.             }
  99.         }
  100.         return $errors;
  101.     }
  102.     public function setValues(&$entity$values)
  103.     {
  104.         $this->simpleSetValues(
  105.             $entity,
  106.             $values,
  107.             array('result' => 'string''status' => 'string''relatedData' => 'string')
  108.         );
  109.         $this->setEntityReferenceFieldValue($entity'modelEntity'$valuesModelEntity::class);
  110.         $this->setEntityReferenceFieldValue($entity'sequence'$valuesEstateFlowScenario::class);
  111.         if (isset($values['timeline'])){
  112.             $this->setEntityReferenceFieldValue($entity'timeline'$valuesEstateFlowScenarioTimeline::class);    
  113.         }
  114.         
  115.     }
  116.     public function sendFlowFailEmail($errorMessage$householdHashId$modelEntityId$sequenceId$timelineId null)
  117.     {
  118.         
  119.         $flowUrl $this->frontendBaseUrl .'/household/'$householdHashId .'/model-entities/estate-report/'.$modelEntityId.'/estate-flow-report?scenarioId='.$sequenceId;
  120.         if ($timelineId !== null){
  121.             $flowUrl $flowUrl .'&timelineId='.$timelineId;
  122.         }
  123.         $parameters = array(
  124.             'flowUrl' => $flowUrl,
  125.             'errorMessage'=> $errorMessage
  126.         );
  127.         $html $this->twigTemplateEngine->render('Emails/flowFailAlert.html.twig',
  128.             $parameters);
  129.         $this->awsSesService->sendStaffEmail(array(
  130.             'from' => 'Aristotle <no-reply@iwplatform.com>',
  131.             'subject' => 'Flow fail alert',
  132.             'replyTo' => 'support@iwplatform.com',
  133.             'body' => $html
  134.         ));        
  135.     }
  136.     public function saveEstateFlow($values) {
  137.         $estateFlow $this->getEntityRepo()->findEstateFlow($values['modelEntity'],$values['sequence'], $values['timeline']);
  138.         if (isset($estateFlow)){
  139.             $this->safeUpdate($estateFlow, ['result'=>$values['result']]);
  140.         } else {
  141.             $this->safeCreate($values);
  142.         }
  143.     }
  144.     public function resolveSequenceEstateFlows($modelEntityId$sequenceId): array
  145.     {
  146.         $estateFlows = [];
  147.         
  148.         // Get the sequence
  149.         $sequence = (object)$this->estateFlowScenarioManager->find($sequenceId);
  150.         if (!$sequence) {
  151.             throw new \Exception('No sequence defined');
  152.         }
  153.         
  154.         // Resolve default timeline (null timelineId)
  155.         $defaultEstateFlow $this->resolveEstateFlow($modelEntityId$sequenceIdnull);
  156.         $estateFlows[] = json_encode($defaultEstateFlow->estateFlow);
  157.         
  158.         // Resolve each timeline in the sequence
  159.         foreach ($sequence->getTimelines() as $timeline) {
  160.             $timelineEstateFlow $this->resolveEstateFlow($modelEntityId$sequenceId$timeline->getId());
  161.             $estateFlows[] =  json_encode($timelineEstateFlow->estateFlow);
  162.         }
  163.         
  164.         return $estateFlows;
  165.     }
  166.     public function resolveEstateFlow($modelEntityId$sequenceId$timelineId$saveCache=true$ignoreCache=false){
  167.         
  168.         $fromCache 'true';
  169.         // Allow cache for the moment
  170.         $estateFlow $this->getEntityRepo()->findEstateFlow($modelEntityId$sequenceId$timelineId);
  171.         
  172.         $estateFlowResult null;
  173.         if ($ignoreCache || !isset($estateFlow) || $estateFlow->getStatus() != EstateFlow::STATUS_OK){
  174.             $fromCache 'false';
  175.             // Resolve estate flow
  176.             $estateFlowResult $this->estateFlowService->resolveEstateFlow($modelEntityId$sequenceId$timelineId);
  177.             // Set related data to estateFlow for refrresh indexing   
  178.             $relatedData $this->getRelatedData($estateFlowResult);
  179.             // Set estateFlow result to json}
  180.             //$estateFlowResult = $this->breakCircularReferences($estateFlowResult);
  181.             //var_dump('start serialize');
  182.             $estateFlowResult json_encode($estateFlowResult->jsonSerialize(), JSON_UNESCAPED_UNICODE);
  183.             //exit('end serialize');
  184.             //var_dump('end serialize');
  185.             if ($saveCache){
  186.                 // If estateFlow exists, update it
  187.                 if (isset($estateFlow)){
  188.                     $estateFlow =  $this->safeUpdate($estateFlow, ['result'=>gzcompress($estateFlowResult9), 'relatedData'=>$relatedData'status'=>EstateFlow::STATUS_OK]);
  189.                 } else {
  190.                     // If estateFlow does not exist, create it
  191.                     $values = [
  192.                         'modelEntity'=>$modelEntityId,
  193.                         'sequence'=>$sequenceId,
  194.                         'timeline'=>$timelineId,
  195.                         'result'=>gzcompress($estateFlowResult9),
  196.                         'relatedData'=>$relatedData,
  197.                         'status'=>EstateFlow::STATUS_OK
  198.                     ];
  199.                     $estateFlow $this->safeCreate($values);
  200.                 }
  201.             } 
  202.         } else {
  203.             $estateFlowResult $estateFlow->getResult();
  204.         }
  205.         return (object)['estateFlow'=>$estateFlowResult'fromCache'=>$fromCache];
  206.     }
  207.     /**
  208.  * Finds and breaks circular references in arrays and objects
  209.  * 
  210.  * @param mixed $data The array or object to process
  211.  * @param array $processed Keep track of processed objects (for internal recursion)
  212.  * @param int $maxDepth Maximum recursion depth to prevent stack overflow
  213.  * @param int $currentDepth Current recursion depth (for internal use)
  214.  * @return mixed The processed data with circular references removed
  215.  */
  216. function breakCircularReferences(&$data, array &$processed = [], int $maxDepth 100int $currentDepth 0)
  217. {
  218.     // Check for recursion depth
  219.     if ($currentDepth >= $maxDepth) {
  220.         return null// Replace with null when max depth reached
  221.     }
  222.     
  223.     // If not an array or object, return as is
  224.     if (!is_array($data) && !is_object($data)) {
  225.         return $data;
  226.     }
  227.     
  228.     // Create a unique ID for this array/object to track processed items
  229.     $id is_object($data) ? spl_object_id($data) : md5(serialize($data));
  230.     
  231.     // If we've seen this exact object/array before, we found a circular reference
  232.     if (isset($processed[$id])) {
  233.         if (is_object($data)) {
  234.             // Print object class name for debugging circular references
  235.             error_log('Circular reference found in class: ' get_class($data));
  236.             // Check if object has getReferenceVersion method before attempting circular reference handling
  237.             debug_print_backtrace();
  238.             exit();
  239.             if (is_object($data) && method_exists($data'getReferenceVersion')) {
  240.                 return $data->getReferenceVersion();
  241.             } else {
  242.                 // For objects, we could return a placeholder or null depending on needs
  243.                 return null// Or create a simple representation like "(circular reference)"
  244.             }
  245.         } else {
  246.             // For arrays, return an empty array or something indicating the circular reference
  247.             return []; // Or ['__circular_reference' => true]
  248.         }
  249.     }
  250.     
  251.     // Mark this array/object as processed
  252.     $processed[$id] = true;
  253.     
  254.     // Handle arrays
  255.     if (is_array($data)) {
  256.         foreach ($data as $key => &$value) {
  257.             $value $this->breakCircularReferences($value$processed$maxDepth$currentDepth 1);
  258.         }
  259.     }
  260.     // Handle objects
  261.     else if (is_object($data)) {
  262.         $reflection = new \ReflectionObject($data);
  263.         $properties $reflection->getProperties();
  264.         
  265.         foreach ($properties as $property) {
  266.             $property->setAccessible(true);
  267.             if ($property->isInitialized($data)) {
  268.                 $value $property->getValue($data);
  269.                 $property->setValue($data$this->breakCircularReferences($value$processed$maxDepth$currentDepth 1));
  270.             }
  271.         }
  272.     }
  273.     
  274.     return $data;
  275. }
  276.     public function refreshEstateFlow($modelEntityId$sequenceId$timelineId){
  277.         
  278.         $estateFlow $this->estateFlowService->resolveEstateFlow($modelEntityId$sequenceId$timelineId);
  279.         
  280.         $estateFlow json_encode($estateFlowJSON_UNESCAPED_UNICODE);
  281.         $values = [
  282.             'modelEntity'=>$modelEntityId,
  283.             'sequence'=>$sequenceId,
  284.             'timeline'=>$timelineId,
  285.             'result'=>$estateFlow,
  286.             'status'=>EstateFlow::STATUS_OK
  287.         ];
  288.         
  289.         return  $this->safeCreate($values);
  290.     }
  291.     public function clearEstateFlow($modelEntityId$sequenceId$timelineId){
  292.         $estateFlow $this->getEntityRepo()->findEstateFlow($modelEntityId,$sequenceId$timelineId);
  293.                 
  294.         if ($estateFlow != null){
  295.             $this->delete($estateFlow);
  296.         }
  297.     }
  298.     public function refreshOutdatedEstateFlows($householdHashId){
  299.         // Refresh outdated estate flows
  300.         $outdatedFlow $this->getEntityRepo()->legacyFindByOne(['status'=>EstateFlow::STATUS_OUTDATED]);
  301.         while($outdatedFlow !== null){
  302.             try{
  303.                 // block the flow from being processed by another instance
  304.                 $values = [
  305.                     'status'=>EstateFlow::STATUS_PENDING
  306.                 ];
  307.                 $this->safeUpdate($outdatedFlow$values);
  308.                 $this->resolveEstateFlow($outdatedFlow->getModelEntity()->getId(), $outdatedFlow->getSequence()->getId(), $outdatedFlow->getTimeline()?->getId(), truetrue);
  309.             } catch(\Exception $e){
  310.                 $values = [
  311.                     'status'=>EstateFlow::STATUS_FAIL,
  312.                     'result'=>$e->getMessage()
  313.                 ];
  314.                 $this->safeUpdate($outdatedFlow$values);
  315.                 $this->sendFlowFailEmail($e->getMessage(), $householdHashId$outdatedFlow->getModelEntity()->getId(), $outdatedFlow->getSequence()->getId(), $outdatedFlow->getTimeline()?->getId());
  316.             } finally {
  317.                 $this->doctrineEntityManager->clear();
  318.                 gc_collect_cycles();
  319.                 $outdatedFlow $this->getEntityRepo()->legacyFindByOne(['status'=>EstateFlow::STATUS_OUTDATED]);
  320.             }
  321.         }
  322.         // Refresh failed estate flows
  323.         $failedFlow $this->getEntityRepo()->legacyFindByOne(['status'=>EstateFlow::STATUS_FAIL]);
  324.         while($failedFlow !== null){
  325.             try{
  326.                 $this->resolveEstateFlow($failedFlow->getModelEntity()->getId(), $failedFlow->getSequence()->getId(), $failedFlow->getTimeline()?->getId(), truetrue);
  327.             } catch(\Exception $e){
  328.                throw $e;
  329.                 // Do not notify for failed flows
  330.             } finally {
  331.                 $this->doctrineEntityManager->clear();
  332.                 gc_collect_cycles();
  333.                 $failedFlow $this->getEntityRepo()->legacyFindByOne(['status'=>EstateFlow::STATUS_FAIL]);
  334.             }
  335.         }   
  336.     }
  337.     // Defines related data for estetflow, used detect when the estate flow is outdated 
  338.     public function getRelatedData(EstateFlowCache $estateFlowCache){
  339.         //Get all transitions ids from the estate flow
  340.         $relatedTransitions array_map(function($transition) {
  341.             return $transition->id;
  342.         }, $estateFlowCache->activatedTransitions);
  343.         //Get all entities id on actions from applied transitions
  344.         $relatedEntities = [];
  345.         $relatedActions = [];
  346.         foreach ($estateFlowCache->activatedTransitions as $transition) {
  347.             foreach ($transition->actions as $action) {
  348.                 // Get action id
  349.                 $relatedActions[] = $action->id;
  350.                 // Get src entity id
  351.                 $actionEntities[] = $action->src->modelEntity->id;
  352.                 // Get targets entities ids
  353.                 foreach ($action->targets as $target) {
  354.                     $actionEntities[] = $target->modelEntity->id;
  355.                 }
  356.                 // Get post auto actions entities ids
  357.                 foreach ($action->postAutoActions as $postAutoAction) {
  358.                     $actionEntities[] = $postAutoAction->src->modelEntity->id;
  359.                 } 
  360.                 // Get pre auto actions entities ids
  361.                 foreach ($action->preAutoActions as $preAutoAction) {
  362.                     $actionEntities[] = $preAutoAction->src->modelEntity->id;
  363.                 }   
  364.             }
  365.         }
  366.         // Get all entities id from initial and result of appliedTransitions
  367.         foreach ($estateFlowCache->appliedTransitions as $transition) {
  368.             // Get initial entities id  
  369.             foreach ($transition->initial->entities as $entity) {
  370.                 $relatedEntities[] = $entity->modelEntity->id;
  371.             }
  372.             // Get result entities id
  373.             foreach ($transition->result->entities as $entity) {
  374.                 $relatedEntities[] = $entity->modelEntity->id;
  375.             }
  376.         }
  377.         
  378.         $relatedData = [];
  379.         // Add entities ids to relatedData with key prefix entity_
  380.         foreach ($relatedEntities as $entity) {
  381.             $relatedData[] = "e_{$entity}_"
  382.         }
  383.         // Add transitions ids to relatedData with key prefix transition_
  384.         foreach ($relatedTransitions as $transition) {
  385.             $relatedData[] = "s_{$transition}_";
  386.         }
  387.         // Add actions ids to relatedData with key prefix action_
  388.         foreach ($relatedActions as $action) {
  389.             $relatedData[] = "a_{$action}_";
  390.         }
  391.         return  implode(','$relatedData);
  392.     }
  393.     public function newAvailableEstateFlowScenario(EstateFlowTransition $estateFlowScenario){
  394.          
  395.         $estateFlowSequenceList $this->getSequencesForScenario($estateFlowScenario);
  396.          foreach ($estateFlowSequenceList as $estateFlowSequence){
  397.             $this->outdatedEstateFlowSequence($estateFlowSequence->getId());
  398.          }
  399.     }
  400.     public function outdatedEstateFlowScenario($scenarioId){
  401.         $estateFlowScenario $this->estateFlowTransitionManager->find($scenarioId);
  402.         if ($estateFlowScenario != null){
  403.             $this->newAvailableEstateFlowScenario($estateFlowScenario);
  404.         }
  405.         $estateFlowIdList $this->getEntityRepo()->findEstateFlowIdListWithScenario($scenarioId);
  406.         if ($estateFlowIdList != null){
  407.             $this->getEntityRepo()->updateEstateFlowListStatus($estateFlowIdListEstateFlow::STATUS_OUTDATED);
  408.         }
  409.     }
  410.     public function outdatedEstateFlowAction($actionId){
  411.         
  412.         $estateFlowIdList $this->getEntityRepo()->findEstateFlowIdListWithAction($actionId);
  413.         if ($estateFlowIdList != null){
  414.             $this->getEntityRepo()->updateEstateFlowListStatus($estateFlowIdListEstateFlow::STATUS_OUTDATED);
  415.         }
  416.     }
  417.     public function outdatedEstateFlowEntity($entityId){
  418.         
  419.         $estateFlowIdList $this->getEntityRepo()->findEstateFlowIdListWithEntity($entityId);
  420.         if ($estateFlowIdList != null){
  421.             $this->getEntityRepo()->updateEstateFlowListStatus($estateFlowIdListEstateFlow::STATUS_OUTDATED);
  422.         }
  423.     }
  424.     public function outdatedEstateFlowSequence($sequenceId){
  425.         $estateFlowIdList $this->getEntityRepo()->findEstateFlowIdListWithSequence($sequenceId);
  426.         if ($estateFlowIdList != null){
  427.             $this->getEntityRepo()->updateEstateFlowListStatus($estateFlowIdListEstateFlow::STATUS_OUTDATED);
  428.         }
  429.     }
  430.     public function outdatedEstateFlowModelEntity($modelEntityId){
  431.         $estateFlowIdList $this->getEntityRepo()->findEstateFlowIdListWithModelEntity($modelEntityId);
  432.         if ($estateFlowIdList != null){
  433.             $this->getEntityRepo()->updateEstateFlowListStatus($estateFlowIdListEstateFlow::STATUS_OUTDATED);
  434.         }
  435.     }
  436.     public function outdatedEstateFlowScenarioTimeline($timelineId){
  437.         $estateFlowIdList $this->getEntityRepo()->findEstateFlowIdListWithTimeline($timelineId);
  438.         if ($estateFlowIdList != null){
  439.             $this->getEntityRepo()->updateEstateFlowListStatus($estateFlowIdListEstateFlow::STATUS_OUTDATED); 
  440.         }
  441.     }
  442.     public function outdatedValuableResource(ValuableResource $valuableResource){
  443.         
  444.         $outdateFlowIds = [];
  445.         foreach ($valuableResource->getActiveOwnerships() as $ownership){
  446.             $outdateFlowIds array_merge($outdateFlowIds$this->getEntityRepo()->findEstateFlowIdListWithModelEntity($ownership->getModelEntityOwner()->getId()));
  447.         }
  448.         $this->getEntityRepo()->updateEstateFlowListStatus($outdateFlowIdsEstateFlow::STATUS_OUTDATED);
  449.         
  450.     }
  451.     public function getSequencesForScenario(EstateFlowTransition $estateFlowScenario){
  452.         $sequences = [];
  453.         
  454.         $allSequences $this->estateFlowScenarioManager->findAll();
  455.         foreach ($allSequences as $sequence){
  456.             if ($this->checkIfSequenceActivatesScenario($sequence$estateFlowScenario)){
  457.                 $sequences[] = $sequence;
  458.             }
  459.         }
  460.         return $sequences;
  461.     }
  462.     public function checkIfSequenceActivatesScenario(EstateFlowScenario $estateFlowSequenceEstateFlowTransition $estateFlowScenario){
  463.         $triggerList $estateFlowScenario->getTriggers();
  464.         foreach ($triggerList as $trigger){
  465.             if ($this->checkTrigger($trigger$estateFlowSequence->getConfiguration())){
  466.                 return true;
  467.             }
  468.         }
  469.         return false;
  470.     }
  471.     private function checkTrigger(
  472.         EstateFlowTrigger $trigger,
  473.         $eventsLoaded
  474.     ): bool
  475.     {
  476.         switch ($trigger->getLogicalOperator()) {
  477.             case EstateFlowTrigger::LOGICAL_OPERATOR_AND:
  478.                 return $this->checkAndTrigger($trigger$eventsLoaded);
  479.             case EstateFlowTrigger::LOGICAL_OPERATOR_OR:
  480.                 return $this->checkOrTrigger($trigger$eventsLoaded);
  481.             case EstateFlowTrigger::LOGICAL_OPERATOR_BEFORE:
  482.                 return $this->checkBeforeTrigger($trigger$eventsLoaded);
  483.             case EstateFlowTrigger::LOGICAL_OPERATOR_AFTER:
  484.                 return $this->checkAfterTrigger($trigger$eventsLoaded);
  485.             default:
  486.                 throw new Exception("Dev: invalid logical operator on trigger"1);
  487.         }
  488.     }
  489.     private function checkIfTriggerEventHaveHappened(
  490.         EstateFlowTriggerEvent $triggerEvent,
  491.         $eventsLoaded
  492.     ): bool
  493.     {
  494.         return $this->findTriggerEventPlaceOnLoadedEvents($triggerEvent$eventsLoaded) !== -1;
  495.     }
  496.     private function findTriggerEventPlaceOnLoadedEvents(
  497.         EstateFlowTriggerEvent $triggerEvent,
  498.         $eventsLoaded
  499.     ): int
  500.     {
  501.         foreach ($eventsLoaded as $index => $event) {
  502.             $event = (object)$event;
  503.             if ($event->description === $triggerEvent->getDescription()) {
  504.                 if ($event->description === EstateFlowTriggerEvent::DESCRIPTION_DEATH) {
  505.                     if ($event->targetModelEntity == $triggerEvent->getTargetModelEntity()->getId()) {
  506.                         return $index;
  507.                     }
  508.                 } elseif ($event->description === EstateFlowTriggerEvent::DESCRIPTION_DATE) {
  509.                     if ($event->date && $triggerEvent->getTargetDate() &&
  510.                         $event->date->format('Y') === $triggerEvent->getTargetDate()->format('Y')) {
  511.                         return $index;
  512.                     }
  513.                 }
  514.             }
  515.         }
  516.         return -1;
  517.     }
  518.     private function checkAndTrigger(
  519.         EstateFlowTrigger $trigger,
  520.         $eventsLoaded
  521.     ): bool
  522.     {
  523.         $triggerDiscarded false;
  524.         $explicitTriggerEvents array_filter(
  525.             $trigger->getEvents()->toArray(),
  526.             fn($event) => in_array(
  527.                 $event->getDescription(),
  528.                 EstateFlowTriggerEvent::EVENT_OPTIONS
  529.             )
  530.         );
  531.         foreach ($explicitTriggerEvents as $triggerEvent) {
  532.             if (!$this->checkIfTriggerEventHaveHappened($triggerEvent$eventsLoaded)) {
  533.                 $triggerDiscarded true;
  534.                 break;
  535.             }
  536.         }
  537.         return !$triggerDiscarded;
  538.     }
  539.     private function checkOrTrigger(
  540.         EstateFlowTrigger $trigger,
  541.         $eventsLoaded
  542.     ): bool
  543.     {
  544.         $triggerActivated false;
  545.         $explicitTriggerEvents array_filter(
  546.             $trigger->getEvents()->toArray(),
  547.             fn($event) => in_array(
  548.                 $event->getDescription(),
  549.                 EstateFlowTriggerEvent::EVENT_OPTIONS
  550.             )
  551.         );
  552.         foreach ($explicitTriggerEvents as $triggerEvent) {
  553.             if ($this->checkIfTriggerEventHaveHappened($triggerEvent$eventsLoaded)) {
  554.                 $triggerActivated true;
  555.                 break;
  556.             }
  557.         }
  558.         return $triggerActivated;
  559.     }
  560.     private function checkBeforeTrigger(
  561.         EstateFlowTrigger $trigger,
  562.         $eventsLoaded
  563.     ): bool
  564.     {
  565.         $triggerDiscarded false;
  566.         $eventLoadedIndex $this->findTriggerEventPlaceOnLoadedEvents($trigger->getEvents()[0], $eventsLoaded);
  567.         if ($eventLoadedIndex !== -1) {
  568.             // Check if at least one of subsequent trigger events are found in previous loaded events
  569.             $previousEventsLoaded array_slice($eventsLoaded0$eventLoadedIndex);
  570.             for ($i 1$i count($trigger->getEvents()) && !empty($previousEventsLoaded) && !$triggerDiscarded$i++) {
  571.                 $triggerEvent $trigger->getEvents()[$i];
  572.                 $triggerDiscarded $this->checkIfTriggerEventHaveHappened($triggerEvent$previousEventsLoaded);
  573.             }
  574.         } else {
  575.             $triggerDiscarded true;
  576.         }
  577.         return !$triggerDiscarded;
  578.     }
  579.     private function checkAfterTrigger(
  580.         EstateFlowTrigger $trigger,
  581.         $eventsLoaded
  582.     ): bool
  583.     {
  584.         $triggerDiscarded false;
  585.         $eventsLoadedPending array_reverse($eventsLoaded);
  586.         $triggerEventsChecked 0;
  587.         for ($i 0$i count($trigger->getEvents()) && !empty($eventsLoadedPending) && !$triggerDiscarded$i++) {
  588.             $triggerEvent $trigger->getEvents()[$i];
  589.             $eventLoadedIndex $this->findTriggerEventPlaceOnLoadedEvents($triggerEvent$eventsLoadedPending);
  590.             if ($eventLoadedIndex === -1) {
  591.                 $triggerDiscarded true;
  592.                 $eventsLoadedPending = [];
  593.             } else {
  594.                 $eventsLoadedPending array_slice($eventsLoadedPending$eventLoadedIndex 1);
  595.             }
  596.             $triggerEventsChecked++;
  597.         }
  598.         return !$triggerDiscarded && $triggerEventsChecked === count($trigger->getEvents());
  599.     }
  600. }