Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 153
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProviderContextService
0.00% covered (danger)
0.00%
0 / 153
0.00% covered (danger)
0.00%
0 / 10
1122
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getCredentials
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 saveCredentials
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
90
 getEnvironment
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 saveEnvironment
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 testCredentials
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
72
 testEnvironment
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getUiContext
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 resolve
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 defaultEnvironment
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Modules\ProviderModule\Services;
4
5use App\Modules\ProviderModule\Models\ProviderCredentialModel;
6use App\Modules\ProviderModule\Models\ProviderEnvironmentModel;
7use App\Modules\ProviderModule\Services\CredentialService;
8use App\Modules\ProviderModule\Services\ProviderService;
9
10class ProviderContextService
11{
12    protected ProviderCredentialModel  $credentialModel;
13    protected ProviderEnvironmentModel $environmentModel;
14    protected CredentialService        $crypto;
15    protected ProviderService          $providerService;
16
17    public function __construct()
18    {
19        $this->credentialModel  = model('ProviderCredentialModel');
20        $this->environmentModel = model('ProviderEnvironmentModel');
21        $this->crypto           = new CredentialService();
22        $this->providerService  = service('ProviderService');
23    }
24
25    // ================================================================
26    // CREDENTIALS — GET
27    // ================================================================
28
29    public function getCredentials(int $providerId, string $country): array
30    {
31        $row = $this->credentialModel->where([
32            'channel_provider_id' => $providerId,
33            'country_code'        => $country,
34            'is_active'           => 1,
35        ])->first();
36
37        if (!$row || empty($row->credentials_json)) {
38            return [];
39        }
40
41        $decrypted = $this->crypto->decrypt($row->credentials_json);
42
43        log_message('debug', "[ProviderContextService::getCredentials] provider={$providerId}"
44            . ' | keys=' . implode(',', array_keys($decrypted))
45            . ' | access_token_prefix=' . substr($decrypted['access_token'] ?? '', 0, 20));
46
47        return $decrypted;
48    }
49
50    // ================================================================
51    // CREDENTIALS — SAVE (UPSERT avec merge défensif)
52    // ================================================================
53
54    /**
55     * @param int    $providerId
56     * @param string $country
57     * @param array  $credentials  Champs en clair issus du formulaire
58     * @param array  $environment  Champs runtime (mode, test_number, timeout_ms)
59     */
60    public function saveCredentials(
61        int    $providerId,
62        string $country,
63        array  $credentials,
64        array  $environment = []   // ← array, plus string
65    ): bool {
66        if (empty($credentials)) {
67            log_message('error', "[ProviderContextService::saveCredentials] credentials vides — provider={$providerId}");
68            return false;
69        }
70
71        // ── Récupération du channel depuis la DB ──────────────────
72        $provider = model('ChannelProviderModel')->find($providerId);
73        $channel  = $provider?->channel ?? '';
74
75        // ── Merge défensif : on ne perd pas les champs non soumis ─
76        $existing = $this->credentialModel->where([
77            'channel_provider_id' => $providerId,
78            'country_code'        => $country,
79        ])->first();
80
81        if ($existing && !empty($existing->credentials_json)) {
82            $existingDecrypted = $this->crypto->decrypt($existing->credentials_json);
83            if (is_array($existingDecrypted) && !empty($existingDecrypted)) {
84                // Les nouvelles valeurs écrasent champ par champ, les absentes sont conservées
85                $credentials = array_merge($existingDecrypted, $credentials);
86            }
87        }
88
89        log_message('debug', "[ProviderContextService::saveCredentials] provider={$providerId}"
90            . ' | keys=' . implode(',', array_keys($credentials))
91            . ' | access_token_prefix=' . substr($credentials['access_token'] ?? '', 0, 20));
92
93        $db = db_connect();
94        $db->transStart();
95
96        try {
97            $encrypted = $this->crypto->encrypt($credentials);
98
99            $payload = [
100                'channel_provider_id' => $providerId,
101                'country_code'        => $country,
102                'channel'             => $channel,
103                'credentials_json'    => $encrypted,
104                'is_active'           => 1,
105            ];
106
107            if ($existing) {
108                $this->credentialModel->update($existing->id, $payload);
109            } else {
110                $this->credentialModel->insert($payload);
111            }
112
113            $db->transComplete();
114
115            // ── Sauvegarde de l'environment si fourni ────────────
116            if (!empty($environment)) {
117                $this->saveEnvironment($providerId, $country, $environment);
118            }
119
120            return $db->transStatus();
121
122        } catch (\Throwable $e) {
123            $db->transRollback();
124            log_message('error', '[ProviderContextService::saveCredentials] ' . $e->getMessage());
125            return false;
126        }
127    }
128
129    // ================================================================
130    // ENVIRONMENT — GET
131    // ================================================================
132
133    public function getEnvironment(int $providerId, string $country): array
134    {
135        $env = $this->environmentModel->getActive($providerId, $country);
136
137        if (!$env) {
138            return $this->defaultEnvironment();
139        }
140
141        return [
142            'mode'        => $env->mode        ?? 'sandbox',
143            'test_number' => $env->test_number ?? null,
144            'test_email'  => $env->test_email  ?? null,
145            'timeout_ms'  => $env->timeout_ms  ?? 30000,
146        ];
147    }
148
149    // ================================================================
150    // ENVIRONMENT — SAVE (UPSERT)
151    // ================================================================
152
153    public function saveEnvironment(int $providerId, string $country, array $environment): bool
154    {
155        $existing = $this->environmentModel->where([
156            'channel_provider_id' => $providerId,
157            'country_code'        => $country,
158        ])->first();
159
160        $payload = [
161            'channel_provider_id' => $providerId,
162            'country_code'        => $country,
163            'mode'                => $environment['mode']        ?? 'sandbox',
164            'test_number'         => $environment['test_number'] ?? null,
165            'test_email'          => $environment['test_email']  ?? null,
166            'timeout_ms'          => (int) ($environment['timeout_ms'] ?? 30000),
167            'is_active'           => 1,
168        ];
169
170        if ($existing) {
171            return (bool) $this->environmentModel->update($existing->id, $payload);
172        }
173
174        return (bool) $this->environmentModel->insert($payload);
175    }
176
177    // ================================================================
178    // TEST CREDENTIALS
179    // ================================================================
180
181    public function testCredentials(int $providerId, array $credentials, array $environment = []): array
182    {
183        $start = microtime(true);
184
185        $provider = model('ChannelProviderModel')->find($providerId);
186
187        if (!$provider) {
188            return ['status' => 'error', 'message' => 'Provider introuvable'];
189        }
190
191        $env = [
192            'mode'        => $environment['mode']       ?? 'sandbox',
193            'test_number' => $environment['test_number'] ?? null,
194            'test_email'  => $environment['test_email']  ?? null,
195            'timeout_ms'  => (int) ($environment['timeout_ms'] ?? 30000),
196        ];
197
198        $env['mode'] = in_array($env['mode'], ['sandbox', 'production'], true)
199            ? $env['mode']
200            : 'sandbox';
201
202        $code = $provider->code ?? '';
203
204        $requiresTestEmail  = in_array($code, ['smtp', 'sendgrid', 'mailgun', 'ses', 'infobip'], true);
205        $requiresTestNumber = str_contains($code, 'sms');
206
207        if ($requiresTestEmail && empty($env['test_email'])) {
208            return ['status' => 'error', 'message' => 'test_email requis pour ce provider email'];
209        }
210
211        if ($requiresTestNumber && empty($env['test_number'])) {
212            return ['status' => 'error', 'message' => 'test_number requis pour ce provider SMS'];
213        }
214
215        log_message('debug', "[ProviderContextService::testCredentials] provider={$providerId} code={$code}"
216            . ' | access_token_prefix=' . substr($credentials['access_token'] ?? '', 0, 20));
217
218        try {
219            $result = $this->providerService->testCredentials($providerId, $credentials, $env);
220
221            return [
222                'status'     => $result['status']  ?? 'error',
223                'message'    => $result['message']  ?? 'Erreur inconnue',
224                'latency_ms' => round((microtime(true) - $start) * 1000),
225            ];
226
227        } catch (\Throwable $e) {
228            return ['status' => 'error', 'message' => $e->getMessage()];
229        }
230    }
231
232    // ================================================================
233    // TEST ENVIRONMENT
234    // ================================================================
235
236    public function testEnvironment(int $providerId, string $country): array
237    {
238        $env = $this->environmentModel->getActive($providerId, $country);
239
240        if (!$env) {
241            return ['status' => 'error', 'message' => 'Environment introuvable'];
242        }
243
244        if (empty($env->test_number)) {
245            return ['status' => 'error', 'message' => 'test_number manquant dans l\'environnement'];
246        }
247
248        return [
249            'status'      => 'success',
250            'message'     => 'Environment valide',
251            'mode'        => $env->mode,
252            'test_number' => $env->test_number,
253        ];
254    }
255
256    // ================================================================
257    // UI CONTEXT
258    // ================================================================
259
260    public function getUiContext(string $channel, string $country): array
261    {
262        $profile  = model('UserProfileModel')->first();
263        $channels = model('ChannelSettingModel')->getChannelFlags();
264
265        $providers = model('ChannelProviderSettingModel')
266            ->getActiveProviders($channel, $country);
267
268        foreach ($providers as $p) {
269            $cred = $this->credentialModel->where([
270                'channel_provider_id' => $p->id,
271                'country_code'        => $country,
272                'is_active'           => 1,
273            ])->first();
274
275            $p->is_configured = !empty($cred?->credentials_json);
276        }
277
278        return [
279            'profile'   => $profile,
280            'channel'   => $channel,
281            'providers' => $providers,
282            'channels'  => $channels,
283        ];
284    }
285
286    // ================================================================
287    // RUNTIME RESOLUTION
288    // ================================================================
289
290    public function resolve(int $providerId, string $country): array
291    {
292        $credentials = $this->getCredentials($providerId, $country);
293        $env         = $this->getEnvironment($providerId, $country);
294
295        if (empty($credentials)) {
296            throw new \RuntimeException("Credentials manquants");
297        }
298
299        return [
300            'provider_id' => $providerId,
301            'country'     => $country,
302            'mode'        => $env['mode'] ?? 'sandbox',
303            'credentials' => $credentials,
304            'environment' => $env,
305        ];
306    }
307
308    // ================================================================
309    // DEFAULT ENV
310    // ================================================================
311
312    private function defaultEnvironment(): array
313    {
314        return [
315            'mode'        => 'sandbox',
316            'test_number' => null,
317            'test_email'  => null,
318            'timeout_ms'  => 30000,
319        ];
320    }
321}