Lines 94.44% 68 / 72
Methods 66.66% 6 / 9
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 processConfirm 91.30% 21 / 23 0.00% 0 / 1 10.07
 extractClientData 100.00% 15 / 15 100.00% 1 / 1 11
 isRebookingConfirm 100.00% 2 / 2 100.00% 1 / 1 2
 validateClientData 100.00% 4 / 4 100.00% 1 / 1 3
 loadSourceProcessForRebooking 100.00% 7 / 7 100.00% 1 / 1 2
 getReservedProcess 92.30% 12 / 13 0.00% 0 / 1 3.00
 confirmProcess 80.00% 4 / 5 0.00% 0 / 1 3.07
 sendConfirmationEmail 100.00% 2 / 2 100.00% 1 / 1 1
14class AppointmentConfirmService
15{
16    private CaptchaService $captchaService;
17
18    public function __construct()
19    {
20        $this->captchaService = new CaptchaService();
21    }
22
23    public function processConfirm(array $body, ?AuthenticatedUser $authenticatedUser): ThinnedProcess|array
24    {
25        $clientData = $this->extractClientData($body);
26        $errors = $this->validateClientData($clientData);
27        if (!empty($errors['errors'])) {
28            return $errors;
29        }
30
31        $sourceProcess = $this->loadSourceProcessForRebooking($clientData);
32        if (is_array($sourceProcess) && ($sourceProcess['errors'] ?? []) !== []) {
33            return $sourceProcess;
34        }
35
36        $reservedProcess = $this->getReservedProcess(
37            $clientData->processId,
38            $clientData->authKey,
39            $authenticatedUser,
40            $sourceProcess instanceof ThinnedProcess ? $sourceProcess : null
41        );
42        if (is_array($reservedProcess) && !empty($reservedProcess['errors'])) {
43            return $reservedProcess;
44        }
45
46        // Todo: check if the email template confirmed exists for the scope before submitting and sending
47        $result = $this->confirmProcess($reservedProcess);
48        if (is_array($result) && !empty($result['errors'])) {
49            return $result;
50        }
51
52        $token = $this->captchaService->generateToken();
53        $result->setCaptchaToken($token);
54
55        if ($result->status === 'confirmed') {
56            $this->sendConfirmationEmail($result);
57        }
58
59        return $result;
60    }
61
62
63    private function extractClientData(array $body): object
64    {
65        return (object) [
66            'processId' => isset($body['processId']) && is_numeric($body['processId'])
67                ? (int) $body['processId']
68                : null,
69            'authKey' => isset($body['authKey']) && is_string($body['authKey']) && trim($body['authKey']) !== ''
70                ? htmlspecialchars(trim($body['authKey']), ENT_QUOTES, 'UTF-8')
71                : null,
72            'sourceProcessId' => isset($body['sourceProcessId']) && is_numeric($body['sourceProcessId'])
73                ? (int) $body['sourceProcessId']
74                : null,
75            'sourceAuthKey' => isset($body['sourceAuthKey']) && is_string($body['sourceAuthKey'])
76                && trim($body['sourceAuthKey']) !== ''
77                ? htmlspecialchars(trim($body['sourceAuthKey']), ENT_QUOTES, 'UTF-8')
78                : null,
79        ];
80    }
81
82    private function isRebookingConfirm(object $data): bool
83    {
84        return ($data->sourceProcessId ?? null) !== null
85            && ($data->sourceAuthKey ?? null) !== null;
86    }
87
88    private function validateClientData(object $data): array
89    {
90        $errors = ValidationService::validateGetProcessById($data->processId, $data->authKey);
91        if ($errors['errors'] !== [] || !$this->isRebookingConfirm($data)) {
92            return $errors;
93        }
94
95        return ValidationService::validateGetProcessById($data->sourceProcessId, $data->sourceAuthKey);
96    }
97
98    /**
99     * @return ThinnedProcess|array{errors: array}|null
100     */
101    private function loadSourceProcessForRebooking(object $clientData): ThinnedProcess|array|null
102    {
103        if (!$this->isRebookingConfirm($clientData)) {
104            return null;
105        }
106
107        return ZmsApiFacadeService::getThinnedProcessById(
108            $clientData->sourceProcessId,
109            $clientData->sourceAuthKey,
110            null
111        );
112    }
113
114    private function getReservedProcess(
115        int $processId,
116        ?string $authKey,
117        ?AuthenticatedUser $user,
118        ?ThinnedProcess $sourceProcess
119    ): ThinnedProcess|array {
120        $process = ZmsApiFacadeService::getProcessById($processId, $authKey, $user);
121        $notFound = ValidationService::validateGetProcessNotFound($process);
122        if (!empty($notFound['errors'])) {
123            return $notFound;
124        }
125
126        $thinned = MapperService::processToThinnedProcess($process);
127        $confirmErrors = ValidationService::validateAppointmentConfirm(
128            $thinned,
129            $process->getExternalUserId(),
130            $sourceProcess
131        );
132        if ($confirmErrors['errors'] !== []) {
133            return $confirmErrors;
134        }
135
136        return $thinned;
137    }
138
139    private function confirmProcess(ThinnedProcess $process): ThinnedProcess|array
140    {
141        $processEntity = MapperService::thinnedProcessToProcess($process);
142        $result = ZmsApiFacadeService::confirmAppointment($processEntity);
143        if (is_array($result) && !empty($result['errors'])) {
144            return $result;
145        }
146
147        return MapperService::processToThinnedProcess($result);
148    }
149
150    private function sendConfirmationEmail(ThinnedProcess $process): void
151    {
152        $processEntity = MapperService::thinnedProcessToProcess($process);
153        ZmsApiFacadeService::sendConfirmationEmail($processEntity);
154    }
155}