<?php
namespace App\Controller\APIRest\SynerticMobileApp;
// Core app
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
// Services
use App\Services\APIRest\Tools\APIResponseTools;
use App\Services\APIRest\SynerticMobileApp\SynerticMobileAppTools;
use App\Services\APIRest\SynerticMobileApp\ServiceOrderTools;
use App\Services\Log\LogMobileAppTools;
use App\Services\LogTools;
use App\Services\Planning\TaskTools;
use App\Utils\APIError;
// Exceptions
use Exception;
use App\Entity\APIRest\AccessAPI;
use App\Entity\Planning\Task;
use App\Entity\Security\Acl;
use App\Enum\Log\LogLevel;
use App\Exception\APIRest\APIAuthenticationException;
use App\Exception\APIRest\APIException;
use App\Exception\APIRest\APIInvalidDataException;
use App\Exception\APIRest\APIMissingDataException;
use App\Services\APIRest\Tools\GhostingTools;
use App\Services\Security\AclTools;
use App\Services\Config\OptionConfigTools;
class SynerticMobileAppController extends AbstractController
{
public function __construct(
ManagerRegistry $doctrine,
LogTools $logTools,
LogMobileAppTools $logMobileAppTools,
APIResponseTools $apiResponseTools,
SynerticMobileAppTools $synerticMobileAppTools,
ServiceOrderTools $serviceOrderTools,
GhostingTools $ghostingTools,
TaskTools $taskTools,
AclTools $aclTools,
OptionConfigTools $optionConfigTools
)
{
$this->em = $doctrine->getManager();
$this->logTools = $logTools;
$this->logMobileAppTools = $logMobileAppTools;
$this->apiResponseTools = $apiResponseTools;
$this->synerticMobileAppTools = $synerticMobileAppTools;
$this->serviceOrderTools = $serviceOrderTools;
$this->ghostingTools = $ghostingTools;
$this->taskTools = $taskTools;
$this->aclTools = $aclTools;
$this->optionConfigTools = $optionConfigTools;
}
public function addFCMToken(Request $request): JsonResponse
{
try
{
$requestedData = json_decode($request->getContent(), true);
$userAppContext = $this->logMobileAppTools->buildUserAppContext($requestedData);
$this->logMobileAppTools->logApiCall(
$request->getMethod(),
$request->getPathInfo(),
$userAppContext
);
if($this->getUser() instanceof AccessAPI)
{
$accessAPI = $this->getUser();
}
else
{
$this->logMobileAppTools->error(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS), $userAppContext);
throw new APIAuthenticationException(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS));
}
if (empty($requestedData))
{
$this->logMobileAppTools->error(new APIError(APIError::INVALID_DATA), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
if(!array_key_exists("fcm_token", $requestedData))
{
$this->logMobileAppTools->error(new APIError(APIError::SYNERTIC_MISSING_FCM_TOKEN), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::SYNERTIC_MISSING_FCM_TOKEN));
}
if($requestedData["fcm_token"] === null)
{
$this->logMobileAppTools->error(new APIError(APIError::SYNERTIC_INVALID_FCM_TOKEN), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::SYNERTIC_INVALID_FCM_TOKEN));
}
// Can't verify that fcm token is legit, must trust api call
$accessAPI->setFcmToken($requestedData['fcm_token']);
$this->em->persist($accessAPI);
try
{
$this->em->flush();
}
catch (\Exception $e)
{
$this->logMobileAppTools->critical('Database flush error', $e, $userAppContext);
$this->logMobileAppTools->error(new APIError(APIError::SYNERTIC_ERROR_SETTING_FCM_TOKEN), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::SYNERTIC_ERROR_SETTING_FCM_TOKEN));
}
return $this->apiResponseTools->successResponse(array("status" => "success"));
}
catch (Exception $e)
{
if (!$e instanceof APIException)
{
$this->logMobileAppTools->critical('', $e, $userAppContext ?? []);
$e = new APIException(new APIError(APIError::UNEXPECTED_ERROR));
}
$this->logMobileAppTools->error($e->getApiError(), $userAppContext ?? []);
return $this->apiResponseTools->exceptionResponse($e);
}
}
public function getFileBase64(Request $request): JsonResponse
{
try
{
$requestedData = json_decode($request->getContent(), true);
$userAppContext = $this->logMobileAppTools->buildUserAppContext($requestedData);
$this->logMobileAppTools->logApiCall(
$request->getMethod(),
$request->getPathInfo(),
$userAppContext
);
if($this->getUser() instanceof AccessAPI)
{
$accessAPI = $this->ghostingTools->resolveGhostedUser($requestedData, $this->getUser());
}
else
{
$this->logMobileAppTools->error(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS), $userAppContext);
throw new APIAuthenticationException(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS));
}
if (empty($requestedData))
{
$this->logMobileAppTools->error(new APIError(APIError::INVALID_DATA), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
if (!array_key_exists('type', $requestedData))
{
$this->logMobileAppTools->error(new APIError(APIError::SYNERTIC_FILE_BASE64_MISSING_TYPE), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::SYNERTIC_FILE_BASE64_MISSING_TYPE));
}
if($requestedData['type'] === null)
{
$this->logMobileAppTools->error(new APIError(APIError::SYNERTIC_FILE_BASE64_INVALID_TYPE), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::SYNERTIC_FILE_BASE64_INVALID_TYPE));
}
$type = '';
if (!empty($requestedData['type']))
{
if (in_array($requestedData['type'], ['attachment', 'devis', 'document', 'invoice', 'credit']))
{
$type = $requestedData['type'];
}
else
{
$this->logMobileAppTools->error(new APIError(APIError::SYNERTIC_FILE_BASE64_INVALID_TYPE), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::SYNERTIC_FILE_BASE64_INVALID_TYPE));
}
}
if ($type == 'devis')
{
$devisFileBase64Data = $this->synerticMobileAppTools->getDevisFileBase64($requestedData);
return $this->apiResponseTools->successResponse($devisFileBase64Data);
}
if ($type == 'document')
{
$documentFileBase64Data = $this->synerticMobileAppTools->getDocumentFileBase64($requestedData);
return $this->apiResponseTools->successResponse($documentFileBase64Data);
}
if ($type == 'invoice' || $type == 'credit')
{
$invoiceFileBase64Data = $this->synerticMobileAppTools->getInvoiceFileBase64($requestedData);
return $this->apiResponseTools->successResponse($invoiceFileBase64Data);
}
if ($type == 'attachment')
{
$attachmentFileBase64Data = $this->synerticMobileAppTools->getAttachmentFileBase64($requestedData);
return $this->apiResponseTools->successResponse($attachmentFileBase64Data);
}
$this->logMobileAppTools->error(new APIError(APIError::INVALID_DATA), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
catch (Exception $e)
{
if (!$e instanceof APIException)
{
$this->logMobileAppTools->critical('', $e, $userAppContext ?? []);
$e = new APIException(new APIError(APIError::UNEXPECTED_ERROR));
}
$this->logMobileAppTools->error($e->getApiError(), $userAppContext ?? []);
return $this->apiResponseTools->exceptionResponse($e);
}
}
public function setServiceOrderStatus(Request $request): JsonResponse
{
try
{
$requestedData = json_decode($request->getContent(), true);
$userAppContext = $this->logMobileAppTools->buildUserAppContext($requestedData);
$this->logMobileAppTools->logApiCall(
$request->getMethod(),
$request->getPathInfo(),
$userAppContext
);
if($this->getUser() instanceof AccessAPI)
{
$accessAPI = $this->ghostingTools->resolveGhostedUser($requestedData, $this->getUser());
}
else
{
$this->logMobileAppTools->error(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS), $userAppContext);
throw new APIAuthenticationException(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS));
}
$statusChangeData = $this->serviceOrderTools->setServiceOrderStatus($requestedData);
return $this->apiResponseTools->successResponse($statusChangeData);
}
catch (Exception $e)
{
if (!$e instanceof APIException)
{
$this->logMobileAppTools->critical('', $e, $userAppContext ?? []);
$e = new APIException(new APIError(APIError::UNEXPECTED_ERROR));
}
$this->logMobileAppTools->error($e->getApiError(), $userAppContext ?? []);
return $this->apiResponseTools->exceptionResponse($e);
}
}
public function checkAppUpdate(Request $request): JsonResponse
{
try
{
$requestedData = json_decode($request->getContent(), true);
$userAppContext = $this->logMobileAppTools->buildUserAppContext($requestedData);
$this->logMobileAppTools->logApiCall(
$request->getMethod(),
$request->getPathInfo(),
$userAppContext
);
if($this->getUser() instanceof AccessAPI)
{
$accessAPI = $this->ghostingTools->resolveGhostedUser($requestedData, $this->getUser());
}
else
{
$this->logMobileAppTools->error(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS), $userAppContext);
throw new APIAuthenticationException(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS));
}
if (empty($requestedData))
{
$this->logMobileAppTools->error(new APIError(APIError::INVALID_DATA), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
$checkUpdate = $this->synerticMobileAppTools->checkAppUpdate($requestedData);
return $this->apiResponseTools->successResponse($checkUpdate);
}
catch (Exception $e)
{
if (!$e instanceof APIException)
{
$this->logMobileAppTools->critical('', $e, $userAppContext ?? []);
$e = new APIException(new APIError(APIError::UNEXPECTED_ERROR));
}
$this->logMobileAppTools->error($e->getApiError(), $userAppContext ?? []);
return $this->apiResponseTools->exceptionResponse($e);
}
}
public function addLog(Request $request): JsonResponse
{
try
{
$requestedData = json_decode($request->getContent(), true);
$userAppContext = $this->logMobileAppTools->buildUserAppContext($requestedData);
$this->logMobileAppTools->logApiCall(
$request->getMethod(),
$request->getPathInfo(),
$userAppContext
);
if($this->getUser() instanceof AccessAPI)
{
$accessAPI = $this->ghostingTools->resolveGhostedUser($requestedData, $this->getUser());
}
else
{
$this->logMobileAppTools->error(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS), $userAppContext);
throw new APIAuthenticationException(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS));
}
if (empty($requestedData))
{
$this->logMobileAppTools->error(new APIError(APIError::INVALID_DATA), $userAppContext);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
// Check if code is IKEA_CALL
if (isset($requestedData['code']) && $requestedData['code'] === 'IKEA_CALL')
{
// Extract taskId from various possible locations in request data
// The mobile app may send taskId in different formats:
// 1. Direct: $requestedData['taskId']
// 2. JSON string in message: $requestedData['message'] (decoded)
// 3. Nested arrays: any array value containing 'taskId'
// 4. JSON string in any value: any string value that decodes to array with 'taskId'
$taskId = null;
// First, check if taskId is directly in requestedData
if (isset($requestedData['taskId']))
{
$taskId = $requestedData['taskId'];
}
// Otherwise, try to parse message as JSON
elseif (isset($requestedData['message']))
{
$messageData = json_decode($requestedData['message'], true);
if ($messageData !== null && isset($messageData['taskId']))
{
$taskId = $messageData['taskId'];
}
}
// Search recursively in all request data for taskId
if ($taskId === null)
{
foreach ($requestedData as $key => $value)
{
if (is_array($value) && isset($value['taskId']))
{
$taskId = $value['taskId'];
break;
}
elseif (is_string($value))
{
// Try to decode as JSON if it's a string
$decoded = json_decode($value, true);
if (is_array($decoded) && isset($decoded['taskId']))
{
$taskId = $decoded['taskId'];
break;
}
}
}
}
// Debug logging for IKEA hotline integration issues
// Log full request structure when taskId extraction fails for debugging
if ($taskId === null)
{
$this->logMobileAppTools->debug(
'IKEA_CALL - requestedData keys: ' . implode(', ', array_keys($requestedData)) . ' | Full data: ' . json_encode($requestedData),
$userAppContext
);
// Legacy addLog call for compatibility with existing monitoring
$addLog = $this->synerticMobileAppTools->addLog($requestedData);
$this->logMobileAppTools->error(
new APIError(APIError::INVALID_DATA, 'IKEA_CALL log missing taskId in request data or message JSON'),
$userAppContext
);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
$task = $this->em->getRepository(Task::class)->find($taskId);
if ($task === null)
{
$this->logMobileAppTools->error(
new APIError(APIError::INVALID_DATA, "IKEA_CALL log: Task with id $taskId not found"),
$userAppContext
);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
$access = $accessAPI->getAccess();
if ($access === null)
{
$this->logMobileAppTools->error(
new APIError(APIError::INVALID_DATA, 'IKEA_CALL log: AccessAPI has no associated Access'),
$userAppContext
);
throw new APIInvalidDataException(new APIError(APIError::INVALID_DATA));
}
$this->taskTools->logIkeaHotlineClick($task, $access, 'mobile_app');
return $this->apiResponseTools->successResponse(array("status" => "success"));
}
// Default behavior for other log codes
$addLog = $this->synerticMobileAppTools->addLog($requestedData);
return $this->apiResponseTools->successResponse($addLog);
}
catch (Exception $e)
{
if (!$e instanceof APIException)
{
$this->logMobileAppTools->critical('', $e, $userAppContext ?? []);
$e = new APIException(new APIError(APIError::UNEXPECTED_ERROR));
}
$this->logMobileAppTools->error($e->getApiError(), $userAppContext ?? []);
return $this->apiResponseTools->exceptionResponse($e);
}
}
/**
* Allow mobile app to send logs to the server.
*/
public function sendLog(Request $request): JsonResponse
{
try
{
$requestedData = json_decode($request->getContent(), true);
$userAppContext = $this->logMobileAppTools->buildUserAppContext($requestedData);
$this->logMobileAppTools->logApiCall(
$request->getMethod(),
$request->getPathInfo(),
$userAppContext
);
if (!$this->getUser() instanceof AccessAPI)
{
$this->logMobileAppTools->error(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS), $userAppContext);
throw new APIAuthenticationException(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS));
}
$accessAPI = $this->ghostingTools->resolveGhostedUser($requestedData, $this->getUser());
// Validate required fields
if (empty($requestedData['message']))
{
$this->logMobileAppTools->error(new APIError(APIError::MISSING_INFO_DATA), $userAppContext);
throw new APIMissingDataException(new APIError(APIError::MISSING_INFO_DATA));
}
$message = $requestedData['message'];
$levelString = $requestedData['level'] ?? 'INFO';
// Parse log level
$level = LogLevel::fromString($levelString);
// Log based on level
$this->logMobileAppTools->log($level, $message, $userAppContext);
return $this->apiResponseTools->successResponse(['logged' => true]);
}
catch (Exception $e)
{
if (!$e instanceof APIException)
{
$this->logMobileAppTools->critical('', $e, $userAppContext ?? []);
$e = new APIException(new APIError(APIError::UNEXPECTED_ERROR));
}
$this->logMobileAppTools->error($e->getApiError(), $userAppContext ?? []);
return $this->apiResponseTools->exceptionResponse($e);
}
}
/**
* Allow mobile app to get the permissions for the current access.
* The permissions are returned as an array of permission ids.
*/
public function acl(Request $request): JsonResponse
{
try
{
$requestedData = json_decode($request->getContent(), true) ?? [];
$userAppContext = $this->logMobileAppTools->buildUserAppContext($requestedData);
$this->logMobileAppTools->logApiCall(
$request->getMethod(),
$request->getPathInfo(),
$userAppContext
);
if ($this->getUser() instanceof AccessAPI)
{
$accessAPI = $this->ghostingTools->resolveGhostedUser($requestedData, $this->getUser());
}
else
{
$this->logMobileAppTools->error(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS), $userAppContext);
throw new APIAuthenticationException(new APIError(APIError::AUTHENTICATION_UNAUTHORIZED_ACCESS));
}
$currentAccess = $accessAPI->getAccess();
$currentGroup = $this->getUser()->getSocietyGroup();
// gets fonction for currentAccess
$fonctionCurrentAccess = $currentAccess->getFunction();
// gets acl for currentGroup
$acls = $this->em->getRepository(Acl::class)->getForSocietyGroup($currentGroup);
$setPermissions = array();
// make table of permissions for currentAccess
$aclPermissions = [];
foreach ($acls as $key => $acl)
{
if ($acl->getValue() == true && $acl->getFunction()->getId() == $fonctionCurrentAccess->getId())
{
$aclPermissions[] .= $acl->getPermission()->getId();
// $setPermissions[$acl->getFunction()->getId()][$acl->getPermission()->getId()] = 1;
}
}
$options = [];
if($this->optionConfigTools->isActive_MobileAppArtisan($currentGroup))
{
$options[] = "32";
}
$setPermissions = [
'acl' => $aclPermissions,
'options' => $options,
];
return $this->apiResponseTools->successResponse($setPermissions);
}
catch (Exception $e)
{
if (!$e instanceof APIException)
{
$this->logMobileAppTools->critical('', $e, $userAppContext ?? []);
$e = new APIException(new APIError(APIError::UNEXPECTED_ERROR));
}
$this->logMobileAppTools->error($e->getApiError(), $userAppContext ?? []);
return $this->apiResponseTools->exceptionResponse($e);
}
}
}