src/Services/LogTools.php line 36

Open in your IDE?
  1. <?php
  2. //----------------------------------------------------------------------
  3. // src/Services/LogTools.php
  4. //----------------------------------------------------------------------
  5. namespace App\Services;
  6. use DateTime;
  7. use Doctrine\Persistence\ManagerRegistry;
  8. use Psr\Log\LoggerInterface;
  9. use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
  10. use Symfony\Contracts\Translation\TranslatorInterface;
  11. use Symfony\Component\Security\Core\Security;
  12. use App\DTO\Log\LogCsvRecord;
  13. use App\Entity\Access;
  14. use App\Entity\AccessClient\AccessClient;
  15. use App\Entity\APIRest\AccessAPI;
  16. use App\Entity\Client\Client;
  17. use App\Entity\HR\HumanResource;
  18. use App\Entity\Ikea\ServiceOrder;
  19. use App\Entity\Mission\Mission;
  20. use App\Entity\OnlinePayment\PaymentType as OnlinePaymentType;
  21. use App\Entity\Planning\Task;
  22. use App\Entity\Platform\Devis\Devis;
  23. use App\Entity\Platform\Invoice\Invoice;
  24. use App\Entity\Platform\Society;
  25. use App\Entity\ProjectManager\ProjectNotebook;
  26. use App\Entity\SocietyGroup;
  27. class LogTools
  28. {
  29.     public function __construct(ManagerRegistry $doctrine, TranslatorInterface $translator, LoggerInterface $ouchLogger, Security $security)
  30.     {
  31.         $this->em = $doctrine->getManager();
  32.         $this->translator = $translator;
  33.         $this->security = $security;
  34.         $this->ouchLogger = $ouchLogger;
  35.     }
  36.     public function getRootDir()
  37.     {
  38.         $home = dirname(__FILE__, 3);
  39.         return $home;
  40.     }
  41.     public function getPublicDir()
  42.     {
  43.         $home = dirname(__FILE__, 3);
  44.         $home .= "/public/";
  45.         return $home;
  46.     }
  47.     public function getCustomLogsDir()
  48.     {
  49.         $home = dirname(__FILE__, 3);
  50.         $home .= "/custom_logs/";
  51.         return $home;
  52.     }
  53.     public function getCustomDataDir()
  54.     {
  55.         $home = dirname(__FILE__, 3);
  56.         $home .= "/custom_data/";
  57.         return $home;
  58.     }
  59.     public function getDMSRootDir()
  60.     {
  61.         $home = dirname(__FILE__, 3);
  62.         $home .= "/DMS/";
  63.         return $home;
  64.     }
  65.     // Plan.io Task #4661
  66.     // ROOT/DMS/Ikea/logs/{society_group_id}/
  67.     public function getIkeaDMSLogsFolder(SocietyGroup $societyGroup)
  68.     {
  69.         $home = $this->getDMSRootDir();
  70.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  71.         $home .= "Ikea/logs/";
  72.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  73.         $home .= $societyGroup->getId();
  74.         $home .= "/";
  75.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  76.         return $home;
  77.     }
  78.     // Plan.io Task #4661
  79.     // ROOT/DMS/Ikea/logs/{society_group_id}/
  80.     public function getIkeaDMSLogFile(ServiceOrder $serviceOrder)
  81.     {
  82.         if ($serviceOrder->getSocietyGroup() !== null)
  83.         {
  84.             $path = $this->getIkeaDMSLogsFolder($serviceOrder->getSocietyGroup());
  85.             $logFile = $path . $serviceOrder->getId() . ".log";
  86.             return $logFile;
  87.         }
  88.         return null;
  89.     }
  90.     // Plan.io Task #4661
  91.     public function logNewXml(ServiceOrder $serviceOrder, string $data)
  92.     {
  93.         $logFile = $this->getIkeaDMSLogFile($serviceOrder);
  94.         if ($logFile !== null)
  95.         {
  96.             $today = new \DateTime();
  97.             $timestamp = $today->format("Y-m-d H:i:s");
  98.             error_log("\n[$timestamp] $data", 3, $logFile);
  99.         }
  100.     }
  101.     // Plan.io Task #4448    
  102.     // ROOT/DMS/IkeaBP/logs/{society_group_id}/
  103.     public function getIkeaBpFolder(SocietyGroup $societyGroup)
  104.     {
  105.         $home = $this->getDMSRootDir();
  106.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  107.         $home .= "IkeaBP/logs/";
  108.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  109.         $home .= $societyGroup->getId();
  110.         $home .= "/";
  111.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  112.         return $home;
  113.     }
  114.     public function getIkeaBpLogFileName(SocietyGroup $societyGroup)
  115.     {        
  116.         $today = new \DateTime();
  117.         $timestamp = $today->format("Ymd_His");
  118.         $logFileName = "IkeaBP_".$societyGroup->getId()."_".$timestamp.".log";
  119.         return $logFileName;
  120.     }
  121.     public function getIkeaBpErrorFileName(SocietyGroup $societyGroup)
  122.     {        
  123.         $today = new \DateTime();
  124.         $timestamp = $today->format("Ymd_His");
  125.         $logFileName = "IkeaBP_".$societyGroup->getId()."_".$timestamp.".err";
  126.         return $logFileName;
  127.     }
  128.     public function getIkeaBpLogFile(SocietyGroup $societyGroup)
  129.     {
  130.         $path = $this->getIkeaBpFolder($societyGroup);        
  131.         $logFileName = $this->getIkeaBpLogFileName($societyGroup);
  132.         $logFile = $path.$logFileName;
  133.         return $logFile;
  134.     }
  135.     public function getIkeaBpErrorFile(SocietyGroup $societyGroup)
  136.     {
  137.         $path = $this->getIkeaBpFolder($societyGroup);        
  138.         $logFileName = $this->getIkeaBpErrorFileName($societyGroup);
  139.         $logFile = $path.$logFileName;
  140.         return $logFile;
  141.     }
  142.     public function logToBpFile_andTask($logFile, Task $task, $msg)
  143.     {
  144.         $today = new \DateTime();
  145.         $timestamp = $today->format("Y-m-d H:i:s");
  146.         error_log("\n[$timestamp] $msg", 3, $logFile);
  147.         $ikeaBpProcessInfo = $task->getIkeaBpProcessInfo();
  148.         $ikeaBpProcessInfo .= $msg;
  149.         $task->setIkeaBpProcessInfo($ikeaBpProcessInfo);
  150.         
  151.         // error_log("\n[$timestamp] $msg", 3, "ploop.log");
  152.     }
  153.     public function logToBpFile($logFile, $msg)
  154.     {
  155.         $today = new \DateTime();
  156.         $timestamp = $today->format("Y-m-d H:i:s");
  157.         error_log("\n[$timestamp] $msg", 3, $logFile);
  158.         
  159.         // error_log("\n[$timestamp] $msg", 3, "ploop.log");
  160.     }
  161.     // ROOT/DMS/Export/CostAccount/society_group_id/
  162.     public function getCostAutoAccountExportFolder($societyGroup = null)
  163.     {
  164.         $home = $this->getDMSRootDir();
  165.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  166.         $home .= "Export/";
  167.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  168.         $home .= "CostAccount/";
  169.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  170.         if ($societyGroup !== null)
  171.         {
  172.             $home .= $societyGroup->getId();
  173.             $home .= "/";
  174.             if (!is_dir($home))    $do = mkdir($home, 0755, true);
  175.         }
  176.         return $home;
  177.     }
  178.     
  179.     // ROOT/DMS/Export/CostAccount/society_group_id/logs/
  180.     public function getCostAutoAccountExportLogsFolder(SocietyGroup $societyGroup)
  181.     {
  182.         $home = $this->getDMSRootDir();
  183.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  184.         $home .= "Export/";
  185.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  186.         $home .= "CostAccount/";
  187.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  188.         $home .= $societyGroup->getId();
  189.         $home .= "/";
  190.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  191.         $home .= "logs/";
  192.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  193.         return $home;
  194.     }
  195.     
  196.     // ROOT/DMS/Export/InvoiceAccount/society_group_id/
  197.     public function getInvoiceAutoAccountExportFolder($societyGroup = null)
  198.     {
  199.         $home = $this->getDMSRootDir();
  200.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  201.         $home .= "Export/";
  202.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  203.         $home .= "InvoiceAccount/";
  204.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  205.         if ($societyGroup !== null)
  206.         {
  207.             $home .= $societyGroup->getId();
  208.             $home .= "/";
  209.             if (!is_dir($home))    $do = mkdir($home, 0755, true);
  210.         }
  211.         return $home;
  212.     }
  213.     
  214.     // ROOT/DMS/Export/InstallmentAccount/society_group_id/
  215.     public function getInstallmentAutoAccountExportFolder($societyGroup = null)
  216.     {
  217.         $home = $this->getDMSRootDir();
  218.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  219.         $home .= "Export/";
  220.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  221.         $home .= "InstallmentAccount/";
  222.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  223.         if ($societyGroup !== null)
  224.         {
  225.             $home .= $societyGroup->getId();
  226.             $home .= "/";
  227.             if (!is_dir($home))    $do = mkdir($home, 0755, true);
  228.         }
  229.         return $home;
  230.     }
  231.     
  232.     // ROOT/DMS/Export/InstallmentAccount/society_group_id/logs/
  233.     public function getInstallmentAutoAccountExportLogsFolder(SocietyGroup $societyGroup)
  234.     {
  235.         $home = $this->getDMSRootDir();
  236.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  237.         $home .= "Export/";
  238.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  239.         $home .= "InstallmentAccount/";
  240.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  241.         $home .= $societyGroup->getId();
  242.         $home .= "/";
  243.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  244.         $home .= "logs/";
  245.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  246.         return $home;
  247.     }
  248.     
  249.     // ROOT/DMS/Export/InvoiceAccount/society_group_id/logs/
  250.     public function getInvoiceAutoAccountExportLogsFolder(SocietyGroup $societyGroup)
  251.     {
  252.         $home = $this->getDMSRootDir();
  253.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  254.         $home .= "Export/";
  255.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  256.         $home .= "InvoiceAccount/";
  257.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  258.         $home .= $societyGroup->getId();
  259.         $home .= "/";
  260.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  261.         $home .= "logs/";
  262.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  263.         return $home;
  264.     }
  265.     /**
  266.      * Exports Logs to ROOT/DMS/Export/InvoiceAccount/society_group_id/logs/
  267.      * File Name : { yyyymmdd }_Export_QCompta_error.log
  268.      */
  269.     public function logError_invoiceAutoAccount(SocietyGroup $societyGroup, $error)
  270.     {
  271.         $exportPath = $this->getInvoiceAutoAccountExportLogsFolder($societyGroup);
  272.         $today = new \DateTime();
  273.         $ymd = $today->format("Ymd");
  274.         $timestamp = $today->format("Y-m-d H:i:s");
  275.         $fileName = $ymd."_Export_QCompta_Factures_error".".log";
  276.         $errorFile = $exportPath.$fileName;
  277.         error_log("\n[$timestamp] $error", 3, $errorFile);
  278.     }
  279.     /**
  280.      * Exports Logs to ROOT/DMS/Export/CostAccount/society_group_id/logs/
  281.      * File Name : { yyyymmdd }_Export_Achats_QCompta_error.log
  282.      */
  283.     public function logError_costAutoAccount(SocietyGroup $societyGroup, $error)
  284.     {
  285.         $exportPath = $this->getCostAutoAccountExportLogsFolder($societyGroup);
  286.         $today = new \DateTime();
  287.         $ymd = $today->format("Ymd");
  288.         $timestamp = $today->format("Y-m-d H:i:s");
  289.         $fileName = $ymd."_Export_QCompta_Achats_error".".log";
  290.         $errorFile = $exportPath.$fileName;
  291.         error_log("\n[$timestamp] $error", 3, $errorFile);
  292.     }
  293.     /**
  294.      * Exports Logs to ROOT/DMS/Export/InstallmentAccount/society_group_id/logs/
  295.      * File Name : { yyyymmdd }_Export_Reglements_QCompta_error.log
  296.      */
  297.     public function logError_installmentAutoAccount(SocietyGroup $societyGroup, $error)
  298.     {
  299.         $exportPath = $this->getInstallmentAutoAccountExportLogsFolder($societyGroup);
  300.         $today = new \DateTime();
  301.         $ymd = $today->format("Ymd");
  302.         $timestamp = $today->format("Y-m-d H:i:s");
  303.         $fileName = $ymd."_Export_QCompta_Reglements_error".".log";
  304.         $errorFile = $exportPath.$fileName;
  305.         error_log("\n[$timestamp] $error", 3, $errorFile);
  306.     }
  307.     //--------------------------------------------------------------------------
  308.     // TO BE USED IN COMMANDS (OneShot) ONLY
  309.     //--------------------------------------------------------------------------
  310.     public function getCommandLogsDir()
  311.     {
  312.         $home = $this->getCustomLogsDir()."command_logs/";
  313.         return $home;
  314.     }
  315.     public function command_log($filename, $msg, $newLine = true)
  316.     {
  317.         $home = $this->getCommandLogsDir();
  318.         $logFile = $home . $filename;
  319.         if ($newLine)
  320.             $msg = "\n".$msg;
  321.         error_log($msg,3,$logFile);
  322.     }
  323.     /**
  324.      * Append a row to a csv log file of the command logs directory.
  325.      *
  326.      * The column names are written once, when the file is created, so several runs on the same
  327.      * day keep appending to the same file without repeating the header.
  328.      *
  329.      * @param string[] $columns
  330.      * @param scalar[] $row
  331.      *
  332.      * Plan.io Task #4685
  333.      */
  334.     public function command_csv_log(string $filename, array $columns, array $row): void
  335.     {
  336.         $logFile = $this->getCommandLogsDir() . $filename;
  337.         $fileExists = file_exists($logFile);
  338.         $stream = fopen($logFile, 'a');
  339.         if ($stream === false)
  340.             return;
  341.         if (!$fileExists)
  342.             fputcsv($stream, $columns, ';');
  343.         fputcsv($stream, $row, ';');
  344.         fclose($stream);
  345.     }
  346.     //--------------------------------------------------------------------------
  347.     public function plooplog($msg)
  348.     {
  349.         error_log("\n".$msg."\n",3,"ploop.log");
  350.     }
  351.     // public function ploopWrite($msg)
  352.     // {
  353.     //     $filename = "ploop.log";
  354.     //     $msg = "\n".$msg."\n";
  355.     //     if (is_writable($filename)) 
  356.     //     {
  357.     //         if ($fp = fopen($filename, 'a')) 
  358.     //         {
  359.     //             fwrite($fp, $msg);
  360.     //         }
  361.     //     }        
  362.     // }
  363.     public function ploop_debug($active, $msg)
  364.     {
  365.         if (!$active)
  366.             return false;
  367.         error_log("\n".$msg."\n",3,"ploop.log");
  368.     }
  369.     public function getGuardConfigDir()
  370.     {
  371.         $home = $this->getRootDir();
  372.         $home .= "/src/Guardian/config/";
  373.         return $home;
  374.     }
  375.     public function getPlanningOptimisationLogsDir()
  376.     {
  377.         $home = $this->getCustomLogsDir()."planning_optimisation_logs/";
  378.         return $home;
  379.     }
  380.     public function getIkeaKitchenPlannerLogsDir()
  381.     {
  382.         $home = $this->getCustomLogsDir()."ikea_kitchen_planner_logs/";
  383.         return $home;
  384.     }
  385.     public function getApiIcodLogsDir()
  386.     {
  387.         $home = $this->getCustomLogsDir()."api_icod/";
  388.         return $home;
  389.     }
  390.     public function getCynclyLogsDir()
  391.     {
  392.         $home = $this->getCustomLogsDir()."cyncly_logs/";
  393.         return $home;
  394.     }
  395.     // Plan.io Task #4634
  396.     // custom_logs/EInvoicing/{societyGroupId|other}/
  397.     private function getEInvoicingGroupDir(?SocietyGroup $societyGroup): ?string
  398.     {
  399.         $home = $this->getCustomLogsDir()."EInvoicing/";
  400.         if (!is_dir($home) && mkdir($home, 0755, true) === false)
  401.         {
  402.             return null;
  403.         }
  404.         $home .= ($societyGroup?->getId() ?? 'other')."/";
  405.         if (!is_dir($home) && mkdir($home, 0755, true) === false)
  406.         {
  407.             $this->errorLog("Folder $home cannot be created.");
  408.             return null;
  409.         }
  410.         return $home;
  411.     }
  412.     // NOTE: This folder is used for WIP subscription system not for the onlinePayment one
  413.     public function getStripeLogsDir()
  414.     {
  415.         $home = $this->getCustomLogsDir()."stripe/";
  416.         return $home;
  417.     }
  418.     public function getGuestSuiteLogsDir()
  419.     {
  420.         $home = $this->getCustomLogsDir()."guest_suite_log/";
  421.         return $home;
  422.     }
  423.     public function getGuardLogsDir()
  424.     {
  425.         $home = $this->getCustomLogsDir()."guard_logs/";
  426.         return $home;
  427.     }
  428.     public function getGuardGlobalLogsDir()
  429.     {
  430.         $home = $this->getCustomLogsDir()."guard_logs/global/";
  431.         return $home;
  432.     }
  433.     public function getIkeaOSLogsDir()
  434.     {
  435.         $home = $this->getCustomLogsDir()."ikea_os_logs/";
  436.         return $home;
  437.     }
  438.     public function getIkeaOS_statusLogsDir()
  439.     {
  440.         $home = $this->getCustomLogsDir()."ikea_os_logs/status_logs/";
  441.         return $home;
  442.     }
  443.     public function getIkeaHotlineCallLogsDir(SocietyGroup $societyGroup): string
  444.     {
  445.         $home = $this->getCustomLogsDir()."ikea_hotline_call/";
  446.         if (!is_dir($home))
  447.         {
  448.             mkdir($home, 0755, true);
  449.         }
  450.         $home .= $societyGroup->getId()."/";
  451.         if (!is_dir($home))
  452.         {
  453.             mkdir($home, 0755, true);
  454.         }
  455.         return $home;
  456.     }
  457.     public function getIkeaSendLogsDir($societyGroup)
  458.     {
  459.         $home = $this->getCustomLogsDir();
  460.         $home .= "ikea_send_logs/";
  461.         if (!is_dir($home))
  462.         {
  463.             $do = mkdir($home, 0755, true);
  464.             if ($do === false)
  465.             {
  466.                 return null;
  467.             }
  468.         }
  469.         if ($societyGroup !== null)
  470.         {
  471.             $id = $societyGroup->getId();
  472.             $home .= $id."/";
  473.             if (!is_dir($home))
  474.             {
  475.                 $do = mkdir($home, 0755, true);
  476.                 if ($do === false)
  477.                 {
  478.                     $this->errorLog("Folder $home cannot be created.");
  479.                     return null;
  480.                 }
  481.             }
  482.         }
  483.         return $home;
  484.     }
  485.     
  486.     public function getPlanningLogsDir()
  487.     {
  488.         $home = $this->getCustomLogsDir()."planning/";
  489.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  490.         return $home;
  491.     }
  492.     
  493.     public function getCostLogsDir()
  494.     {
  495.         $home = $this->getCustomLogsDir()."cost_logs/";
  496.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  497.         return $home;
  498.     }
  499.     
  500.     public function getInstallmentLogsDir()
  501.     {
  502.         $home = $this->getCustomLogsDir()."installment_logs/";
  503.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  504.         return $home;
  505.     }
  506.     
  507.     public function getInvoiceLogsDir()
  508.     {
  509.         $home = $this->getCustomLogsDir()."invoice_logs/";
  510.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  511.         return $home;
  512.     }
  513.     
  514.     // custom_logs/invoice_logs/missing_devis_product/
  515.     public function getInvoiceMissingDevisProductsLogsDir()
  516.     {
  517.         $home = $this->getInvoiceLogsDir();
  518.         $home .= "missing_devis_product/";
  519.         if (!is_dir($home))
  520.         {
  521.             $do = mkdir($home, 0755, true);
  522.             if ($do === false)
  523.             {
  524.                 return null;
  525.             }
  526.         }
  527.         return $home;
  528.     }
  529.     
  530.     // custom_logs/cost_logs/export/{society_group_id}/
  531.     public function getCostExportLogsDir($societyGroup)
  532.     {
  533.         $home = $this->getCostLogsDir();
  534.         $home .= "export/";
  535.         if (!is_dir($home))
  536.         {
  537.             $do = mkdir($home, 0755, true);
  538.             if ($do === false)
  539.             {
  540.                 return null;
  541.             }
  542.         }
  543.         if ($societyGroup !== null)
  544.         {
  545.             $id = $societyGroup->getId();
  546.             $home .= $id."/";
  547.             if (!is_dir($home))
  548.             {
  549.                 $do = mkdir($home, 0755, true);
  550.                 if ($do === false)
  551.                 {
  552.                     $this->errorLog("Folder $home cannot be created.");
  553.                     return null;
  554.                 }
  555.             }
  556.         }
  557.         return $home;
  558.     }
  559.     
  560.     // custom_logs/installment_logs/export/{society_group_id}/
  561.     public function getInstallmentExportLogsDir($societyGroup)
  562.     {
  563.         $home = $this->getInstallmentLogsDir();
  564.         $home .= "export/";
  565.         if (!is_dir($home))
  566.         {
  567.             $do = mkdir($home, 0755, true);
  568.             if ($do === false)
  569.             {
  570.                 return null;
  571.             }
  572.         }
  573.         if ($societyGroup !== null)
  574.         {
  575.             $id = $societyGroup->getId();
  576.             $home .= $id."/";
  577.             if (!is_dir($home))
  578.             {
  579.                 $do = mkdir($home, 0755, true);
  580.                 if ($do === false)
  581.                 {
  582.                     $this->errorLog("Folder $home cannot be created.");
  583.                     return null;
  584.                 }
  585.             }
  586.         }
  587.         return $home;
  588.     }
  589.     
  590.     // custom_logs/invoice_logs/export/{society_group_id}/
  591.     public function getInvoiceExportLogsDir(?SocietyGroup $societyGroup)
  592.     {
  593.         $home = $this->getInvoiceLogsDir();
  594.         $home .= "export/";
  595.         if (!is_dir($home))
  596.         {
  597.             $do = mkdir($home, 0755, true);
  598.             if ($do === false)
  599.             {
  600.                 return null;
  601.             }
  602.         }
  603.         if ($societyGroup !== null)
  604.         {
  605.             $id = $societyGroup->getId();
  606.             $home .= $id."/";
  607.             if (!is_dir($home))
  608.             {
  609.                 $do = mkdir($home, 0755, true);
  610.                 if ($do === false)
  611.                 {
  612.                     $this->errorLog("Folder $home cannot be created.");
  613.                     return null;
  614.                 }
  615.             }
  616.         }
  617.         return $home;
  618.     }
  619.     
  620.     // Plan.io Task #4683
  621.     // custom_logs/EInvoicing/{society_group_id|other}/Export/
  622.     public function getInboundInvoiceExportLogsDir(?SocietyGroup $societyGroup)
  623.     {
  624.         $home = $this->getEInvoicingGroupDir($societyGroup);
  625.         if ($home === null)
  626.         {
  627.             return null;
  628.         }
  629.         $home .= "Export/";
  630.         if (!is_dir($home) && mkdir($home, 0755, true) === false)
  631.         {
  632.             $this->errorLog("Folder $home cannot be created.");
  633.             return null;
  634.         }
  635.         return $home;
  636.     }
  637.     public function getITaskBaseLogsDir()
  638.     {
  639.         $home = $this->getCustomLogsDir()."itask_logs/";
  640.         return $home;
  641.     }
  642.     public function getITaskLogsDir($societyGroup = null)
  643.     {
  644.         $home = $this->getITaskBaseLogsDir() . "auto/";
  645.         if ($societyGroup !== null)
  646.         {
  647.             $id = $societyGroup->getId();
  648.             $home .= $id."/";
  649.             if (!is_dir($home))
  650.             {
  651.                 $do = mkdir($home, 0755, true);
  652.                 if ($do === false)
  653.                 {
  654.                     $this->errorLog("Folder $home cannot be created.");
  655.                     return null;
  656.                 }
  657.             }
  658.         }
  659.         return $home;
  660.     }
  661.     public function getITaskDebugDir()
  662.     {
  663.         $home = $this->getITaskBaseLogsDir()."debug/";
  664.         return $home;
  665.     }
  666.     public function getLoginLogsDir()
  667.     {
  668.         $home = $this->getCustomLogsDir()."login_logs/";
  669.         return $home;
  670.     }
  671.     public function getProductLogsDir()
  672.     {
  673.         $home = $this->getCustomLogsDir()."product_logs/";
  674.         return $home;
  675.     }
  676.     public function getChargeLogsDir()
  677.     {
  678.         $home = $this->getCustomLogsDir()."charge_logs/";
  679.         return $home;
  680.     }
  681.     public function getSmsLogsDir()
  682.     {
  683.         $home = $this->getCustomLogsDir()."sms_logs/";
  684.         return $home;
  685.     }
  686.     public function getEmailLogsDir()
  687.     {
  688.         $home = $this->getCustomLogsDir()."email_logs/";
  689.         return $home;
  690.     }
  691.     // Plan.io Task #4624
  692.     public function getProjectManagerLogsDir()
  693.     {
  694.         $home = $this->getCustomLogsDir()."project_manager/";
  695.         return $home;
  696.     }
  697.     // Plan.io Task #4518
  698.     public function getNotifierLogsDir()
  699.     {
  700.         $home = $this->getCustomLogsDir()."notifier/";
  701.         return $home;
  702.     }
  703.     public function getTemplateLogsDir()
  704.     {
  705.         $home = $this->getCustomLogsDir()."template/";
  706.         return $home;
  707.     }
  708.     //--------------------------------------------------------------------------
  709.     // Plan.io Task #3922 
  710.     // DMS Global, Email and Object Logs
  711.     //--------------------------------------------------------------------------
  712.     // Old path : ROOT/custom_logs/global_logs
  713.     // New path : ROOT/DMS/Logs/Global/
  714.     public function getDMS_GlobalLogsDir()
  715.     {
  716.         $home = dirname(__FILE__, 3);
  717.         $home .= "/DMS/Logs/Global/";
  718.         return $home;
  719.     }
  720.     
  721.     // DMS/Logs/Email/
  722.     public function getDMS_EmailLogsDir()
  723.     {        
  724.         $home = dirname(__FILE__, 3);
  725.         $home .= "/DMS/Logs/Email/";
  726.         return $home;
  727.     }
  728.     // DMS/Logs/Mission/
  729.     public function getDMS_MissionLogsDir()
  730.     {        
  731.         $home = dirname(__FILE__, 3);
  732.         $home .= "/DMS/Logs/Mission/";
  733.         return $home;
  734.     }
  735.     // DMS/Logs/Client/
  736.     public function getDMS_ClientLogsDir()
  737.     {        
  738.         $home = dirname(__FILE__, 3);
  739.         $home .= "/DMS/Logs/Client/";
  740.         return $home;
  741.     }
  742.     // DMS/Logs/HumanResource/
  743.     public function getDMS_HumanResourceLogsDir()
  744.     {        
  745.         $home = dirname(__FILE__, 3);
  746.         $home .= "/DMS/Logs/HumanResource/";
  747.         return $home;
  748.     }
  749.     // DMS/Logs/ProjectManager/Notebook/
  750.     public function getDMS_ProjectManagerNotebookLogsDir()
  751.     {        
  752.         $home = dirname(__FILE__, 3);
  753.         $home .= "/DMS/Logs/ProjectManager/Notebook/";
  754.         return $home;
  755.     }
  756.     //--------------------------------------------------------------------------
  757.     public function getSecurityLogsDir()
  758.     {
  759.         $home = $this->getCustomLogsDir()."security/";
  760.         return $home;
  761.     }
  762.     // Plan.io Task #4410
  763.     public function getWebappLogsDir()
  764.     {
  765.         $home = $this->getCustomLogsDir()."webapp/";
  766.         return $home;
  767.     }
  768.     // Plan.io Task #4518
  769.     // Used in 
  770.     //    src/Services/Platform/NoteTools.php
  771.     //    src/Services/Communication/Email/NotifierEmails.php
  772.     //    src/Services/Ding/Notifier.php
  773.     public function notifier_debug($msg, $active = true)
  774.     {
  775.         if ($active)
  776.         {
  777.             $home = $this->getNotifierLogsDir();
  778.             if (!is_dir($home))    $do = mkdir($home, 0755, true);
  779.     
  780.             $today = new \DateTime();
  781.             $log = "notifier_debug_".$today->format("Y").$today->format("m").".log";
  782.             $logFile = $home . $log;
  783.     
  784.             $timestamp = $today->format("[Y-m-d H:i:s] ");
  785.     
  786.             error_log("\n".$timestamp.$msg."\n",3,$logFile);
  787.             
  788.             error_log("\n".$timestamp.$msg."\n",3,"ploop.log");
  789.         }               
  790.     }
  791.     // Plan.io Task #4624
  792.     public function project_manager_debug($msg, $active = true)
  793.     {
  794.         if ($active)
  795.         {
  796.             $home = $this->getProjectManagerLogsDir();
  797.             if (!is_dir($home))    $do = mkdir($home, 0755, true);
  798.             $today = new \DateTime();
  799.             $log = "project_manager_debug_".$today->format("Y").$today->format("m").".log";
  800.             $logFile = $home . $log;
  801.             $timestamp = $today->format("[Y-m-d H:i:s] ");
  802.             error_log("\n".$timestamp.$msg."\n",3,$logFile);            
  803.             
  804.             // error_log("\n".$timestamp.$msg."\n",3,"ploop.log");            
  805.         }        
  806.     }
  807.     // Plan.io Task #4483
  808.     public function analyzePlanning($isActive, $msg)
  809.     {
  810.         if (!$isActive) return true;
  811.         $home = $this->getPlanningLogsDir();
  812.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  813.         $today = new \DateTime();
  814.         $log = "global_planning_execution_time_".$today->format("Y").$today->format("m").".log";
  815.         $logFile = $home . $log;
  816.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  817.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  818.     }
  819.     // Plan.io Task #4410
  820.     public function webapp_debug($msg)
  821.     {
  822.         $home = $this->getWebappLogsDir();
  823.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  824.         $today = new \DateTime();
  825.         $log = "webapp_debug_".$today->format("Y").$today->format("m").".log";
  826.         $logFile = $home . $log;
  827.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  828.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  829.     }
  830.     // Plan.io Task #4410
  831.     public function webapp_image_debug($msg)
  832.     {
  833.         $home = $this->getWebappLogsDir();
  834.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  835.         $today = new \DateTime();
  836.         $log = "webapp_image_debug_".$today->format("Y").$today->format("m").".log";
  837.         $logFile = $home . $log;
  838.         $timestamp = $today->format("[Y-m-d H:i:s]");
  839.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  840.     }
  841.     public function getTakeOutExportDir()
  842.     {
  843.         $home = $this->getCustomDataDir()."take_out_export/";
  844.         return $home;
  845.     }
  846.     public function getCssDir()
  847.     {
  848.         $home = $this->getRootDir()."/public/theme/css/";
  849.         return $home;
  850.     }
  851.     public function getJohnDoeDir()
  852.     {
  853.         $home = $this->getRootDir()."/public/assets/john_doe/";
  854.         return $home;
  855.     }
  856.     // custom_logs/cost/auto_accounting/
  857.     public function getCostAutoAccountingDebugDir()
  858.     {
  859.         $home = $this->getCustomLogsDir()."cost/";
  860.         if (!is_dir($home))
  861.         {
  862.             $do = mkdir($home, 0755, true);
  863.             if ($do === false)
  864.             {
  865.                 $this->errorLog("Folder $home cannot be created.");
  866.                 return null;
  867.             }
  868.         }
  869.         $home .= 'auto_accounting/';
  870.         if (!is_dir($home))
  871.         {
  872.             $do = mkdir($home, 0755, true);
  873.             if ($do === false)
  874.             {
  875.                 $this->errorLog("Folder $home cannot be created.");
  876.                 return null;
  877.             }
  878.         }
  879.         return $home;
  880.     }
  881.     // custom_logs/invoice/auto_accounting/
  882.     public function getInvoiceAutoAccountingDebugDir()
  883.     {
  884.         $home = $this->getCustomLogsDir()."invoice/";
  885.         if (!is_dir($home))
  886.         {
  887.             $do = mkdir($home, 0755, true);
  888.             if ($do === false)
  889.             {
  890.                 $this->errorLog("Folder $home cannot be created.");
  891.                 return null;
  892.             }
  893.         }
  894.         $home .= 'auto_accounting/';
  895.         if (!is_dir($home))
  896.         {
  897.             $do = mkdir($home, 0755, true);
  898.             if ($do === false)
  899.             {
  900.                 $this->errorLog("Folder $home cannot be created.");
  901.                 return null;
  902.             }
  903.         }
  904.         return $home;
  905.     }
  906.     // custom_logs/zone/{society_group_id}
  907.     public function getZoneDir($societyGroup = null)
  908.     {
  909.         $home = $this->getCustomLogsDir()."zone/";
  910.         if ($societyGroup !== null)
  911.         {
  912.             $id = $societyGroup->getId();
  913.             $home .= $id."/";
  914.             if (!is_dir($home))
  915.             {
  916.                 $do = mkdir($home, 0755, true);
  917.                 if ($do === false)
  918.                 {
  919.                     $this->errorLog("Folder $home cannot be created.");
  920.                     return null;
  921.                 }
  922.             }
  923.         }
  924.         return $home;
  925.     }
  926.     // custom_logs/zone/debug/
  927.     public function getZoneDebugDir($societyGroup = null)
  928.     {
  929.         $home = $this->getZoneDir()."debug/";
  930.         return $home;
  931.     }
  932.     // custom_logs/availability/
  933.     public function getAvailablityDir()
  934.     {
  935.         $home = $this->getCustomLogsDir()."availability/";
  936.         return $home;
  937.     }
  938.     // custom_logs/financial/
  939.     // Plan.io Task #4386
  940.     public function getFinancialDir()
  941.     {
  942.         $home = $this->getCustomLogsDir()."financial/";
  943.         return $home;
  944.     }
  945.     // custom_logs/financial/scan_financial/
  946.     // Plan.io Task #4386
  947.     public function getScanFinancialDir()
  948.     {
  949.         $home = $this->getFinancialDir()."scan_financial/";
  950.         return $home;
  951.     }
  952.     // custom_logs/financial/duplicate_code/
  953.     // Plan.io Task #4386
  954.     public function getDuplicateCodeFinancialDir()
  955.     {
  956.         $home = $this->getFinancialDir()."duplicate_code/";
  957.         return $home;
  958.     }
  959.     // Plan.Io Task #3747
  960.     // custom_logs/individual/
  961.     public function getIndividualDir()
  962.     {
  963.         $home = $this->getCustomLogsDir()."individual/";
  964.         if (!is_dir($home))
  965.         {
  966.             $do = mkdir($home, 0755, true);
  967.             if ($do === false)
  968.             {
  969.                 $this->errorLog("Folder $home cannot be created.");
  970.                 return null;
  971.             }
  972.         }
  973.         return $home;
  974.     }
  975.     // Plan.Io Task #3747
  976.     // custom_logs/individual/dump/{society_group_id}
  977.     public function getIndividualDumpDir($societyGroup = null)
  978.     {
  979.         $home = $this->getIndividualDir() . "dump/";
  980.         if ($societyGroup !== null)
  981.         {
  982.             $id = $societyGroup->getId();
  983.             $home .= $id."/";
  984.             if (!is_dir($home))
  985.             {
  986.                 $do = mkdir($home, 0755, true);
  987.                 if ($do === false)
  988.                 {
  989.                     $this->errorLog("Folder $home cannot be created.");
  990.                     return null;
  991.                 }
  992.             }
  993.         }
  994.         return $home;
  995.     }
  996.     // Plan.io Task #3747
  997.     // custom_logs/individual/merge/{society_group_id}
  998.     public function getIndividualMergeDir($societyGroup = null)
  999.     {
  1000.         $home = $this->getIndividualDir() . "merge/";
  1001.         if ($societyGroup !== null)
  1002.         {
  1003.             $id = $societyGroup->getId();
  1004.             $home .= $id."/";
  1005.             if (!is_dir($home))
  1006.             {
  1007.                 $do = mkdir($home, 0755, true);
  1008.                 if ($do === false)
  1009.                 {
  1010.                     $this->errorLog("Folder $home cannot be created.");
  1011.                     return null;
  1012.                 }
  1013.             }
  1014.         }
  1015.         return $home;
  1016.     }
  1017.     // Plan.io Task #4247
  1018.     // custom_logs/mission/
  1019.     public function getMissionDir()
  1020.     {
  1021.         $home = $this->getCustomLogsDir()."mission/";
  1022.         if (!is_dir($home))
  1023.         {
  1024.             $do = mkdir($home, 0755, true);
  1025.             if ($do === false)
  1026.             {
  1027.                 $this->errorLog("Folder $home cannot be created.");
  1028.                 return null;
  1029.             }
  1030.         }
  1031.         return $home;
  1032.     }
  1033.     // Plan.io Task #4247
  1034.     // custom_logs/mission/dump/{society_group_id}
  1035.     public function getMissionDumpDir($societyGroup = null)
  1036.     {
  1037.         $home = $this->getMissionDir() . "dump/";
  1038.         if ($societyGroup !== null)
  1039.         {
  1040.             $id = $societyGroup->getId();
  1041.             $home .= $id."/";
  1042.             if (!is_dir($home))
  1043.             {
  1044.                 $do = mkdir($home, 0755, true);
  1045.                 if ($do === false)
  1046.                 {
  1047.                     $this->errorLog("Folder $home cannot be created.");
  1048.                     return null;
  1049.                 }
  1050.             }
  1051.         }
  1052.         return $home;
  1053.     }
  1054.     // Plan.io Task #4247
  1055.     // custom_logs/mission/merge/{society_group_id}
  1056.     public function getMissionMergeDir($societyGroup = null)
  1057.     {
  1058.         $home = $this->getMissionDir() . "merge/";
  1059.         if ($societyGroup !== null)
  1060.         {
  1061.             $id = $societyGroup->getId();
  1062.             $home .= $id."/";
  1063.             if (!is_dir($home))
  1064.             {
  1065.                 $do = mkdir($home, 0755, true);
  1066.                 if ($do === false)
  1067.                 {
  1068.                     $this->errorLog("Folder $home cannot be created.");
  1069.                     return null;
  1070.                 }
  1071.             }
  1072.         }
  1073.         return $home;
  1074.     }
  1075.     // Plan.io Task #4331
  1076.     // custom_logs/payment_logs/YYYY/MM/
  1077.     public function getPaymentLogDir()
  1078.     {
  1079.         $home = $this->getCustomLogsDir()."payment_logs/";
  1080.         return $home;
  1081.     }
  1082.     public function getPaymentErrorLogDir()
  1083.     {
  1084.         $home = $this->getPaymentLogDir() . "error/";
  1085.         $now = new \DateTime();
  1086.         $home .= $now->format('Y') . "/" . $now->format('m') . "/";
  1087.         return $home;
  1088.     }
  1089.     // Plan.io Task #4383
  1090.     public function getBookingLogDir()
  1091.     {
  1092.         $home = $this->getCustomLogsDir()."booking/";
  1093.         return $home;
  1094.     }
  1095.     // Plan.io Task #4383
  1096.     public function getBookingSavSlotLogDir()
  1097.     {
  1098.         $home = $this->getBookingLogDir()."booking_sav_slot/";
  1099.         return $home;
  1100.     }
  1101.     // Plan.io Task #4383
  1102.     public function getCleanBookingSavSlotLogDir()
  1103.     {
  1104.         $home = $this->getBookingLogDir()."clean_booking_sav_slot/";
  1105.         return $home;
  1106.     }
  1107.     public function getGuardLogPath(SocietyGroup $societyGroup)
  1108.     {
  1109.         // Get the path to the log folder
  1110.         // If the folder does not exits, this creates it
  1111.         $logPath = $this->getGuardLogsDir();
  1112.         // Try to get the name of the log file from the entity
  1113.         $logName = $societyGroup->getGuardLog();
  1114.         // If empty, craft it
  1115.         if (empty($logName))
  1116.         {
  1117.             $today = new \DateTime();
  1118.             $logName = "guard_log_".$today->format('Y_m_d')."_SG_".$societyGroup->getInternalRef().".log";
  1119.             // Update the object
  1120.             $societyGroup->setGuardLog($logName);
  1121.         }
  1122.         // Craft the path to the file
  1123.         $logFile = $logPath . $logName;
  1124.         // If the file does not exist, create it
  1125.         if (!file_exists($logFile))
  1126.         {
  1127.             error_log("",3,$logFile);
  1128.         }
  1129.         return $logFile;
  1130.     }
  1131.     public function addTimestampToGuardLog($logFile)
  1132.     {
  1133.         $today = new \DateTime();
  1134.         error_log("\n",3,$logFile);
  1135.         error_log("\n",3,$logFile);
  1136.         error_log($today->format("Y/m/d H:i:s"),3,$logFile);
  1137.         error_log("\n",3,$logFile);
  1138.     }
  1139.     public function mobileAppLogCall($message = null)
  1140.     {
  1141.         // Global switch to enable/disable mobile app logging
  1142.         $mobileAppLoggingActive = true;
  1143.         if(!$mobileAppLoggingActive)
  1144.             return false;
  1145.         // Returns User object or null if not authenticated
  1146.         $currentUser = $this->getCurrentUser();
  1147.         if($currentUser === null) // Do not permit log if user is not authenticated, should not happen
  1148.             return false;
  1149.         // Check that current user is not an AccessClient
  1150.         if(    ($currentUser instanceof AccessClient) )
  1151.             return false;
  1152.         $logPath = $this->getApiIcodLogsDir();
  1153.         $now = new DateTime();
  1154.         $logPath = $logPath . 'api_rest_log/mobileAppCall_' . $now->format('Y_m') . '.log';
  1155.         $today = new \DateTime();
  1156.         $currentDate = $today->format('Y-m-d H:i:s.v');
  1157.         $trace = debug_backtrace();
  1158.         $func = $trace[1]["function"];
  1159.         $line = "" . $currentDate . "," . $currentUser->display() . "," . $currentUser->getId() . "," . $currentUser->getSocietyGroup()?->getRef() . "," . $currentUser->getSocietyGroup()?->getId() . "," . $func;
  1160.         if($message !== null)
  1161.             $line .= "," . $message;
  1162.         $line .= "\n";
  1163.         error_log($line, 3, $logPath);
  1164.     }
  1165.     public function getApiRestRateLimitLogPath(): string
  1166.     {
  1167.         $baseDir = $this->getApiIcodLogsDir() . "api_rest_log/";
  1168.         if (!is_dir($baseDir))
  1169.         {
  1170.             if (!mkdir($baseDir, 0755, true))
  1171.             {
  1172.                 $now = new \DateTime();
  1173.                 return $baseDir . "rate_limit_" . $now->format("Y") . "_" . $now->format("m") . ".csv";
  1174.             }
  1175.         }
  1176.         $now = new \DateTime();
  1177.         $fileName = "rate_limit_" . $now->format("Y") . "_" . $now->format("m") . ".csv";
  1178.         return $baseDir . $fileName;
  1179.     }
  1180.     public function apiRestRateLimitLog(array $args): void
  1181.     {
  1182.         $scope = array_key_exists('scope', $args) ? (string) $args['scope'] : '';
  1183.         $ip = array_key_exists('ip', $args) ? (string) $args['ip'] : '';
  1184.         $accessApi = array_key_exists('accessApi', $args) && $args['accessApi'] instanceof AccessAPI ? $args['accessApi'] : null;
  1185.         $route = array_key_exists('route', $args) ? (string) $args['route'] : '';
  1186.         $maxRequests = array_key_exists('max_requests', $args) ? $args['max_requests'] : '';
  1187.         $accessApiId = '';
  1188.         $accessApiUsername = '';
  1189.         $societyGroupId = '';
  1190.         $societyGroupRef = '';
  1191.         if ($accessApi instanceof AccessAPI)
  1192.         {
  1193.             $accessApiId = (string) $accessApi->getId();
  1194.             $accessApiUsername = (string) $accessApi->getUsername();
  1195.             $societyGroup = $accessApi->getSocietyGroup();
  1196.             if ($societyGroup instanceof SocietyGroup)
  1197.             {
  1198.                 $societyGroupId = (string) $societyGroup->getId();
  1199.                 $societyGroupRef = (string) $societyGroup->getRef();
  1200.             }
  1201.         }
  1202.         $path = $this->getApiRestRateLimitLogPath();
  1203.         $fileExists = file_exists($path);
  1204.         $stream = fopen($path, $fileExists ? "a" : "w");
  1205.         if ($stream === false)
  1206.         {
  1207.             return;
  1208.         }
  1209.         if (!$fileExists)
  1210.         {
  1211.             $header = [
  1212.                 "timestamp",
  1213.                 "scope",
  1214.                 "ip",
  1215.                 "access_api.id",
  1216.                 "access_api.username",
  1217.                 "society_group.id",
  1218.                 "society_group.ref",
  1219.                 "route",
  1220.                 "max_requests",
  1221.             ];
  1222.             fputcsv($stream, $header, ';');
  1223.         }
  1224.         $now = new \DateTime();
  1225.         $timestamp = $now->format("Y-m-d H:i:s");
  1226.         $line = [
  1227.             $timestamp,
  1228.             $scope,
  1229.             $ip,
  1230.             $accessApiId,
  1231.             $accessApiUsername,
  1232.             $societyGroupId,
  1233.             $societyGroupRef,
  1234.             $route,
  1235.             $maxRequests,
  1236.         ];
  1237.         fputcsv($stream, $line, ';');
  1238.         fclose($stream);
  1239.     }
  1240.     public function mobileAppLog(array $args)
  1241.     {
  1242.         if (empty($args['info_error'])) return false;
  1243.         // Get data
  1244.         $errorCode = $args['info_error']['errorCode'];
  1245.         $errorMessage = $args['info_error']['errorMessage'];
  1246.         $message = array_key_exists('message', $args) ? $args['message'] : null;
  1247.         // Optional
  1248.         $author = array_key_exists('author', $args) ? $args['author'] : null;
  1249.         if ($author === null)
  1250.         {
  1251.             if ($this->security->getUser() instanceof Access)
  1252.             {
  1253.                 $author = $this->security->getUser();
  1254.             }
  1255.             else
  1256.             {
  1257.                 $author = $this->em
  1258.                     ->getRepository(Access::class)
  1259.                     ->findOneByUsername(Access::API_ACCESS);
  1260.             }
  1261.         }
  1262.         $authorId = '';
  1263.         $authorUsername = '';
  1264.         $authorSocietyGroup = '';
  1265.         $authorSocietyGroupId = '';
  1266.         if ($author !== null)
  1267.         {
  1268.             $authorId = $author->getId();
  1269.             $authorUsername = $author->getUsername();
  1270.             $authorSocietyGroup = $author->getSocietyGroup();
  1271.             if ($authorSocietyGroup !== null) {
  1272.                 $authorSocietyGroupId = $authorSocietyGroup->getId();
  1273.             }
  1274.         }
  1275.         // Handle OriginalUser
  1276.         $originalUser = $this->getOriginalUser();
  1277.         $originalUserId = '';
  1278.         $originalUserName = '';
  1279.         if ($originalUser !== null)
  1280.         {
  1281.             $originalUserId = $originalUser->getId();
  1282.             $originalUserName = $originalUser->getUserIdentifier();
  1283.         }
  1284.         // Error
  1285.         $error = false;
  1286.         if (array_key_exists('error', $args) && ($args['error'] == false || $args['error'] == true))
  1287.         {
  1288.             $error = $args['error'];
  1289.         }
  1290.         $status = $error ? 'ERROR' : 'DONE';
  1291.         $logPath = $this->getApiIcodLogsDir();
  1292.         $now = new DateTime();
  1293.         $logPath = $logPath . 'api_rest_log/mobile_app_' . $now->format('Y_m') . '.log';
  1294.         $path = $logPath;
  1295.         // Backtrace
  1296.         $backTrace = debug_backtrace()[1];
  1297.         $backTraceMethod = $backTrace['function'];
  1298.         $backTraceClass = $backTrace['class'];
  1299.         // Date
  1300.         $now = new \DateTime();
  1301.         $nowDate = $now->format('Y-m-d H:i:s');
  1302.         $line = "[ " . $nowDate . " ][ " . $status . " ][ " . $errorCode . " ][ " . $errorMessage . " ][ id societyGroup : " . $authorSocietyGroupId . " ][ id access : " . $authorId . " ][ " . $authorUsername . " ][ " . $originalUserId . " ][ " . $originalUserName . " ][ " . $backTraceClass . " ][ " . $backTraceMethod . " ]\t";
  1303.         if(!empty($message)) $line .= " : " . $message;
  1304.         $line .= "\n";
  1305.         error_log($line, 3, $logPath);
  1306.     }
  1307.     /**
  1308.      * @deprecated Use LogMobileAppTools::logInternal() instead.
  1309.      */
  1310.     public function mobileAppLogInternal(array $args): bool
  1311.     {
  1312.         return false;
  1313.     }
  1314.     /**
  1315.      * @deprecated Use LogMobileAppTools::hasLogGuid() instead.
  1316.      */
  1317.     public function hasMobileAppLogGuid($guid): bool
  1318.     {
  1319.         return false;
  1320.     }
  1321.     public function planning_optimisation_error_log($args)
  1322.     {
  1323.         $home = $this->getPlanningOptimisationLogsDir();
  1324.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1325.         $home .= "errors/";
  1326.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1327.         $today = new \DateTime();
  1328.         $log = "planning_errors_".$today->format("Ymd").".log";
  1329.         $logFile = $home . $log;
  1330.         $jsonString = json_encode($args, JSON_PRETTY_PRINT);
  1331.         $fp = fopen($logFile, 'a');
  1332.         // Is it empty ?
  1333.         if (filesize($logFile))
  1334.         {
  1335.             // Not empty
  1336.             // Remove last char "}" of file and add ,
  1337.             $stat = fstat($fp);
  1338.             ftruncate($fp, $stat['size']-1);
  1339.             fwrite($fp, ",\n");
  1340.         }
  1341.         else
  1342.         {
  1343.             // Empty
  1344.             // Write begining of json object
  1345.             fwrite($fp, "{\n");
  1346.         }
  1347.         fwrite($fp, '"'.uniqid().'"' . " : ");
  1348.         fwrite($fp, $jsonString);
  1349.         fwrite($fp, "}");
  1350.         fclose($fp);
  1351.     }
  1352.     public function kitchenPlannerErrorlog($msg)
  1353.     {
  1354.         $home = $this->getIkeaKitchenPlannerLogsDir();
  1355.         if (!is_dir($home))    
  1356.             mkdir($home, 0755, true);
  1357.         $home .= "errors/";
  1358.         if (!is_dir($home))
  1359.             mkdir($home, 0755, true);
  1360.         $today = new \DateTime();
  1361.         $log = "kitchen_planner_errors_".$today->format("Ymd").".log";
  1362.         $logFile = $home . $log;
  1363.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1364.         $backTrace = debug_backtrace()[1];
  1365.         $method = $backTrace['function'];
  1366.         $class = $backTrace['class'];
  1367.         $trace = $class."::".$method;
  1368.         $trace = str_replace("\\", "::", $trace);
  1369.         $content = "[".$trace . "] " . $msg;
  1370.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1371.     }
  1372.     public function planning_optimisation_debug($active, $msg)
  1373.     {
  1374.         if (!$active)
  1375.             return false;
  1376.         $home = $this->getPlanningOptimisationLogsDir();
  1377.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1378.         $today = new \DateTime();
  1379.         $ym = $today->format('Y_m');
  1380.         $log = "planning_optimisation_debug_$ym.log";
  1381.         $logFile = $home . $log;
  1382.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1383.         $backTrace = debug_backtrace()[1];
  1384.         $method = $backTrace['function'];
  1385.         $class = $backTrace['class'];
  1386.         $trace = $class."::".$method;
  1387.         $trace = str_replace("\\", "::", $trace);
  1388.         $content = "[".$trace . "] " . $msg;
  1389.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1390.     }
  1391.     // Logs Errors to root/custom_logs/stripe/errors/stripe_errors_yyyymmdd.log
  1392.     // One single log each day
  1393.     public function stripe_error_log($args)
  1394.     {
  1395.         $home = $this->getStripeLogsDir();
  1396.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1397.         $home .= "errors/";
  1398.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1399.         $today = new \DateTime();
  1400.         $log = "stripe_errors_".$today->format("Ymd").".log";
  1401.         $logFile = $home . $log;
  1402.         $jsonString = json_encode($args, JSON_PRETTY_PRINT);
  1403.         $fp = fopen($logFile, 'a');
  1404.         // Is it empty ?
  1405.         if (filesize($logFile))
  1406.         {
  1407.             // Not empty
  1408.             // Remove last char "}" of file and add ,
  1409.             $stat = fstat($fp);
  1410.             ftruncate($fp, $stat['size']-1);
  1411.             fwrite($fp, ",\n");
  1412.         }
  1413.         else
  1414.         {
  1415.             // Empty
  1416.             // Write begining of json object
  1417.             fwrite($fp, "{\n");
  1418.         }
  1419.         fwrite($fp, '"'.uniqid().'"' . " : ");
  1420.         fwrite($fp, $jsonString);
  1421.         fwrite($fp, "}");
  1422.         fclose($fp);
  1423.     }
  1424.     // Logs Errors to root/custom_logs/guest_suite_log/guest_suite_yyyymmdd.log
  1425.     // One single log each day
  1426.     public function guest_suite_log($args)
  1427.     {
  1428.         $home = $this->getGuestSuiteLogsDir();
  1429.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1430.         $today = new \DateTime();
  1431.         $log = "guest_suite_".$today->format("Ymd").".log";
  1432.         $logFile = $home . $log;
  1433.         $jsonString = json_encode($args, JSON_PRETTY_PRINT);
  1434.         $fp = fopen($logFile, 'a');
  1435.         // Is it empty ?
  1436.         if (filesize($logFile))
  1437.         {
  1438.             // Not empty
  1439.             // Remove last char "}" of file and add ,
  1440.             $stat = fstat($fp);
  1441.             ftruncate($fp, $stat['size']-1);
  1442.             fwrite($fp, ",\n");
  1443.         }
  1444.         else
  1445.         {
  1446.             // Empty
  1447.             // Write begining of json object
  1448.             fwrite($fp, "{\n");
  1449.         }
  1450.         fwrite($fp, '"'.uniqid().'"' . " : ");
  1451.         fwrite($fp, $jsonString);
  1452.         fwrite($fp, "}");
  1453.         fclose($fp);
  1454.     }
  1455.     // Logs to root/custom_logs/sms_logs/sms_reminder_task_yyyy_mm.log
  1456.     // Used by CRON command reminder
  1457.     public function sms_reminder_debug($active, $msg)
  1458.     {
  1459.         if (!$active)
  1460.             return false;
  1461.         $home = $this->getSmsLogsDir();
  1462.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1463.         $today = new \DateTime();
  1464.         $ym = $today->format('Y_m');
  1465.         $log = "sms_reminder_task_$ym.log";
  1466.         $logFile = $home . $log;
  1467.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1468.         $content = $timestamp . $msg;
  1469.         error_log("\n".$content,3,$logFile);
  1470.     }
  1471.     // Logs to root/custom_logs/sms_logs/sms_debug_yyyy_mm.log
  1472.     // Used to debug info SMS
  1473.     // For errors check LogTools::smsLogDebug method/file
  1474.     public function sms_debug($active, $msg)
  1475.     {
  1476.         if (!$active)
  1477.             return false;
  1478.         $home = $this->getSmsLogsDir();
  1479.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1480.         $today = new \DateTime();
  1481.         $ym = $today->format('Y_m');
  1482.         $log = "sms_debug_$ym.log";
  1483.         $logFile = $home . $log;
  1484.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1485.         $backTrace = debug_backtrace()[1];
  1486.         $method = $backTrace['function'];
  1487.         $class = $backTrace['class'];
  1488.         $trace = $class."::".$method;
  1489.         $trace = str_replace("\\", "::", $trace);
  1490.         $content = "[".$trace . "] " . $msg;
  1491.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1492.     }
  1493.     // Logs to root/custom_logs/email_logs/email_debug_yyyy_mm.log
  1494.     // Handles Backtrace info
  1495.     public function email_debug($active, $msg)
  1496.     {
  1497.         if (!$active)
  1498.             return false;
  1499.         $home = $this->getEmailLogsDir();
  1500.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1501.         $today = new \DateTime();
  1502.         $ym = $today->format('Y_m');
  1503.         $log = "email_debug_$ym.log";
  1504.         $logFile = $home . $log;
  1505.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1506.         $backTrace = debug_backtrace()[1];
  1507.         $method = $backTrace['function'];
  1508.         $class = $backTrace['class'];
  1509.         $trace = $class."::".$method;
  1510.         $trace = str_replace("\\", "::", $trace);
  1511.         $content = "[".$trace . "] " . $msg;
  1512.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1513.     }
  1514.     // Plan.io Task #4634
  1515.     // Logs to custom_logs/EInvoicing/{societyGroupId|other}/Invoices/{invoiceId|unmatched}.log
  1516.     // One file per inbound invoice: covers its full lifecycle (reception, enrichment, status changes).
  1517.     public function einvoicingInvoiceLog(string $msg, ?SocietyGroup $societyGroup, ?string $invoiceId): void
  1518.     {
  1519.         $home = $this->getEInvoicingGroupDir($societyGroup);
  1520.         if ($home === null)
  1521.         {
  1522.             return;
  1523.         }
  1524.         $home .= "Invoices/";
  1525.         if (!is_dir($home))
  1526.         {
  1527.             mkdir($home, 0755, true);
  1528.         }
  1529.         $file = ($invoiceId !== null && $invoiceId !== '' ? $invoiceId : 'unmatched').".log";
  1530.         $this->writeEInvoicingLogLine($home.$file, $msg);
  1531.     }
  1532.     // Plan.io Task #4634
  1533.     // Logs to custom_logs/EInvoicing/{societyGroupId|other}/Onboarding/{societyId|unmatched}.log
  1534.     // One file per society: covers its full onboarding history with the provider.
  1535.     public function einvoicingOnboardingLog(string $msg, ?Society $society): void
  1536.     {
  1537.         $home = $this->getEInvoicingGroupDir($society?->getSocietyGroup());
  1538.         if ($home === null)
  1539.         {
  1540.             return;
  1541.         }
  1542.         $home .= "Onboarding/";
  1543.         if (!is_dir($home))
  1544.         {
  1545.             mkdir($home, 0755, true);
  1546.         }
  1547.         $file = ($society?->getId() !== null ? (string) $society->getId() : 'unmatched').".log";
  1548.         $this->writeEInvoicingLogLine($home.$file, $msg);
  1549.     }
  1550.     // Plan.io Task #4634
  1551.     // Logs to custom_logs/EInvoicing/{societyGroupId|other}/Iopole_API/iopole_api_{Y_m_d}.log
  1552.     // One file per day: every Iopole API call answering an error (non 2xx status or transport failure).
  1553.     // When no society group is given, it is resolved from the currently authenticated user.
  1554.     public function einvoicingIopoleApiErrorLog(string $msg, ?SocietyGroup $societyGroup = null): void
  1555.     {
  1556.         $home = $this->getEInvoicingGroupDir($societyGroup ?? $this->getCurrentUserSocietyGroup());
  1557.         if ($home === null)
  1558.         {
  1559.             return;
  1560.         }
  1561.         $home .= "Iopole_API/";
  1562.         if (!is_dir($home))
  1563.         {
  1564.             mkdir($home, 0755, true);
  1565.         }
  1566.         $file = "iopole_api_".(new DateTime())->format('Y_m_d').".log";
  1567.         $this->writeEInvoicingLogLine($home.$file, $msg);
  1568.     }
  1569.     // Plan.io Task #4634
  1570.     // Society group of the authenticated user, or null outside of a firewall (CLI commands, webhooks).
  1571.     private function getCurrentUserSocietyGroup(): ?SocietyGroup
  1572.     {
  1573.         $access = $this->security->getUser();
  1574.         if ($access instanceof Access || $access instanceof AccessAPI)
  1575.         {
  1576.             return $access->getSocietyGroup();
  1577.         }
  1578.         return null;
  1579.     }
  1580.     // Plan.io Task #4634
  1581.     // Visual separator printed before each EInvoicing log entry, for readability.
  1582.     private const EINVOICING_LOG_SEPARATOR = '────────────────────────────────────────────────────────────────';
  1583.     // Plan.io Task #4634
  1584.     // Shared write logic for EInvoicing logs: separator + timestamp/class::method header,
  1585.     // message on its own line below.
  1586.     private function writeEInvoicingLogLine(string $logFile, string $msg): void
  1587.     {
  1588.         $today = new \DateTime();
  1589.         $backTrace = debug_backtrace()[2];
  1590.         $classParts = explode('\\', $backTrace['class']);
  1591.         $shortClass = end($classParts);
  1592.         $trace = $shortClass.'::'.$backTrace['function'];
  1593.         $entry = self::EINVOICING_LOG_SEPARATOR."\n"
  1594.             .$today->format('[Y-m-d H:i:s] ').$trace."\n"
  1595.             .$msg."\n";
  1596.         error_log($entry, 3, $logFile);
  1597.     }
  1598.     // Logs to root/custom_logs/stripe/stripe_yyyymmdd.log
  1599.     // One single log each day
  1600.     public function stripe_debug($active, $msg)
  1601.     {
  1602.         if (!$active)
  1603.             return false;
  1604.         $home = $this->getStripeLogsDir();
  1605.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1606.         $today = new \DateTime();
  1607.         $log = "stripe_".$today->format("Ymd").".log";
  1608.         $logFile = $home . $log;
  1609.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1610.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  1611.     }
  1612.     // OLD : Logs to root/custom_logs/ikea_debug/ikea_os_yyyymm.log
  1613.     // NEW : Logs to root/custom_logs/ikea_debug/ikea_os_yyyy_week_{weekNumber}.log
  1614.     // One single log each week - Log file created on Monday
  1615.     public function ikea_debug($active, $msg)
  1616.     {
  1617.         if (!$active)
  1618.             return false;
  1619.         $home = $this->getCustomLogsDir()."ikea_debug/";
  1620.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1621.         $today = new \DateTime();
  1622.         
  1623.         // Old format : Each month
  1624.         // $log = "ikea_os_".$today->format("Y").$today->format("m").".log";
  1625.         // New format : Each week on Mondays
  1626.         // ikea_os_2026_week_21.log
  1627.         $log = "ikea_os_".$today->format("o")."_week_".$today->format("W").".log";
  1628.         $logFile = $home . $log;
  1629.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1630.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  1631.         
  1632.         $this->plooplog("\n".$timestamp.$msg."\n");
  1633.     }
  1634.     // Logs to root/custom_logs/ikea_debug_status/ikea_os_status_yyyymmdd.log
  1635.     // One single log each day
  1636.     public function ikea_debug_status($active, $msg)
  1637.     {
  1638.         if (!$active)
  1639.             return false;
  1640.         $home = $this->getCustomLogsDir()."ikea_debug_status/";
  1641.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1642.         $today = new \DateTime();
  1643.         $log = "ikea_os_status_".$today->format("Ymd").".log";
  1644.         $logFile = $home . $log;
  1645.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1646.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  1647.     }
  1648.     // Logs to root/custom_logs/ikea_debug_status/ikea_os_cancelled_yyyymm.log
  1649.     // One single log each month
  1650.     public function ikea_debug_cancelled($active, $msg)
  1651.     {
  1652.         if (!$active)
  1653.             return false;
  1654.         $home = $this->getCustomLogsDir()."ikea_debug_status/";
  1655.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1656.         $today = new \DateTime();
  1657.         $log = "ikea_os_cancelled_".$today->format("Ym").".log";
  1658.         $logFile = $home . $log;
  1659.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1660.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  1661.     }
  1662.     public function ikea_send($args)
  1663.     {
  1664.         //Returns User object or null if not authenticated
  1665.         $currentUser = $this->getCurrentUser();
  1666.         if ($currentUser === null)
  1667.         {
  1668.             return false;
  1669.         }
  1670.         $home = $this->getIkeaSendLogsDir($currentUser->getSocietyGroup());
  1671.         if ($home === null)
  1672.         {
  1673.             return false;
  1674.         }
  1675.         $now = new \DateTime();
  1676.         $log = "ikea_object_send_".$now->format("Ym").".csv";
  1677.         $logFile = $home . $log;
  1678.         // $user = array_key_exists('user', $args) ? (string) $args['user'] : '';
  1679.         $filename = array_key_exists('filename', $args) ? (string) $args['filename'] : '';
  1680.         $targetPath = array_key_exists('targetPath', $args) ? (string) $args['targetPath'] : '';
  1681.         $ikeaOrderNumber = array_key_exists('ikeaOrderNumber', $args) ? (string) $args['ikeaOrderNumber'] : '';
  1682.         $action = array_key_exists('action', $args) ? (string) $args['action'] : '';
  1683.         $result = array_key_exists('result', $args) ? (string) $args['result'] : '';
  1684.         $fileExists = file_exists($logFile);
  1685.         $stream = fopen($logFile, $fileExists ? "a" : "w");
  1686.         if ($stream === false)
  1687.         {
  1688.             return false;
  1689.         }
  1690.         if (!$fileExists)
  1691.         {
  1692.             $header = [
  1693.                 "timestamp",
  1694.                 "user",
  1695.                 "filename",
  1696.                 "targetPath",
  1697.                 "ikeaOrderNumber",
  1698.                 "action",
  1699.                 "result",
  1700.             ];
  1701.             fputcsv($stream, $header, ';');
  1702.         }
  1703.         $timestamp = $now->format("Y-m-d H:i:s");
  1704.         $line = [
  1705.             $timestamp,
  1706.             $currentUser->getUsername(),
  1707.             $filename,
  1708.             $targetPath,
  1709.             $ikeaOrderNumber,
  1710.             $action,
  1711.             $result,
  1712.         ];
  1713.         fputcsv($stream, $line, ';');
  1714.         fclose($stream);
  1715.         return true;
  1716.     }
  1717.     
  1718.     // Logs to root/custom_logs/cost/auto_accounting/cost_auto_accounting_debug_yyyy_mm_dd.log
  1719.     // Handles Backtrace info
  1720.     public function cost_auto_accounting_debug($active, $msg)
  1721.     {
  1722.         if (!$active)
  1723.             return false;
  1724.         $home = $this->getCostAutoAccountingDebugDir();
  1725.         if ($home === null) return false;
  1726.         $today = new \DateTime();
  1727.         $ym = $today->format('Y_m_d');
  1728.         $log = "cost_auto_accounting_debug_$ym.log";
  1729.         $logFile = $home . $log;
  1730.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1731.         $backTrace = debug_backtrace()[1];
  1732.         $method = $backTrace['function'];
  1733.         $class = $backTrace['class'];
  1734.         $trace = $class."::".$method;
  1735.         $trace = str_replace("\\", "::", $trace);
  1736.         $content = "[".$trace . "] " . $msg;
  1737.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1738.         return true;
  1739.     }
  1740.     
  1741.     // Logs to root/custom_logs/invoice/auto_accounting/invoice_auto_accounting_debug_yyyy_mm_dd.log
  1742.     // Handles Backtrace info
  1743.     public function invoice_auto_accounting_debug($active, $msg)
  1744.     {
  1745.         if (!$active)
  1746.             return false;
  1747.         $home = $this->getInvoiceAutoAccountingDebugDir();
  1748.         if ($home === null) return false;
  1749.         $today = new \DateTime();
  1750.         $ym = $today->format('Y_m_d');
  1751.         $log = "invoice_auto_accounting_debug_$ym.log";
  1752.         $logFile = $home . $log;
  1753.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1754.         $backTrace = debug_backtrace()[1];
  1755.         $method = $backTrace['function'];
  1756.         $class = $backTrace['class'];
  1757.         $trace = $class."::".$method;
  1758.         $trace = str_replace("\\", "::", $trace);
  1759.         $content = "[".$trace . "] " . $msg;
  1760.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1761.         return true;
  1762.     }
  1763.     // Logs to root/custom_logs/zone/debug/zone_debug_yyyy_mm_dd.log
  1764.     // Handles Backtrace info
  1765.     public function zone_debug($active, $msg)
  1766.     {
  1767.         if (!$active)
  1768.             return false;
  1769.         $home = $this->getZoneDebugDir();
  1770.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1771.         $today = new \DateTime();
  1772.         $ym = $today->format('Y_m_d');
  1773.         $log = "zone_debug_$ym.log";
  1774.         $logFile = $home . $log;
  1775.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1776.         $backTrace = debug_backtrace()[1];
  1777.         $method = $backTrace['function'];
  1778.         $class = $backTrace['class'];
  1779.         $trace = $class."::".$method;
  1780.         $trace = str_replace("\\", "::", $trace);
  1781.         $content = "[".$trace . "] " . $msg;
  1782.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1783.     }
  1784.     // Logs to root/custom_logs/itask_logs/debug/debug_yyyy_mm_dd.log
  1785.     public function itask_debug($active, $msg)
  1786.     {
  1787.         if (!$active)
  1788.             return false;
  1789.         $home = $this->getITaskDebugDir();
  1790.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1791.         $today = new \DateTime();
  1792.         $ym = $today->format('Y_m_d');
  1793.         $log = "debug_$ym.log";
  1794.         $logFile = $home . $log;
  1795.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1796.         $content = $msg;
  1797.         // $backTrace = debug_backtrace()[1];
  1798.         // $method = $backTrace['function'];
  1799.         // $class = $backTrace['class'];
  1800.         // $trace = $class."::".$method;
  1801.         // $trace = str_replace("\\", "::", $trace);
  1802.         //
  1803.         // $content = "[".$trace . "] " . $msg;
  1804.         error_log("\n".$timestamp.$content."\n",3,$logFile);
  1805.     }
  1806.     // Logs to root/custom_logs/pra/pra_os_yyyymm.log
  1807.     // One single log each month
  1808.     public function pra_debug($active, $msg)
  1809.     {
  1810.         if (!$active)
  1811.             return false;
  1812.         $home = $this->getCustomLogsDir()."pra/";
  1813.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1814.         $today = new \DateTime();
  1815.         $log = "pra_".$today->format("Y").$today->format("m").".log";
  1816.         $logFile = $home . $log;
  1817.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1818.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  1819.     }
  1820.     // Used to trace SMS related errors
  1821.     // For happy path check LogTools::sms_debug method/file
  1822.     public function smsLogDebug($msg, $activeProvider = null)
  1823.     {
  1824.         $today = new \DateTime();
  1825.         $ym = $today->format('Y_m');
  1826.         $backTrace = debug_backtrace()[1];
  1827.         $method = $backTrace['function'];
  1828.         $class = $backTrace['class'];
  1829.         $trace = $class."::".$method;
  1830.         $traceLevelOne = str_replace("\\", "::", $trace);
  1831.         $backTrace = debug_backtrace()[2];
  1832.         $method = $backTrace['function'];
  1833.         $class = $backTrace['class'];
  1834.         $trace = $class."::".$method;
  1835.         $traceLevelTwo = str_replace("\\", "::", $trace);
  1836.         // $backTrace = debug_backtrace()[3];
  1837.         // $method = $backTrace['function'];
  1838.         // $class = $backTrace['class'];
  1839.         // $trace = $class."::".$method;
  1840.         // $traceLevelThree = str_replace("\\", "::", $trace);
  1841.         // $content = $traceLevelOne . " : " . $traceLevelTwo . " : " . $traceLevelThree . " : " . $msg;
  1842.         $content = $traceLevelOne . " : " . $traceLevelTwo . " : " . $msg;
  1843.         if ($activeProvider !== null)
  1844.         {
  1845.             if ($activeProvider->isSmsMode())
  1846.             {
  1847.                 $file = $this->getSmsLogsDir() . "sms_mode_$ym.log";
  1848.             }
  1849.             else
  1850.             {
  1851.                 if ($activeProvider->isSmsTwilio())
  1852.                 {
  1853.                     $file = $this->getSmsLogsDir() . "sms_twilio_$ym.log";
  1854.                 }
  1855.             }
  1856.         }
  1857.         else
  1858.         {
  1859.             $file = $this->getSmsLogsDir() . "sms_unknown_provider_$ym.log";
  1860.         }
  1861.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1862.         error_log("\n".$timestamp.$content."\n",3,$file);
  1863.     }
  1864.     // Task plan.io #4024
  1865.     // Logs to root/custom_logs/availability/availability_clean_{Y_m_d}.log
  1866.     public function availabilityClean_log($msg)
  1867.     {
  1868.         $home = $this->getAvailablityDir();
  1869.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1870.         $today = new \DateTime();
  1871.         $log = "availability_clean_" . $today->format("Y") . "_" . $today->format("m") . "_" . $today->format("d") . ".log";
  1872.         $logFile = $home . $log;
  1873.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1874.         error_log("\n".$timestamp.$msg."\n",3,$logFile);
  1875.     }
  1876.     // Task plan.io #4386
  1877.     // Logs to root/custom_logs/financial/scan_financial/scan_financial_{Y_m_d}.log
  1878.     public function scanFinancialCommand_log($msg)
  1879.     {
  1880.         $home = $this->getScanFinancialDir();
  1881.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1882.         $today = new \DateTime();
  1883.         $log = "scan_financial_" . $today->format("Y_m_d") . ".log";
  1884.         $logFile = $home . $log;
  1885.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1886.         error_log($timestamp.$msg."\n",3,$logFile);
  1887.     }
  1888.     // Task plan.io #4386
  1889.     // Logs to root/custom_logs/financial/duplicate_code/duplicate_code_{Y_m_d}.log
  1890.     public function logDuplicateFinancialCode(Client $client, array $clientsSameAccount)
  1891.     {
  1892.         $home = $this->getDuplicateCodeFinancialDir();
  1893.         if (!is_dir($home))    $do = mkdir($home, 0755, true);
  1894.         $today = new \DateTime();
  1895.         $log = "duplicate_code_" . $today->format("Y_m_d") . ".log";
  1896.         $logFile = $home . $log;
  1897.         // Build log message
  1898.         $societyGroup = $client->getSocietyGroup();
  1899.         $financial = $client->getFinancial();
  1900.         $societyGroupDisplay = $societyGroup !== null ? $societyGroup->displayForLog() : '';
  1901.         $financialDisplay = $financial !== null ? $financial->displayForLog() : '';
  1902.         $timestamp = $today->format("[Y-m-d H:i:s] ");
  1903.         $msg = "-------------------";
  1904.         $msg .= "\n" . $timestamp;
  1905.         $msg .= "\nSocietyGroup : " . $societyGroupDisplay;
  1906.         $msg .= "\nClient : " . $client->displayForLog();
  1907.         $msg .= "\nIndividual : " . $client->displayChildrenForLog();
  1908.         $msg .= "\nFinancial : " . $financialDisplay;
  1909.         $msg .= "\n";
  1910.         $msg .= "\nOther client/financial concerned\n";
  1911.         foreach($clientsSameAccount as $clientSameAccount)
  1912.         {
  1913.             $financialSameAccount = $clientSameAccount->getFinancial();
  1914.             if ($financialSameAccount === null) continue;
  1915.             $msg .= "\nClient : " . $clientSameAccount->displayForLog();
  1916.             $msg .= ", Individual : " . $clientSameAccount->displayChildrenForLog();
  1917.             $msg .= ", Financial : " . $financialSameAccount->displayForLog();
  1918.         }
  1919.         error_log("\n" . $msg,3,$logFile);
  1920.     }
  1921.     // Task plan.io #4331
  1922.     // Logs to root/custom_logs/payment_logs/error
  1923.     public function payment_log_error($msg, $args)
  1924.     {
  1925.         // Setup
  1926.         $today = new \DateTime();
  1927.         $backTrace = debug_backtrace()[1];
  1928.         $method = $backTrace['function'];
  1929.         $class = $backTrace['class'];
  1930.         $trace = $class."::".$method;
  1931.         // Build logFile
  1932.         $home = $this->getPaymentErrorLogDir();
  1933.         if (!is_dir($home))    mkdir($home, 0755, true);
  1934.         // Build args
  1935.         $societyGroupDisplay = "no_society_group";
  1936.         if (array_key_exists('society_group', $args) && $args['society_group'] instanceof SocietyGroup)
  1937.         {
  1938.             $societyGroupDisplay = $args['society_group']->displayForLog();
  1939.         }
  1940.         $type = !empty($args['type']) ? $args['type'] : "";
  1941.         switch($type)
  1942.         {
  1943.             case OnlinePaymentType::CODE_STRIPE:
  1944.             {
  1945.                 $prefixLog = "STRIPE";
  1946.                 break;
  1947.             }
  1948.             case OnlinePaymentType::CODE_PAYPAL:
  1949.             {
  1950.                 $prefixLog = "PAYPAL";
  1951.                 break;
  1952.             }
  1953.             default:
  1954.             {
  1955.                 $prefixLog = "PAYMENT";
  1956.                 break;
  1957.             }
  1958.         }
  1959.         $logFilename = "payment_error_" . $today->format("Y-m-d") . ".log";
  1960.         $logFilename = $home . $logFilename;
  1961.         if (!file_exists($logFilename))
  1962.         {
  1963.             // Create header
  1964.             $header = "payment_type,timestamp,society_group,trace,message";
  1965.             error_log($header . "\n", 3, $logFilename);
  1966.         }
  1967.         // Log
  1968.         $logMessage = $prefixLog . ",";
  1969.         $logMessage .= $today->format("Y-m-d H:i:s") . ",";
  1970.         $logMessage .= $societyGroupDisplay . ",";
  1971.         $logMessage .= $trace . ",";
  1972.         $logMessage .= $msg . "\n";
  1973.         error_log($logMessage, 3, $logFilename);
  1974.     }
  1975.     // Plan.io Task #4327
  1976.     // Logs to root/custom_logs/booking/booking_sav_slot
  1977.     public function bookingSavSlot_log($msg, $args)
  1978.     {
  1979.         $home = $this->getBookingSavSlotLogDir();
  1980.         if (!is_dir($home))    mkdir($home, 0755, true);
  1981.         $today = new \DateTime();
  1982.         $logFile = "booking-log-" . $today->format("Y-m-d") . ".log";
  1983.         $logFile = $home . $logFile;
  1984.         $devis = !empty($args['devis']) ? $args['devis'] : null;
  1985.         $timestamp = $today->format("[Y-m-d H:i:s]");
  1986.         $log = $timestamp;
  1987.         if ($devis !== null)
  1988.         {
  1989.             $log .= "[".$devis->getId()."-".$devis->getRef()."]";
  1990.         }
  1991.         $log .= " " . $msg;
  1992.         error_log($log."\n",3,$logFile);
  1993.     }
  1994.     // Plan.io Task #4327
  1995.     // Logs to root/custom_logs/booking/clean_booking_sav_slot
  1996.     public function cleanBookingSavSlot_log($msg)
  1997.     {
  1998.         $home = $this->getCleanBookingSavSlotLogDir();
  1999.         if (!is_dir($home))    mkdir($home, 0755, true);
  2000.         $today = new \DateTime();
  2001.         $logFile = "clean-booking-log-" . $today->format("Y-m-d") . ".log";
  2002.         $logFile = $home . $logFile;
  2003.         error_log($msg."\n",3,$logFile);
  2004.     }
  2005.     /**
  2006.      * Logs a throwable to the ouch DB log, together with the SQL query and its
  2007.      * bound parameters when the error comes from Doctrine.
  2008.      *
  2009.      * @param mixed $e Anything; only a \Throwable is logged, everything else is ignored
  2010.      */
  2011.     public function errorlog_db($e)
  2012.     {
  2013.         // \Throwable and not \Exception: PHP 8 errors (Error, TypeError…) do not
  2014.         // extend \Exception, and call sites catching \Throwable would lose them.
  2015.         if (!($e instanceof \Throwable))
  2016.         {
  2017.             return null;
  2018.         }
  2019.         // Read outside the Doctrine block below: a plain exception carries no query,
  2020.         // and its message must reach the log all the same.
  2021.         $msg = $e->getMessage();
  2022.         $sql = null;
  2023.         $params = null;
  2024.         // DBAL 3 declares DriverException::getQuery() as nullable â€” a driver error
  2025.         // with no query attached returns null, and method_exists(null, ...) is a
  2026.         // TypeError, which would replace the very exception being logged.
  2027.         if (method_exists($e, 'getQuery'))
  2028.         {
  2029.             $query = $e->getQuery();
  2030.             if (is_object($query) && method_exists($query, 'getSQL') && method_exists($query, 'getParams'))
  2031.             {
  2032.                 $sql = $query->getSQL();
  2033.                 $params = print_r($query->getParams(), true);
  2034.             }
  2035.         }
  2036.         // Guarded: frame 1 is missing when there is no caller, and 'class' is unset
  2037.         // when the caller is a closure or a plain function.
  2038.         $backTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1] ?? [];
  2039.         $method = $backTrace['function'] ?? '';
  2040.         $class = $backTrace['class'] ?? '';
  2041.         // Access handling
  2042.         $access = $this->getCurrentUser();
  2043.         $originalUser = $this->getOriginalUser();
  2044.         $accessData = "";
  2045.         $originalUserData = "";
  2046.         if ($access !== null)
  2047.         {
  2048.             $accessData = $access->displayForLog();
  2049.         }
  2050.         if ($originalUser !== null)
  2051.         {
  2052.             $originalUserData = $originalUser->displayForLog();
  2053.         }
  2054.         $args = array(
  2055.             'recorded_by'        =>    "LogTools::errorlog_db",
  2056.             'class'                =>    $class,
  2057.             'method'            =>    $method,
  2058.             'access'            =>    $accessData,
  2059.             'original_access'    =>    $originalUserData,
  2060.             'msg'                =>    $msg,
  2061.             'query'                =>    $sql,
  2062.             'params'          =>    $params,
  2063.         );
  2064.         $this->plooplog(print_r($args, true));
  2065.         $this->ouchLogger->error('', $args);
  2066.     }
  2067.     public function errorlog($msg)
  2068.     {
  2069.         $backTrace = debug_backtrace()[1];
  2070.         $method = $backTrace['function'];
  2071.         $class = $backTrace['class'];
  2072.         // Access handling
  2073.         $access = $this->getCurrentUser();
  2074.         $originalUser = $this->getOriginalUser();
  2075.         $accessData = "";
  2076.         $originalUserData = "";
  2077.         if ($access !== null)
  2078.         {
  2079.             $accessData = $access->displayForLog();
  2080.         }
  2081.         if ($originalUser !== null)
  2082.         {
  2083.             $originalUserData = $originalUser->displayForLog();
  2084.         }
  2085.         $args = array(
  2086.             'recorded_by'        =>    "LogTools::errorlog",
  2087.             'class'                =>    $class,
  2088.             'method'            =>    $method,
  2089.             'original_access'    =>    $originalUserData,
  2090.             'msg'                =>    $msg,
  2091.         );
  2092.         if ($access instanceof Access)
  2093.         {
  2094.             $args['access'] = $accessData;
  2095.         }
  2096.         else
  2097.         {
  2098.             if ($access instanceof AccessClient)
  2099.             {
  2100.                 $args['accessClient'] = $accessData;
  2101.             }
  2102.             else
  2103.             {
  2104.                 if ($access instanceof AccessAPI)
  2105.                 {
  2106.                     $args['accessApi'] = $accessData;
  2107.                 }
  2108.             }
  2109.         }
  2110.         $this->plooplog(print_r($args, true));
  2111.         $this->ouchLogger->error('', $args);
  2112.     }
  2113.     // plan.io #3716
  2114.     public function errorlogApi($msg, $accessApi)
  2115.     {
  2116.         $backTrace = debug_backtrace()[1];
  2117.         $method = $backTrace['function'];
  2118.         $class = $backTrace['class'];
  2119.         // Access handling
  2120.         $accessData = "";
  2121.         if ($accessApi !== null)
  2122.         {
  2123.             $accessData = $accessApi->displayForLog();
  2124.         }
  2125.         $args = array(
  2126.             'class'                =>    $class,
  2127.             'method'            =>    $method,
  2128.             'access'            =>    $accessData,
  2129.             'msg'                =>    $msg,
  2130.         );
  2131.         $this->ouchLogger->error('', $args);
  2132.     }
  2133.     public function getCurrentUser()
  2134.     {
  2135.         $emptyAccess = $this->em->getRepository(Access::class)->findOneByEmail(Access::EMPTY_ACCESS);
  2136.         // Get the Access|AccessAPI|AccessClient object returned by the security
  2137.         $access = $this->security->getUser();
  2138.         if (empty($access))
  2139.         {
  2140.             // This occurs when the code is execuded by a Command (CRON Task for example)
  2141.             // In this case there is no firewall, and thus no Access|AccessAPI
  2142.             $access = $emptyAccess;
  2143.         }
  2144.         else
  2145.         {
  2146.             // The firewall has been bypassed => We have an user provider,
  2147.             // either Access or AccessAPI
  2148.             // Plan.io Task #4327 ... or an AccessClient :)
  2149.             if (!($access instanceof Access) && !($access instanceof AccessAPI) && !($access instanceof AccessClient))
  2150.             {
  2151.                 // This should not happen, but hey, life is weird, who knows
  2152.                 // Even VSCode proved to be worse than Atom ;)
  2153.                 $access = $emptyAccess;
  2154.             }
  2155.         }
  2156.         // Plan.io Task #3946
  2157.         if ($access instanceof AccessAPI)
  2158.         {
  2159.             // Almost all AccessAPI have a corresponding Access, so retrive it
  2160.             $access = $access->getAccess();
  2161.         }
  2162.         return $access;
  2163.     }
  2164.     // https://symfony.com/doc/5.4/security/impersonating_user.html
  2165.     // This is also in AdminVoter
  2166.     // This is also in Logger
  2167.     // This is also in LogTools
  2168.     // This is also in AccessTools
  2169.     // This is also in AlertTools
  2170.     public function getOriginalUser()
  2171.     {
  2172.         $originalUser = null;
  2173.         // #3251 Fix to avoid infinity loop
  2174.         $currentUser = $this->getCurrentUser();
  2175.         if ($currentUser === null || $currentUser->getEmail() == Access::EMPTY_ACCESS)
  2176.         {
  2177.             return null;
  2178.         }
  2179.         $token = $this->security->getToken();
  2180.         if ($token instanceof SwitchUserToken)
  2181.         {
  2182.             $originalUser = $token->getOriginalToken()->getUser();
  2183.         }
  2184.         return $originalUser;
  2185.     }
  2186.     public function generateAlphaNumCode($length)
  2187.     {
  2188.         $lowercase = "qwertyuiopasdfghjklzxcvbnm";
  2189.         $numbers = "1234567890";
  2190.         $randomCode = "";
  2191.         $length = abs($length);
  2192.         $lengthSlice = $length / 2;
  2193.         $max = strlen($lowercase) - 1;
  2194.         for ($x = 0; $x < $lengthSlice; $x++)
  2195.         {
  2196.             $i = random_int(0, $max);
  2197.             $randomCode .= $lowercase[$i];
  2198.         }
  2199.         $max = strlen($numbers) - 1;
  2200.         for ($x = 0; $x < $lengthSlice; $x++)
  2201.         {
  2202.             $i = random_int(0, $max);
  2203.             $randomCode .= $numbers[$i];
  2204.         }
  2205.         return str_shuffle($randomCode);
  2206.     }
  2207.     public function generateUniqueGuardLogFileName()
  2208.     {
  2209.         $today = new \DateTime();
  2210.         $prefix = "guard_log_".$today->format("Y_m_d")."_";
  2211.         $code = $this->generateAlphaNumCode(30);
  2212.         $logFileName = $prefix . $code . ".log";
  2213.         return $logFileName;
  2214.     }
  2215.     public function fetchGuardLogFile($logFileName)
  2216.     {
  2217.         $logPath = $this->getGuardGlobalLogsDir();
  2218.         if (!file_exists($logPath))
  2219.         {
  2220.             if (!mkdir($logPath))
  2221.             {
  2222.                 return null;
  2223.             }
  2224.         }
  2225.         $logFile = $logPath.$logFileName;
  2226.         return $logFile;
  2227.     }
  2228.     public function formatGuardLogFile($logFile)
  2229.     {
  2230.         if (!file_exists($logFile))
  2231.         {
  2232.             return "";
  2233.         }
  2234.         $log = file_get_contents($logFile);
  2235.         if ($log)
  2236.         {
  2237.             $log = str_replace("\n", "<br>", $log);
  2238.             $log = str_replace("not found", "<span class='text-danger'>not found</span>", $log);
  2239.             $log = str_replace("inactive", "<span class='text-danger'>inactive</span>", $log);
  2240.             $log = str_replace("error", "<span class='text-danger'><strong>error</strong></span>", $log);
  2241.             $log = str_replace("ADD", "<span style='color:#39c300'><strong>ADD</strong></span>", $log);
  2242.             $log = str_replace("DENY", "<span style='color:#ff731d'><strong>DENY</strong></span>", $log);
  2243.             $log = str_replace("ERROR", "<span style='color:#da0000'><strong>ERROR</strong></span>", $log);
  2244.             $log = str_replace("UPDATE", "<span style='color:#af0076'><strong>UPDATE</strong></span>", $log);
  2245.             $log = str_replace("SYNC", "<span style='color:#d800a6'><strong>SYNC</strong></span>", $log);
  2246.             $log = str_replace("DEACTIVATE", "<span style='color:#645caa'><strong>DEACTIVATE</strong></span>", $log);
  2247.             $log = str_replace("ACTIVATE", "<span style='color:#645caa'><strong>ACTIVATE</strong></span>", $log);
  2248.             $log = str_replace("REMOVE", "<span style='color:#F94A29'><strong>REMOVE</strong></span>", $log);
  2249.             $log = str_replace("DELETE", "<span style='color:#F94A29'><strong>DELETE</strong></span>", $log);
  2250.         }
  2251.         return $log;
  2252.     }
  2253.     public function loadGuardFile($logFile, $fileName)
  2254.     {
  2255.         $log = null;
  2256.         $matrix = null;
  2257.         // Get the configuration file
  2258.         $webPath = $this->getGuardConfigDir();
  2259.         $matrixFile = $webPath . $fileName;
  2260.         if (!file_exists($matrixFile))
  2261.         {
  2262.             $log = array(
  2263.                 "status"        =>    false,
  2264.                 "error"            =>    "config_file_does_not_exist",
  2265.                 "info"            =>    $matrixFile,
  2266.             );
  2267.         }
  2268.         else
  2269.         {
  2270.             // Read its contents
  2271.             $matrix = file_get_contents($matrixFile);
  2272.             if (!$matrix)
  2273.             {
  2274.                 $log = array(
  2275.                     "status"        =>    false,
  2276.                     "error"            =>    "config_file_could_not_be_read",
  2277.                     "info"            =>    $matrixFile,
  2278.                 );
  2279.             }
  2280.             else
  2281.             {
  2282.                 // If we are here it means that all went well
  2283.                 return $matrix;
  2284.             }
  2285.         }
  2286.         if ($log !== null)
  2287.         {
  2288.             // Something went wrong
  2289.             // Get the error and translate it
  2290.             $error = $this->translator->trans($log["error"]);
  2291.             $error .= " : ";
  2292.             $error .= $log["info"];
  2293.             // Save it to the log file, if any
  2294.             if ($logFile !== null)
  2295.             {
  2296.                 error_log("\n".$error, 3, $logFile);
  2297.             }
  2298.             // Log it to global logs also
  2299.             $this->errorlog($error);
  2300.             return null;
  2301.         }
  2302.         // If we are here it means that all went well
  2303.         return $matrix;
  2304.     }
  2305.     public function loadGuardConfigFile($logFile, $fileName)
  2306.     {
  2307.         $log = null;
  2308.         $matrix = null;
  2309.         // Get the configuration file
  2310.         $webPath = $this->getGuardConfigDir();
  2311.         $matrixFile = $webPath . $fileName;
  2312.         if (!file_exists($matrixFile))
  2313.         {
  2314.             $log = array(
  2315.                 "status"        =>    false,
  2316.                 "error"            =>    "config_file_does_not_exist",
  2317.                 "info"            =>    $matrixFile,
  2318.             );
  2319.         }
  2320.         else
  2321.         {
  2322.             // Read its contents
  2323.             $matrix = file_get_contents($matrixFile);
  2324.             if (!$matrix)
  2325.             {
  2326.                 $log = array(
  2327.                     "status"        =>    false,
  2328.                     "error"            =>    "config_file_could_not_be_read",
  2329.                     "info"            =>    $matrixFile,
  2330.                 );
  2331.             }
  2332.             else
  2333.             {
  2334.                 // Parse it
  2335.                 $yamlparser = new \Symfony\Component\Yaml\Parser();
  2336.                 $matrix = $yamlparser->parse($matrix);
  2337.                 if (!$matrix || empty($matrix))
  2338.                 {
  2339.                     $log = array(
  2340.                         "status"        =>    false,
  2341.                         "error"            =>    "config_file_could_not_be_parsed",
  2342.                         "info"            =>    $matrixFile,
  2343.                     );
  2344.                 }
  2345.             }
  2346.         }
  2347.         if ($log !== null)
  2348.         {
  2349.             // Something went wrong
  2350.             // Get the error and translate it
  2351.             $error = $this->translator->trans($log["error"]);
  2352.             $error .= " : ";
  2353.             $error .= $log["info"];
  2354.             // Save it to the log file, if any
  2355.             if ($logFile !== null)
  2356.             {
  2357.                 error_log("\n".$error, 3, $logFile);
  2358.             }
  2359.             // Log it to global logs also
  2360.             $this->errorlog($error);
  2361.             return null;
  2362.         }
  2363.         // If we are here it means that all went well
  2364.         return $matrix;
  2365.     }
  2366.     // Plan.io Task #4105
  2367.     public function loadSocietyGroupDeleteScript(SocietyGroup $societyGroup)
  2368.     {
  2369.         $matrix = null;
  2370.         $fileName = "society_group_delete.sql";
  2371.         // Get the configuration file
  2372.         $webPath = $this->getGuardConfigDir();
  2373.         $matrixFile = $webPath . $fileName;
  2374.         if (!file_exists($matrixFile))
  2375.         {
  2376.             return array(
  2377.                 "status"        =>    false,
  2378.                 "error"            =>    "config_file_does_not_exist",
  2379.                 "info"            =>    $matrixFile,
  2380.             );
  2381.         }
  2382.         else
  2383.         {
  2384.             // Read its contents
  2385.             $matrix = file_get_contents($matrixFile);
  2386.             if (!$matrix)
  2387.             {
  2388.                 return array(
  2389.                     "status"        =>    false,
  2390.                     "error"            =>    "config_file_could_not_be_read",
  2391.                     "info"            =>    $matrixFile,
  2392.                 );
  2393.             }
  2394.             else
  2395.             {
  2396.                 // If we are here it means that all went well
  2397.                 // Replace SGID with $societyGroup->id
  2398.                 $matrix = str_replace("SGID", $societyGroup->getId(), $matrix);
  2399.                 return array(
  2400.                     "status"        =>    true,
  2401.                     "data"            =>    $matrix,
  2402.                 );
  2403.             }
  2404.         }
  2405.         return array(
  2406.             "status"        =>    false,
  2407.             "error"            =>    "unknown_error",
  2408.             "info"            =>    $matrixFile,
  2409.         );
  2410.     }
  2411.     // Types : add, deny, error, update, sync, activate, deactivate
  2412.     // add : #39c300 rgb(57,195,0)
  2413.     // deny : #ff731d rgb(255,115,29)
  2414.     // error : #da0000 rgb(218,0,0)
  2415.     // update : #af0076 rgb(175,0,118)
  2416.     // sync : #d800a6 rgb(216,0,166)
  2417.     // activate : #645caa rgb(100,92,170)
  2418.     // deactivate : #645caa rgb(160,132,202)
  2419.     public function guardLog($logFile, $msg, $type = null)
  2420.     {
  2421.         if ($logFile === null)
  2422.         {            
  2423.             return false;
  2424.         }
  2425.         $backTrace = debug_backtrace();
  2426.         // When the logging is done by the SocietyGroupGenerator
  2427.         // Allow the deny statements
  2428.         // Skip them otherwise
  2429.         $allowDeny = false;
  2430.         if (in_array($type, array("deny")))
  2431.         {
  2432.             if (isset($backTrace[2]))
  2433.             {
  2434.                 $method = $backTrace[2]['function'];
  2435.                 // $this->plooplog("method(2) = ".$method);
  2436.                 if ($method == 'createSocietyGroupWithGuard')
  2437.                 {
  2438.                     $allowDeny = true;
  2439.                 }
  2440.             }
  2441.         }
  2442.         if (in_array($type, array("deny")) && !$allowDeny)
  2443.         {
  2444.             return false;
  2445.         }
  2446.         $backTrace = debug_backtrace()[1];
  2447.         $method = $backTrace['function'];
  2448.         $class = $backTrace['class'];
  2449.         $class = str_replace("App\\Guardian\\", "", $class);
  2450.         $class = str_replace("App\\Services\\", "", $class);
  2451.         $class = str_replace("App\\Controller\\", "", $class);
  2452.         if ($type !== null)
  2453.         {
  2454.             $msg = "\n" . strtoupper($type) . " ; " . $class . " ; " . $method . " ; " . $msg;
  2455.         }
  2456.         else
  2457.         {
  2458.             $msg = "\n" . $class . " ; " . $method . " ; " . $msg;
  2459.         }
  2460.         error_log($msg, 3, $logFile);
  2461.         return true;
  2462.     }
  2463.     public function logInvoiceMissingDevisProduct(Invoice $invoice, Devis $devis, $msg)
  2464.     {
  2465.         $logFile = $this->getInvoiceMissingDevisProductsLogsDir().$invoice->getSocietyGroup()->getId().".log";
  2466.         $today = new \DateTime();
  2467.         $timestamp = $today->format("Y-m-d H:i:s");
  2468.         $invoiceId = $invoice->getId();
  2469.         $invoiceRef = $invoice->getRef();
  2470.         $devisId = $devis->getId();
  2471.         $devisRef = $devis->getRef();
  2472.         $output = "\n[$timestamp][$invoiceId][$invoiceRef][$devisId][$devisRef]$msg";
  2473.         error_log($output, 3, $logFile);
  2474.     }
  2475.     //--------------------------------------------------------------------------
  2476.     // Plan.io Task #3922
  2477.     public function camelToSnakeCase($text)
  2478.     {
  2479.         return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $text));
  2480.     }
  2481.     public function handleLoggingData($entity)
  2482.     {
  2483.         $info = null;
  2484.         $specialAuthor = null;
  2485.         $ignore = false;
  2486.         $action = null;
  2487.         if (method_exists($entity, "getLoggingData"))
  2488.         {
  2489.             $loggingData = $entity->getLoggingData();
  2490.             // $this->ploopLog("[LogTools][handleLoggingData] --------------------------------------------------");
  2491.             // $this->ploopLog("[LogTools][handleLoggingData] entity = ".$entity->displayForLog());
  2492.             // $this->ploopLog("[LogTools][handleLoggingData] loggingData = ".print_r($loggingData, true));
  2493.             if (!empty($loggingData) && is_array($loggingData))
  2494.             {
  2495.                 if (array_key_exists('action', $loggingData))
  2496.                 {
  2497.                     $action = $loggingData['action'];
  2498.                 }
  2499.                 if (array_key_exists('info', $loggingData))
  2500.                 {
  2501.                     $info = $loggingData['info'];
  2502.                 }
  2503.                 if (array_key_exists('special_author', $loggingData))
  2504.                 {
  2505.                     $specialAuthor = $loggingData['special_author'];
  2506.                 }
  2507.                 if (array_key_exists('ignore', $loggingData))
  2508.                 {
  2509.                     $ignore = boolval($loggingData['ignore']);
  2510.                 }
  2511.             }
  2512.         }
  2513.         // $this->ploopLog("[LogTools][handleLoggingData] info = ".$info);
  2514.         // if ($ignore) $this->ploopLog("[LogTools][handleLoggingData] ignore = true");
  2515.         // else $this->ploopLog("[LogTools][handleLoggingData] ignore = false");
  2516.         // $this->ploopLog("[LogTools][handleLoggingData] --------------------------------------------------");
  2517.         return array(
  2518.             'action'            => $action,
  2519.             'info'              => $info,
  2520.             'special_author'    => $specialAuthor,
  2521.             'ignore'            => $ignore,
  2522.         );
  2523.     }
  2524.     //--------------------------------------------------------------------------
  2525.     // Plan.io Task #3922
  2526.     public function getLogPathForMission(Mission $mission)
  2527.     {
  2528.         $originalLogPath = $this->getDMS_MissionLogsDir();
  2529.         $societyGroup = $mission->getSocietyGroupAuthor();
  2530.         if ($societyGroup === null)
  2531.         {
  2532.             return null;
  2533.         }
  2534.         $societyGroupId = $societyGroup->getId();
  2535.         $societyGroupRef = $societyGroup->getInternalRef();
  2536.         if (empty($societyGroupId) || empty($societyGroupRef))
  2537.         {
  2538.             return null;
  2539.         }            
  2540.         // Craft folder name
  2541.         $logFolderName = $societyGroupId."_".$societyGroupRef;
  2542.         $logPath = $originalLogPath.$logFolderName."/";
  2543.         // Craft file name
  2544.         $id = $mission->getId();
  2545.         $logFileName = "mission_log_".$id.".csv";        
  2546.         
  2547.         $logFile = $logPath.$logFileName;
  2548.         if (file_exists($logFile))
  2549.         {
  2550.             return $logFile;
  2551.         }
  2552.         return null;
  2553.     }
  2554.     // Plan.io Task #3922
  2555.     public function getLogPathForClient(Client $client)
  2556.     {
  2557.         $originalLogPath = $this->getDMS_ClientLogsDir();
  2558.         $societyGroup = $client->getSocietyGroup();
  2559.         if ($societyGroup === null)
  2560.         {
  2561.             return null;
  2562.         }
  2563.         $societyGroupId = $societyGroup->getId();
  2564.         $societyGroupRef = $societyGroup->getInternalRef();
  2565.         if (empty($societyGroupId) || empty($societyGroupRef))
  2566.         {
  2567.             return null;
  2568.         }            
  2569.         // Craft folder name
  2570.         $logFolderName = $societyGroupId."_".$societyGroupRef;
  2571.         $logPath = $originalLogPath.$logFolderName."/";
  2572.         // Craft file name
  2573.         $id = $client->getId();
  2574.         $logFileName = "client_log_".$id.".csv";        
  2575.         
  2576.         $logFile = $logPath.$logFileName;
  2577.         if (file_exists($logFile))
  2578.         {
  2579.             return $logFile;
  2580.         }
  2581.         return null;
  2582.     }
  2583.     /**
  2584.      * Returns the path to the CSV log file for the given human resource, or null if not found.
  2585.      * Path pattern: DMS/Logs/HumanResource/{societyGroupId}_{societyGroupRef}/human_resource_log_{id}.csv
  2586.      */
  2587.     public function getLogPathForHumanResource(HumanResource $humanResource)
  2588.     {
  2589.         $originalLogPath = $this->getDMS_HumanResourceLogsDir();
  2590.         $societyGroup = $humanResource->getSocietyGroup();
  2591.         if ($societyGroup === null)
  2592.         {
  2593.             return null;
  2594.         }
  2595.         $societyGroupId = $societyGroup->getId();
  2596.         $societyGroupRef = $societyGroup->getInternalRef();
  2597.         if (empty($societyGroupId) || empty($societyGroupRef))
  2598.         {
  2599.             return null;
  2600.         }
  2601.         $logFolderName = $societyGroupId."_".$societyGroupRef;
  2602.         $logPath = $originalLogPath.$logFolderName."/";
  2603.         $id = $humanResource->getId();
  2604.         $logFileName = "human_resource_log_".$id.".csv";
  2605.         $logFile = $logPath.$logFileName;
  2606.         if (file_exists($logFile))
  2607.         {
  2608.             return $logFile;
  2609.         }
  2610.         return null;
  2611.     }
  2612.     // Plan.io Task #4624
  2613.     public function getLogPathForProjectNotebook(ProjectNotebook $notebook)
  2614.     {
  2615.         $originalLogPath = $this->getDMS_ProjectManagerNotebookLogsDir();
  2616.         $societyGroup = $notebook->getSocietyGroup();
  2617.         if ($societyGroup === null)
  2618.         {
  2619.             return null;
  2620.         }
  2621.         $societyGroupId = $societyGroup->getId();
  2622.         $societyGroupRef = $societyGroup->getInternalRef();
  2623.         if (empty($societyGroupId) || empty($societyGroupRef))
  2624.         {
  2625.             return null;
  2626.         }            
  2627.         // Craft folder name
  2628.         $logFolderName = $societyGroupId."_".$societyGroupRef;
  2629.         $logPath = $originalLogPath.$logFolderName."/";
  2630.         // Craft file name
  2631.         $id = $notebook->getId();
  2632.         $logFileName = "project_notebook_log_".$id.".csv";            
  2633.         
  2634.         $logFile = $logPath.$logFileName;
  2635.         if (file_exists($logFile))
  2636.         {
  2637.             return $logFile;
  2638.         }
  2639.         return null;
  2640.     }
  2641.     
  2642.     /**
  2643.      * Reads the logging CSV produced by Logging::logToFile() into an array of LogCsvRecord DTOs.
  2644.      * File format: semicolon-delimited, first line may be blank, then header row, then data rows.
  2645.      *
  2646.      * #3922 : This was generated by Cursor => Review and Clean
  2647.      * 
  2648.      * @return LogCsvRecord[]
  2649.      */
  2650.     public function readLoggingCsv(string $filePath): array
  2651.     {
  2652.         // Ensure the file exists and is readable before attempting to load it
  2653.         if (!is_readable($filePath))
  2654.         {
  2655.             return [];
  2656.         }
  2657.         // Load the entire file content (CSV may be written with mixed line endings by error_log)
  2658.         $raw = file_get_contents($filePath);
  2659.         if ($raw === false)
  2660.         {
  2661.             return [];
  2662.         }
  2663.         // Split into lines using any common newline sequence (Unix, Windows, legacy Mac)
  2664.         $allLines = preg_split('/\r\n|\r|\n/', $raw);
  2665.         if ($allLines === false)
  2666.         {
  2667.             return [];
  2668.         }
  2669.         // Drop empty lines so the first non-empty line is the header (Logging writes a leading newline)
  2670.         $nonEmptyLines = array_values(array_filter($allLines, static function (string $line): bool
  2671.         {
  2672.             return trim($line) !== '';
  2673.         }));
  2674.         if (count($nonEmptyLines) < 2)
  2675.         {
  2676.             return [];
  2677.         }
  2678.         // First non-empty line is the semicolon-separated header row; normalize and remove empty cells
  2679.         $headerLine = $nonEmptyLines[0];
  2680.         $headers = array_values(array_filter(array_map('trim', explode(';', $headerLine)), static function (string $h): bool {
  2681.             return $h !== '';
  2682.         }));
  2683.         // Parse each remaining line as a data row and map to a LogCsvRecord DTO
  2684.         $records = [];
  2685.         for ($i = 1, $n = count($nonEmptyLines); $i < $n; $i++)
  2686.         {
  2687.             $values = explode(';', $nonEmptyLines[$i]);
  2688.             $records[] = LogCsvRecord::fromRow($headers, $values);
  2689.         }
  2690.         return $records;
  2691.     }
  2692.     /**
  2693.      * Streams the logging CSV produced by Logging::logToFile() as LogCsvRecord DTOs.
  2694.      * Use this for large files (e.g. tens of MB) to avoid loading the whole file into memory.
  2695.      * File format: semicolon-delimited, first line may be blank, then header row, then data rows.
  2696.      * 
  2697.      * #3922 : This was generated by Cursor => Review and Clean
  2698.      * Working, but still too much data to output
  2699.      * DOM loading is extremly slow ... and becomes not responsive
  2700.      *
  2701.      * @return \Generator<int, LogCsvRecord, void, void>
  2702.      *
  2703.      * Example:
  2704.      *   foreach ($logTools->readLoggingCsvXXL('/path/to/file.csv') as $record) {
  2705.      *       // process $record
  2706.      *   }
  2707.      */
  2708.     public function readLoggingCsvXXL(string $filePath): \Generator
  2709.     {
  2710.         // Ensure the file exists and is readable before opening
  2711.         if (!is_readable($filePath))
  2712.         {
  2713.             return;
  2714.         }
  2715.         $handle = fopen($filePath, 'rb');
  2716.         if ($handle === false)
  2717.         {
  2718.             return;
  2719.         }
  2720.         try
  2721.         {
  2722.             // Read line by line (fgets stops at \n; \r\n or \r are included and trimmed below)
  2723.             // so only one line is in memory at a time.
  2724.             $headers = null;
  2725.             while (($line = fgets($handle)) !== false)
  2726.             {
  2727.                 $line = trim($line);
  2728.                 if ($line === '')
  2729.                 {
  2730.                     continue;
  2731.                 }
  2732.                 // First non-empty line is the header; parse and store for mapping data rows
  2733.                 if ($headers === null)
  2734.                 {
  2735.                     $headers = array_values(array_filter(
  2736.                         array_map('trim', explode(';', $line)),
  2737.                         static function (string $h): bool {
  2738.                             return $h !== '';
  2739.                         }
  2740.                     ));
  2741.                     continue;
  2742.                 }
  2743.                 // Subsequent non-empty lines are data rows: parse and yield one record
  2744.                 $values = explode(';', $line);
  2745.                 yield LogCsvRecord::fromRow($headers, $values);
  2746.             }
  2747.         }
  2748.         finally
  2749.         {
  2750.             // Always close the handle when the generator is exhausted or the consumer stops iterating
  2751.             fclose($handle);
  2752.         }
  2753.     }
  2754. }