Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 358
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
SenderSingleController
0.00% covered (danger)
0.00%
0 / 358
0.00% covered (danger)
0.00%
0 / 18
3540
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 index
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 send
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 1
156
 preview
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 saveDraft
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 1
72
 drafts
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 sent
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 resend
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
12
 editSent
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 view
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 viewSent
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 resolveMessage
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 buildSchedule
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 showDraft
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 editDraft
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 updateDraft
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 deleteDraft
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 resolveRecipient
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Modules\SchedulerModule\Controllers;
4
5use App\Controllers\BaseController;
6use App\Modules\ContactModule\Models\ContactModel;
7use App\Modules\NotificationModule\Models\NotificationQueueModel;
8use App\Modules\NotificationModule\Models\NotificationDraftModel;
9use App\Modules\TemplateModule\Models\MessageTemplateModel;
10use App\Modules\SecurityModule\Services\AuthService;
11use App\Modules\ContactModule\Models\SenderModel;
12use App\Modules\ChannelModule\Models\ChannelProviderModel;
13use App\Modules\TemplateModule\Services\MessageTemplateService;
14
15
16class SenderSingleController extends BaseController
17{
18    protected ContactModel $contactModel;
19    protected NotificationQueueModel $queueModel;
20    protected NotificationDraftModel $draftModel;
21    protected MessageTemplateModel $templateModel;
22    protected AuthService $authService;
23    protected SenderModel $senderModel;
24    protected ChannelProviderModel $providerModel;
25    protected MessageTemplateService $templateService;
26
27
28    public function __construct()
29    {
30        $this->contactModel     = new ContactModel();
31        $this->queueModel       = new NotificationQueueModel();
32        $this->draftModel       = new NotificationDraftModel();
33        $this->templateModel    = new MessageTemplateModel();
34        $this->authService      = new AuthService();
35        $this->senderModel   = new SenderModel();
36        $this->providerModel = new ChannelProviderModel();
37        $this->templateService = service('messageTemplateService');
38 
39    }
40
41    /* =========================
42     * VIEW
43     * ========================= */
44
45    public function index()
46    {
47        $contacts = $this->contactModel
48            ->where('status', 'active')
49            ->orderBy('name', 'ASC')
50            ->findAll();
51
52        return view('App\Modules\SchedulerModule\Views\sender_single\index', [
53            'title'    => 'Envoi unique',
54            'contacts' => $contacts
55        ]);
56    }
57
58    /* =========================
59     * SEND
60     * ========================= */
61
62public function send()
63{
64    try {
65        $raw  = $this->request->getBody();
66        $data = $this->request->getJSON(true);
67
68        if (!$data) {
69            return $this->response->setJSON([
70                'status'  => 'error',
71                'message' => 'Payload invalide — body: ' . $raw
72            ]);
73        }
74
75        // ── 1. Extraction des variables ──────────────────────────
76        $channel      = $data['channel']      ?? null;
77        $contactId    = (int) ($data['contact_id'] ?? 0);
78        $scheduleType = $data['schedule_type'] ?? 'now';
79        $senderId     = $data['sender_id']     ?? null;
80        $priority     = $data['priority']      ?? 'normal';
81
82        // ── 2. Validation des paramètres obligatoires ────────────
83        if (!$channel || !$contactId) {
84            return $this->response->setJSON([
85                'status'  => 'error',
86                'message' => 'Paramètres invalides',
87                'debug'   => [
88                    'channel'    => $channel,
89                    'contact_id' => $data['contact_id'] ?? 'ABSENT',
90                ]
91            ]);
92        }
93
94        // ── 3. Résolution du contact ─────────────────────────────
95        $contact = $this->contactModel->find($contactId);
96
97        if (!$contact) {
98            return $this->response->setJSON([
99                'status'  => 'error',
100                'message' => 'Contact introuvable'
101            ]);
102        }
103
104        // ── 4. Résolution du provider ────────────────────────────
105        $db             = \Config\Database::connect();
106        $providerRecord = $db->table('channel_provider')
107                             ->where('id', $senderId)
108                             ->get()
109                             ->getRowObject();
110
111        $provider       = $providerRecord?->name ?? 'default';
112
113        // ── 5. Résolution du message et de la planification ──────
114        $message     = $this->resolveMessage($data, $contact);
115        $scheduledAt = $this->buildSchedule($data);
116        $status      = ($scheduleType === 'now') ? 'processing' : 'pending';
117
118        // ── 6. Push en queue ─────────────────────────────────────
119        $queueItem = service('notificationQueueFactory')->push(
120            $contact,
121            $channel,
122            $message,
123            $status,
124            $scheduledAt,
125            'single_send',
126            null,
127            ['sender_id' => $senderId]   // ← le factory s'occupe du reste
128        );
129
130        if (!$queueItem) {
131            return $this->response->setJSON([
132                'status'  => 'error',
133                'message' => 'Échec création queue'
134            ]);
135        }
136
137        // ── 7. Envoi immédiat ────────────────────────────────────
138        if ($scheduleType === 'now') {
139            $dispatchService = service('notificationDispatchService');
140
141            try {
142                $result = $dispatchService->dispatch($queueItem);
143            } catch (\Throwable $dispatchEx) {
144                return $this->response->setJSON([
145                    'status'  => 'error',
146                    'message' => $dispatchEx->getMessage(),
147                    'file'    => $dispatchEx->getFile(),
148                    'line'    => $dispatchEx->getLine(),
149                ]);
150            }
151
152            return $this->response->setJSON([
153                'status'  => $result ? 'success' : 'error',
154                'message' => $result ? 'Message envoyé' : 'Échec envoi — dispatch retourné false',
155            ]);
156        }
157
158        // ── 8. Envoi planifié ────────────────────────────────────
159        return $this->response->setJSON([
160            'status'  => 'success',
161            'message' => 'Notification planifiée pour le ' . $scheduledAt,
162        ]);
163
164    } catch (\Throwable $e) {
165        return $this->response->setJSON([
166            'status'  => 'error',
167            'message' => $e->getMessage(),
168            'file'    => $e->getFile(),
169            'line'    => $e->getLine(),
170            'trace'   => explode("\n", $e->getTraceAsString()),
171        ]);
172    }
173}
174    /* =========================
175     * PREVIEW
176     * ========================= */
177
178    public function preview()
179    {
180        $data = $this->request->getJSON(true);
181
182        $contact = $this->contactModel->find($data['contact_id'] ?? 0);
183
184        if (!$contact) {
185            return $this->response->setJSON([
186                'status'  => 'error',
187                'message' => 'Contact invalide'
188            ]);
189        }
190
191        $message = $this->resolveMessage($data, $contact);
192
193        return $this->response->setJSON([
194            'status' => 'success',
195            'preview' => [
196                'channel' => $data['channel'] ?? null,
197                'to'      => $this->resolveRecipient($contact, $data['channel'] ?? null),
198                'message' => $message,
199                'contact' => [
200                    'id'    => $contact->id,
201                    'name'  => $contact->name,
202                    'email' => $contact->email,
203                    'phone' => $contact->phone,
204                ]
205            ]
206        ]);
207    }
208
209    /* =========================
210     * SAVE DRAFT
211     * ========================= */
212
213    public function saveDraft()
214    {
215        $data = $this->request->getJSON(true);
216
217        if (!$data) {
218            return $this->response->setJSON([
219                'status'  => 'error',
220                'message' => 'Payload invalide'
221            ]);
222        }
223
224        $userId    = $this->authService->userId();
225        $contactId = (int) ($data['contact_id'] ?? 0);
226        $channel   = $data['channel'] ?? null;
227        $message   = trim($data['message'] ?? '');
228
229        if (!$userId) {
230            return $this->response->setJSON([
231                'status'  => 'error',
232                'message' => 'Utilisateur non authentifié'
233            ]);
234        }
235
236        if (!$contactId || !$channel || $message === '') {
237            return $this->response->setJSON([
238                'status'  => 'error',
239                'message' => 'Paramètres obligatoires manquants'
240            ]);
241        }
242
243        $contact = $this->contactModel->find($contactId);
244
245        if (!$contact) {
246            return $this->response->setJSON([
247                'status'  => 'error',
248                'message' => 'Contact invalide'
249            ]);
250        }
251
252        $recipient = $this->resolveRecipient($contact, $channel);
253
254        if (!$recipient) {
255            return $this->response->setJSON([
256                'status'  => 'error',
257                'message' => 'Canal incompatible avec ce contact'
258            ]);
259        }
260
261        $scheduledAt = $this->buildSchedule($data);
262
263        $payload = [
264            'to'            => $recipient,
265            'message'       => $message,
266            'channel'       => $channel,
267            'sender_id'     => $data['sender_id'] ?? null,
268            'scheduled_at'  => $scheduledAt,
269            'contact'       => [
270                'id'    => $contact->id,
271                'name'  => $contact->name,
272                'email' => $contact->email,
273                'phone' => $contact->phone,
274            ],
275        ];
276
277        $draftId = $this->draftModel->insert([
278            'user_id'      => $userId,
279            'contact_id'   => $contact->id,
280            'channel'      => $channel,
281            'sender_id'    => $data['sender_id'] ?? null,
282            'message'      => $message,
283            'payload'      => json_encode($payload),
284            'scheduled_at' => $scheduledAt,
285            'status'       => 'draft',
286        ]);
287
288        return $this->response->setJSON([
289            'status'   => 'success',
290            'draft_id' => $draftId,
291            'message'  => 'Brouillon enregistré avec succès'
292        ]);
293    }
294
295    /* =========================
296     * DRAFT LIST
297     * ========================= */
298
299    public function drafts()
300    {
301        $userId = $this->authService->userId();
302
303        if (!$userId) {
304            return redirect()->to('/login');
305        }
306
307        $drafts = $this->draftModel
308            ->where('user_id', $userId)
309            ->where('status', 'draft')
310            ->orderBy('created_at', 'DESC')
311            ->findAll();
312
313        return view('App\Modules\SchedulerModule\Views\sender_single\drafts', [
314            'drafts' => $drafts
315        ]);
316    }
317
318    /* =========================
319     * SENT LIST
320     * ========================= */
321
322    public function sent()
323    {
324        return view(
325            'App\Modules\SchedulerModule\Views\sender_single\sent',
326            [
327                'sent' => $this->queueModel->getSentMessages()
328            ]
329        );
330    }
331
332        /* =========================
333        * RESENT LIST
334        * ========================= */
335    public function resend($id)
336    {
337        $old = $this->queueModel->find($id);
338
339        if (!$old) {
340            return redirect()->back()->with('error', 'Message introuvable');
341        }
342
343        // Récupération du contact
344        $contact = $this->contactModel->find($old->contact_id);
345
346        if (!$contact) {
347            return redirect()->back()->with('error', 'Contact introuvable');
348        }
349
350        // Création nouvelle entrée
351        $newId = $this->queueModel->insert([
352            'contact_id'   => $old->contact_id,
353            'channel'      => $old->channel,
354            'payload'      => $old->payload,
355            'status'       => 'pending',
356            'attempts'     => 0,
357            'scheduled_at' => date('Y-m-d H:i:s'),
358            'meta_key'     => 'resend_from_' . $old->id,
359        ]);
360
361        return redirect()
362            ->back()
363            ->with('success',
364                'Message reprogrammé pour ' .
365                ($contact->name ?? 'N/A') .
366                ' (' .
367                ($contact->email ?? 'N/A') .
368                ')'
369            );
370    }
371
372
373   public function editSent($id)
374{
375    $old = $this->queueModel->find($id);
376
377    if (!$old) {
378        return redirect()->back();
379    }
380
381    $newId = $this->queueModel->insert([
382        'contact_id'   => $old->contact_id,
383        'channel'      => $old->channel,
384        'provider'     => $old->provider,
385        'payload'      => $old->payload,
386        'status'       => 'pending',
387        'parent_id'    => $old->id,
388        'scheduled_at' => date('Y-m-d H:i:s'),
389        'attempts'     => 0,
390    ]);
391
392    // ⚠️ ICI: rediriger vers le NOUVEAU message
393return redirect()->to(
394    base_url('dashboard/envoi/unique/envoyes/' . $newId . '/edit?mode=clone')
395);
396}
397
398        /* =========================
399        * VIEW LIST
400        * ========================= */
401        public function view($id)
402    {
403        $message = $this->queueModel->find($id);
404
405        if (!$message) {
406            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
407        }
408
409        return view(
410            'App\Modules\SchedulerModule\Views\sender_single\view',
411            [
412                'message' => $message
413            ]
414        );
415    }
416
417
418    /* =========================
419        * VIEW SENT 
420        * ========================= */
421    public function viewSent($id)
422    {
423        $notification = $this->queueModel->find($id);
424
425        if (!$notification) {
426            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
427        }
428
429        // Normalisation du payload
430        $payload = $notification->payload;
431
432        if (is_string($payload)) {
433            $decoded = json_decode($payload, true);
434            $payload = json_last_error() === JSON_ERROR_NONE ? $decoded : [];
435        } elseif (is_object($payload)) {
436            $payload = json_decode(json_encode($payload), true);
437        }
438
439        return view(
440            'App\Modules\SchedulerModule\Views\sender_single\view_sent',
441            [
442                'notification' => $notification,
443                'payload'      => $payload
444            ]
445        );
446    }
447
448    /* =========================
449     * HELPERS
450     * ========================= */
451
452    private function resolveMessage(array $data, $contact): string
453    {
454        $templateId = $data['template_id'] ?? null;
455
456        if ($templateId) {
457            $template = $this->templateModel->find($templateId);
458
459            if ($template) {
460                return $this->templateService->render(
461                    $template->content,
462                    [
463                        'name'    => $contact->name,
464                        'email'   => $contact->email ?? '',
465                        'phone'   => $contact->phone ?? '',
466                        'company' => $contact->company ?? '',
467                    ]
468                );
469            }
470        }
471
472        return trim($data['message'] ?? '');
473    }
474
475
476    private function buildSchedule(array $data): ?string
477    {
478        if (!empty($data['scheduled_date']) && !empty($data['scheduled_time'])) {
479            return date(
480                'Y-m-d H:i:s',
481                strtotime($data['scheduled_date'] . ' ' . $data['scheduled_time'])
482            );
483        }
484
485        return null;
486    }
487
488 
489
490    public function showDraft($id)
491{
492    $draft = $this->draftModel->find($id);
493
494    if (!$draft) {
495        return $this->response->setJSON([
496            'status' => 'error',
497            'message' => 'Brouillon introuvable'
498        ]);
499    }
500
501    return $this->response->setJSON([
502    'status' => 'success',
503    'data' => [
504        'id'         => $draft->id,
505        'channel'    => $draft->channel,
506        'message'    => $draft->message,
507        'payload'    => is_string($draft->payload)
508                        ? json_decode($draft->payload, true)
509                        : $draft->payload,
510        'created_at' => $draft->created_at
511    ]
512]);
513}
514
515
516public function editDraft($id)
517{
518    $draft = $this->draftModel->find($id);
519
520    if (!$draft) {
521        return $this->response->setJSON([
522            'status'  => 'error',
523            'message' => 'Brouillon introuvable'
524        ]);
525    }
526
527    // Normalisation payload (sécurisation format JSON / object)
528    $payload = $draft->payload;
529
530    if (is_string($payload)) {
531        $payload = json_decode($payload, true);
532    } elseif (is_object($payload)) {
533        $payload = json_decode(json_encode($payload), true);
534    }
535
536    return $this->response->setJSON([
537        'status' => 'success',
538        'data'   => [
539            'id'        => $draft->id,
540            'contact_id'=> $draft->contact_id,
541            'channel'   => $draft->channel,
542            'sender_id' => $draft->sender_id,
543            'message'   => $draft->message ?? ($payload['message'] ?? ''),
544            'scheduled_at' => $draft->scheduled_at ?? ($payload['scheduled_at'] ?? null),
545            'payload'   => $payload,
546        ]
547    ]);
548}
549
550
551public function updateDraft($id)
552{
553    $data = $this->request->getJSON(true);
554
555    $draft = $this->draftModel->find($id);
556
557    if (!$draft) {
558        return $this->response->setJSON([
559            'status' => 'error',
560            'message' => 'Brouillon introuvable'
561        ]);
562    }
563
564    $scheduledAt = null;
565
566    if (!empty($data['scheduled_date']) && !empty($data['scheduled_time'])) {
567        $scheduledAt = date(
568            'Y-m-d H:i:s',
569            strtotime($data['scheduled_date'] . ' ' . $data['scheduled_time'])
570        );
571    }
572
573    $this->draftModel->update($id, [
574        'channel'       => $data['channel'] ?? $draft->channel,
575        'sender_id'     => $data['sender_id'] ?? $draft->sender_id,
576        'message'       => $data['message'] ?? $draft->message,
577        'scheduled_at'  => $scheduledAt ?? $draft->scheduled_at,
578    ]);
579
580    return $this->response->setJSON([
581        'status' => 'success',
582        'message' => 'Brouillon mis à jour'
583    ]);
584}
585
586public function deleteDraft($id)
587{
588    $draft = $this->draftModel->find($id);
589
590    if (!$draft) {
591        return $this->response->setJSON([
592            'status' => 'error',
593            'message' => 'Brouillon introuvable'
594        ]);
595    }
596
597    $this->draftModel->delete($id);
598
599    return $this->response->setJSON([
600        'status' => 'success',
601        'message' => 'Brouillon supprimé'
602    ]);
603}
604
605private function resolveRecipient($contact, ?string $channel): ?string
606{
607    return match ($channel) {
608        'sms', 'whatsapp' => $contact->phone  ?? null,
609        'email'           => $contact->email  ?? null,
610        default           => $contact->phone  ?? $contact->email ?? null,
611    };
612}
613
614
615   
616}