src/AppRemix/Logging/Tools.php line 34

Open in your IDE?
  1. <?php
  2. //----------------------------------------------------------------------
  3. // src/Logging/Tools.php
  4. //----------------------------------------------------------------------
  5. namespace App\AppRemix\Logging;
  6. use Doctrine\Persistence\ManagerRegistry;
  7. use Symfony\Component\PropertyAccess\PropertyAccess;
  8. use Symfony\Contracts\Translation\TranslatorInterface;
  9. use Symfony\Component\Security\Core\Security;
  10. use App\Entity\Access;
  11. use App\Entity\APIRest\AccessAPI;
  12. use App\AppRemix\Entity\Client;
  13. use App\AppRemix\Entity\Devis;
  14. use App\AppRemix\Entity\Invoice;
  15. use App\AppRemix\Entity\Log;
  16. use App\AppRemix\Entity\Mission;
  17. use App\AppRemix\Entity\Payment;
  18. use App\AppRemix\Entity\Society;
  19. use App\Services\Common\TextTools;
  20. use App\Services\LogTools;
  21. class Tools
  22. {
  23.     public function __construct(ManagerRegistry $doctrine, TranslatorInterface $translator, Security $security,
  24.         LogTools $logTools, TextTools $textTools)
  25.     {
  26.         $this->em = $doctrine->getManager();
  27.         $this->translator = $translator;
  28.         $this->security = $security;
  29.         $this->logTools = $logTools;                
  30.         $this->textTools = $textTools;        
  31.     }
  32.     // No Persist, No Flush
  33.     // Returns the $log object
  34.     public function smartLog($args)
  35.     {
  36.         // Get the arguments
  37.         if (array_key_exists("action", $args))                        $action = $args["action"];
  38.         else                                                        $action = null;
  39.         // Action is mandatory, all the rest isn't
  40.         if ($action === null)
  41.             return null;
  42.         if (array_key_exists("object_id", $args))                    $objectId = $args["object_id"];
  43.         else                                                        $objectId = null;
  44.         if (array_key_exists("object_entity", $args))                $objectEntity = $args["object_entity"];
  45.         else                                                        $objectEntity = null;
  46.         if (array_key_exists("object_display", $args))                $objectDisplay = $this->textTools->cleanTextWithoutHtmlentities($args["object_display"]);
  47.         else                                                        $objectDisplay = null;
  48.         if (array_key_exists("mission_id", $args))                    $missionId = $args["mission_id"];
  49.         else                                                        $missionId = null;
  50.         if (array_key_exists("mission_display", $args))                $missionDisplay = $this->textTools->cleanTextWithoutHtmlentities($args["mission_display"]);
  51.         else                                                        $missionDisplay = null;
  52.         if (array_key_exists("client_id", $args))                    $clientId = $args["client_id"];
  53.         else                                                        $clientId = null;
  54.         if (array_key_exists("client_display", $args))                $clientDisplay = $this->textTools->cleanTextWithoutHtmlentities($args["client_display"]);
  55.         else                                                        $clientDisplay = null;
  56.         if (array_key_exists("old_value", $args))                    $oldValue = $this->textTools->cleanTextWithoutHtmlentities($args["old_value"]);
  57.         else                                                        $oldValue = null;
  58.         if (array_key_exists("new_value", $args))                    $newValue = $this->textTools->cleanTextWithoutHtmlentities($args["new_value"]);
  59.         else                                                        $newValue = null;
  60.         if (array_key_exists("info", $args))                        $info = $args["info"];
  61.         else                                                        $info = null;
  62.         if (array_key_exists("society", $args))                        $society = $args["society"];
  63.         else                                                        $society = null;
  64.         // Handle AccessApi
  65.         if (array_key_exists("access_api", $args))
  66.         {
  67.             $accessApi = $args['access_api'];
  68.         }
  69.         else
  70.         {
  71.             $accessApi = $this->getCurrentUser();            
  72.         }
  73.         // Handle logging for AccessClient users
  74.         // At this point $access holds a valid not null object
  75.         // However, this object can be either an Access or an AccessClient
  76.         if ($accessApi instanceof AccessAPI)
  77.         {
  78.             $this->logTools->ploopLog("[AppRemix][Logging][smartLog] accessApi is AccessAPI ".$accessApi->displayForLog());
  79.         }
  80.         else
  81.         {           
  82.             $this->logTools->ploopLog("[AppRemix][Logging][smartLog] accessApi is an alien ".$accessApi->displayForLog());
  83.         }
  84.         $log = new Log();    
  85.         
  86.         $log->setAction($action);
  87.         $log->setObjectId($objectId);
  88.         
  89.         $log->setObjectEntity($objectEntity);
  90.         $log->setObjectDisplay($objectDisplay);
  91.         
  92.         $log->setMissionId($missionId);
  93.         $log->setMissionDisplay($missionDisplay);
  94.         $log->setClientId($clientId);
  95.         $log->setClientDisplay($clientDisplay);
  96.         $log->setOldValue($oldValue);
  97.         $log->setNewValue($newValue);
  98.         $log->setInfo($info);
  99.         if ($society !== null)
  100.         {
  101.             $log->setSocietyId($society->getId());            
  102.             $log->setSocietyName($society->getName());
  103.         }
  104.         if ($accessApi !== null)
  105.         {
  106.             $log->setAccessApiId($accessApi->getId());
  107.             $log->setAccessApiUsername($accessApi->getUsername());
  108.         }
  109.         
  110.         // Translations
  111.         if (!empty($log->getObjectEntity()))
  112.         {
  113.             $translation = $this->translator->trans($log->getObjectEntity(), array(), 'rekapp_remix');
  114.             $log->setObjectEntityTranslation($translation);
  115.         }
  116.         $translation = $this->translator->trans($log->getAction(), array(), 'rekapp_remix');
  117.         $log->setActionTranslation($translation);                
  118.         return $log;
  119.     }
  120.     // Always return an object of type AccessAPI    
  121.     public function getCurrentUser()
  122.     {
  123.         $emptyAccess = $this->em->getRepository(Access::class)->findOneByEmail(Access::EMPTY_ACCESS);
  124.         // Get the Access|AccessAPI|AccessClient object returned by the security
  125.         $access = $this->security->getUser();
  126.         if (empty($access))
  127.         {
  128.             // This occurs when the code is execuded by a Command (CRON Task for example)
  129.             // In this case there is no firewall, and thus no Access|AccessAPI
  130.             $access = $emptyAccess;
  131.         }
  132.         else
  133.         {
  134.             // The firewall has been bypassed => We have an user provider,
  135.             // either Access or AccessAPI
  136.             // or an AccessClient :)
  137.             if (!($access instanceof Access) && !($access instanceof AccessAPI) && !($access instanceof AccessClient))
  138.             {
  139.                 // This should not happen, but hey, life is weird, who knows
  140.                 // Even VSCode proved to be worse than Atom ;)
  141.                 $access = $emptyAccess;
  142.             }
  143.         }
  144.         if ($access instanceof AccessAPI)
  145.         {
  146.             // Bingo
  147.             return $access;    
  148.         }
  149.         // This sould not happen
  150.         return $emptyAccess;
  151.     }
  152.     public function handleAddressChanges($args, $before, $after)
  153.     {
  154.         $args["old_value"] = null;
  155.         $args["new_value"] = null;
  156.         if ($before === null && $after === null)
  157.         {
  158.             return $args;
  159.         }
  160.         if ($before !== null && $after !== null)
  161.         {
  162.             if ($before->differsInValue($after))
  163.             {
  164.                 $args["old_value"] = $before->display();
  165.                 $args["new_value"] = $after->display();
  166.                 return $args;
  167.             }
  168.         }
  169.         else
  170.         {
  171.             // before or after is null, but not both
  172.             if ($before !== null) $args["old_value"] = $before->display();
  173.             if ($after !== null) $args["new_value"] = $after->display();
  174.             return $args;
  175.         }
  176.         return $args;
  177.     }
  178.     public function handleBasicChanges($args, $before, $after)
  179.     {
  180.         $args["old_value"] = $before;
  181.         $args["new_value"] = $after;
  182.         return $args;
  183.     }
  184.     /**
  185.      * Compares Doctrine JSON column values as plain strings and records the diff when they differ.
  186.      *
  187.      * @param array<string, mixed> $args
  188.      *
  189.      * @return array<string, mixed>
  190.      */
  191.     public function handleJsonChanges($args, $before, $after)
  192.     {
  193.         $args["old_value"] = null;
  194.         $args["new_value"] = null;
  195.         if ($before === null && $after === null)
  196.         {
  197.             return $args;
  198.         }
  199.         $beforeString = $this->jsonValueToString($before);
  200.         $afterString = $this->jsonValueToString($after);
  201.         if ($before !== null && $after !== null)
  202.         {
  203.             if ($beforeString !== $afterString)
  204.             {
  205.                 $args["old_value"] = $beforeString;
  206.                 $args["new_value"] = $afterString;
  207.                 return $args;
  208.             }
  209.         }
  210.         else
  211.         {
  212.             // before or after is null, but not both
  213.             if ($before !== null)
  214.             {
  215.                 $args["old_value"] = $beforeString;
  216.             }
  217.             if ($after !== null)
  218.             {
  219.                 $args["new_value"] = $afterString;
  220.             }
  221.             return $args;
  222.         }
  223.         return $args;
  224.     }
  225.     /**
  226.      * Encodes a JSON column value as a plain string for comparison and logging.
  227.      */
  228.     private function jsonValueToString(mixed $value): ?string
  229.     {
  230.         if ($value === null)
  231.         {
  232.             return null;
  233.         }
  234.         $encoded = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  235.         if ($encoded === false)
  236.         {
  237.             return (string) $value;
  238.         }
  239.         return $encoded;
  240.     }
  241.     public function handleBasicBoolChanges($args, $before, $after)
  242.     {
  243.         if ($before && !$after)
  244.         {
  245.             // $args["old_value"] = null;
  246.             $args["old_value"] = $this->translator->trans("yes");
  247.             $args["new_value"] = $this->translator->trans("no");
  248.         }
  249.         if (!$before && $after)
  250.         {
  251.             // $args["old_value"] = null;
  252.             $args["old_value"] = $this->translator->trans("no");
  253.             $args["new_value"] = $this->translator->trans("yes");
  254.         }
  255.         return $args;
  256.     }
  257.     public function handleLabelChanges($args, $before, $after)
  258.     {
  259.         $args["old_value"] = null;
  260.         $args["new_value"] = null;
  261.         if ($before !== null) $args["old_value"] = $before->getValue();
  262.         if ($after !== null) $args["new_value"]    = $after->getValue();
  263.         return $args;
  264.     }
  265.     /**
  266.      * Maps Payment status integer codes to translated labels (payment_status_* keys).
  267.      *
  268.      * @param array<string, mixed> $args
  269.      *
  270.      * @return array<string, mixed>
  271.      */
  272.     public function handlePaymentStatusChanges($args, $before, $after)
  273.     {
  274.         $args["old_value"] = $this->translatePaymentStatus($before);
  275.         $args["new_value"] = $this->translatePaymentStatus($after);
  276.         return $args;
  277.     }
  278.     /**
  279.      * Resolves a Payment status code via Payment::STATUS_* constants and rekapp_remix translations.
  280.      */
  281.     private function translatePaymentStatus(mixed $status): ?string
  282.     {
  283.         if ($status === null)
  284.         {
  285.             return null;
  286.         }
  287.         $status = (int) $status;
  288.         foreach ((new \ReflectionClass(Payment::class))->getConstants() as $name => $value)
  289.         {
  290.             if (strpos($name, 'STATUS_') !== 0 || $name === 'STATUS_LIST')
  291.             {
  292.                 continue;
  293.             }
  294.             if ($value === $status)
  295.             {
  296.                 $key = 'payment_'.$this->logTools->camelToSnakeCase($name);
  297.                 return $this->translator->trans($key, [], 'rekapp_remix');
  298.             }
  299.         }
  300.         return (string) $status;
  301.     }
  302.     /**
  303.      * Maps Payment type integer codes to translated labels (payment_type_* keys).
  304.      *
  305.      * @param array<string, mixed> $args
  306.      *
  307.      * @return array<string, mixed>
  308.      */
  309.     public function handlePaymentTypeChanges($args, $before, $after)
  310.     {
  311.         $args["old_value"] = $this->translatePaymentType($before);
  312.         $args["new_value"] = $this->translatePaymentType($after);
  313.         return $args;
  314.     }
  315.     /**
  316.      * Resolves a Payment type code via Payment::TYPE_* constants and rekapp_remix translations.
  317.      */
  318.     private function translatePaymentType(mixed $type): ?string
  319.     {
  320.         if ($type === null)
  321.         {
  322.             return null;
  323.         }
  324.         $type = (int) $type;
  325.         foreach ((new \ReflectionClass(Payment::class))->getConstants() as $name => $value)
  326.         {
  327.             if (strpos($name, 'TYPE_') !== 0 || $name === 'TYPE_LIST')
  328.             {
  329.                 continue;
  330.             }
  331.             if ($value === $type)
  332.             {
  333.                 $key = 'payment_'.$this->logTools->camelToSnakeCase($name);
  334.                 return $this->translator->trans($key, [], 'rekapp_remix');
  335.             }
  336.         }
  337.         return (string) $type;
  338.     }
  339.     /**
  340.      * Maps Devis financial state keys to translated labels (devis_financial_state_* keys).
  341.      *
  342.      * The Devis::FINANCIAL_STATE_* values are already snake case, so the translation key is the
  343.      * value prefixed — a seventh state needs a translation entry and no code change.
  344.      *
  345.      * @param array<string, mixed> $args
  346.      *
  347.      * @return array<string, mixed>
  348.      */
  349.     public function handleFinancialStateChanges($args, $before, $after)
  350.     {
  351.         $args["old_value"] = $this->translateFinancialState($before);
  352.         $args["new_value"] = $this->translateFinancialState($after);
  353.         return $args;
  354.     }
  355.     /**
  356.      * Resolves a Devis financial state key through the rekapp_remix translations.
  357.      */
  358.     private function translateFinancialState(?string $state): ?string
  359.     {
  360.         if ($state === null)
  361.         {
  362.             return null;
  363.         }
  364.         return $this->translator->trans('devis_financial_state_'.$state, [], 'rekapp_remix');
  365.     }
  366.     public function handleObjectChanges($args, $before, $after)
  367.     {
  368.         $args["old_value"] = null;
  369.         $args["new_value"] = null;
  370.         if ($before !== null)
  371.         {
  372.             if (method_exists($before, "getLogLabel"))
  373.             {
  374.                 $args["old_value"] = $before->getLogLabel();
  375.             }
  376.             else
  377.             {
  378.                 $args["old_value"] = $before->display();
  379.             }
  380.         }
  381.         if ($after !== null)
  382.         {
  383.             if (method_exists($after, "getLogLabel"))
  384.             {
  385.                 $args["new_value"]    = $after->getLogLabel();
  386.             }
  387.             else
  388.             {
  389.                 $args["new_value"]    = $after->display();
  390.             }
  391.         }
  392.         return $args;
  393.     }
  394.     public function handleDateChanges($args, $before, $after, $displayHours = false)
  395.     {
  396.         $testFormat = "Ymd";
  397.         if ($displayHours) $testFormat = "YmdHi";
  398.         $displayFormat = "d/m/Y";
  399.         if ($displayHours) $displayFormat = "d/m/Y H:i";
  400.         $args["old_value"] = null;
  401.         $args["new_value"] = null;
  402.         if ($before === null && $after === null)
  403.         {
  404.             return $args;
  405.         }
  406.         if ($before !== null && $after !== null)
  407.         {
  408.             if ($before->format($testFormat) != $after->format($testFormat))
  409.             {
  410.                 $args["old_value"] = $before->format($displayFormat);
  411.                 $args["new_value"] = $after->format($displayFormat);
  412.                 return $args;
  413.             }
  414.         }
  415.         else
  416.         {
  417.             // before or after is null, but not both
  418.             if ($before !== null)
  419.             {
  420.                 $args["old_value"] = $before->format($displayFormat);
  421.             }
  422.             if ($after !== null)
  423.             {
  424.                 $args["new_value"] = $after->format($displayFormat);
  425.             }
  426.             return $args;
  427.         }
  428.         
  429.         return null;
  430.     }
  431.     public function handleNewSociety(Log $log)
  432.     {
  433.         if ($log->getObjectEntity() == "Society" && $log->getAction() == "society_add")
  434.         {
  435.             $society = $this->em->getRepository(Society::class)->findOneById($log->getObjectId());
  436.             if ($society !== null)    
  437.             {
  438.                 $accessApi = $society->getAccessApi();
  439.                 if ($accessApi !== null)
  440.                 {
  441.                     $log->setAccessApiId($accessApi->getId());    
  442.                     $log->setAccessApiUsername($accessApi->getUsername());    
  443.                 }
  444.             }
  445.         }
  446.     }
  447.     public function handleMission(Log $log)
  448.     {
  449.         if ($log->getObjectEntity() == "Mission")
  450.         {
  451.             $mission = $this->em->getRepository(Mission::class)->findOneById($log->getObjectId());
  452.             if ($mission !== null)    
  453.             {
  454.                 $log->setMissionId($mission->getId());
  455.                 $log->setMissionDisplay($mission->display());
  456.             }
  457.         }
  458.     }
  459.     /**
  460.      * Fills a log line's client columns once the client has an id. A client created inline
  461.      * by devis/add or invoice/add is persisted in the same flush as its document, so neither
  462.      * log line can know the client id when its args are built at prePersist.
  463.      *
  464.      * Only fills empty columns: a value set by initArgs() is never overwritten.
  465.      */
  466.     public function handleClient(Log $log, object $object): void
  467.     {
  468.         if ($log->getClientId() !== null)
  469.         {
  470.             return;
  471.         }
  472.         $client = match (true)
  473.         {
  474.             $object instanceof Client => $object,
  475.             $object instanceof Devis, $object instanceof Invoice, $object instanceof Payment => $object->getClient(),
  476.             default => null,
  477.         };
  478.         if ($client === null || $client->getId() === null)
  479.         {
  480.             return;
  481.         }
  482.         $log->setClientId($client->getId());
  483.         $log->setClientDisplay($this->textTools->cleanTextWithoutHtmlentities($client->displayForListing()));
  484.     }
  485. }