Passed
Push — master ( d52980...afe0a2 )
by Yannick
10:56
created

ChatController::globalAck()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 20
rs 9.4888
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Controller;
8
9
use Chamilo\CoreBundle\Traits\ControllerTrait;
10
use Chamilo\CoreBundle\Traits\CourseControllerTrait;
11
use Chamilo\CoreBundle\Traits\ResourceControllerTrait;
12
use Chamilo\CourseBundle\Controller\CourseControllerInterface;
13
use Chamilo\CourseBundle\Entity\CChatConversation;
14
use Chamilo\CourseBundle\Repository\CChatConversationRepository;
15
use CourseChatUtils;
16
use Doctrine\Persistence\ManagerRegistry;
17
use Event;
18
use Symfony\Component\HttpFoundation\JsonResponse;
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\HttpFoundation\Response;
21
use Symfony\Component\Routing\Attribute\Route;
22
use Throwable;
23
24
use const JSON_UNESCAPED_SLASHES;
25
use const JSON_UNESCAPED_UNICODE;
26
27
class ChatController extends AbstractResourceController implements CourseControllerInterface
28
{
29
    use ControllerTrait;
30
    use CourseControllerTrait;
31
    use ResourceControllerTrait;
32
33
    #[Route(path: '/resources/chat/', name: 'chamilo_core_chat_home', options: ['expose' => true])]
34
    public function index(Request $request): Response
35
    {
36
        Event::event_access_tool(TOOL_CHAT);
0 ignored issues
show
Bug introduced by
The method event_access_tool() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

36
        Event::/** @scrutinizer ignore-call */ 
37
               event_access_tool(TOOL_CHAT);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
37
        Event::registerLog([
0 ignored issues
show
Bug introduced by
The method registerLog() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

37
        Event::/** @scrutinizer ignore-call */ 
38
               registerLog([

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
38
            'tool' => TOOL_CHAT,
39
            'action' => 'start',
40
            'action_details' => 'start-chat',
41
        ]);
42
43
        $parentNode = $this->getParentResourceNode($request);
44
45
        return $this->render('@ChamiloCore/Chat/chat.html.twig', [
46
            'restrict_to_coach' => ('true' === api_get_setting('chat.course_chat_restrict_to_coach')),
47
            'user' => api_get_user_info(),
48
            'emoji_smile' => '<span>&#128522;</span>',
49
            'course_url_params' => api_get_cidreq(),
50
            'course' => api_get_course_entity(),
51
            'session_id' => api_get_session_id(),
52
            'group_id' => api_get_group_id(),
53
            'chat_parent_node_id' => $parentNode?->getId() ?? 0,
54
        ]);
55
    }
56
57
    #[Route(path: '/resources/chat/conversations/', name: 'chamilo_core_chat_ajax', options: ['expose' => true])]
58
    public function ajax(Request $request, ManagerRegistry $doctrine): Response
59
    {
60
        $debug = false;
61
        $log = function (string $msg, array $ctx = []) use ($debug): void {
62
            if (!$debug) {
63
                return;
64
            }
65
            error_log('[ChatController] '.$msg.' | '.json_encode($ctx, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
66
        };
67
68
        if (!api_protect_course_script()) {
69
            $log('protect.failed');
70
71
            return new JsonResponse(['status' => false, 'error' => 'forbidden'], 403);
72
        }
73
74
        $courseId = api_get_course_int_id();
75
        $userId = api_get_user_id();
76
        $sessionId = api_get_session_id();
77
        $groupId = api_get_group_id();
78
79
        $log('request.start', [
80
            'cid' => $courseId,
81
            'uid' => $userId,
82
            'sid' => $sessionId,
83
            'gid' => $groupId,
84
            'query' => $request->query->all(),
85
            'post' => $request->request->all(),
86
        ]);
87
88
        $parentResourceNode = $this->getParentResourceNode($request);
89
        $log('parent.node', ['id' => $parentResourceNode?->getId()]);
90
91
        if (!$parentResourceNode) {
0 ignored issues
show
introduced by
$parentResourceNode is of type Chamilo\CoreBundle\Entity\ResourceNode, thus it always evaluated to true.
Loading history...
92
            return new JsonResponse(['status' => false, 'error' => 'parent_node_not_found'], 404);
93
        }
94
95
        /** @var CChatConversationRepository $conversationRepository */
96
        $conversationRepository = $doctrine->getRepository(CChatConversation::class);
97
98
        // Helper: single-file-per-day behavior, append in chronological order
99
        $chat = new CourseChatUtils(
100
            $courseId,
101
            $userId,
102
            $sessionId,
103
            $groupId,
104
            $parentResourceNode,
105
            $conversationRepository
106
        );
107
108
        $action = (string) $request->get('action', 'track');
109
        $log('action', ['name' => $action]);
110
111
        $json = ['status' => false];
112
113
        try {
114
            switch ($action) {
115
                case 'chat_logout':
116
                    Event::registerLog([
117
                        'tool' => TOOL_CHAT,
118
                        'action' => 'exit',
119
                        'action_details' => 'exit-chat',
120
                    ]);
121
                    $json = ['status' => true];
122
                    $log('logout.ok');
123
124
                    break;
125
126
                case 'track':
127
                    $chat->keepUserAsConnected();
128
                    $chat->disconnectInactiveUsers();
129
130
                    $friend = (int) $request->get('friend', 0);
131
                    $newUsersOnline = $chat->countUsersOnline();
132
                    $oldUsersOnline = (int) $request->get('users_online', 0);
133
134
                    $json = [
135
                        'status' => true,
136
                        'data' => [
137
                            'oldFileSize' => false,
138
                            'history' => $chat->readMessages(false, $friend),
139
                            'usersOnline' => $newUsersOnline,
140
                            'userList' => $newUsersOnline !== $oldUsersOnline ? $chat->listUsersOnline() : null,
141
                            'currentFriend' => $friend,
142
                        ],
143
                    ];
144
                    $log('track.ok', [
145
                        'friend' => $friend,
146
                        'usersOnline' => $newUsersOnline,
147
                        'listChanged' => ($newUsersOnline !== $oldUsersOnline),
148
                    ]);
149
150
                    break;
151
152
                case 'preview':
153
                    $msg = (string) $request->get('message', '');
154
                    $json = [
155
                        'status' => true,
156
                        'data' => ['message' => $chat->prepareMessage($msg)],
157
                    ];
158
                    $log('preview.ok', ['len' => \strlen($msg)]);
159
160
                    break;
161
162
                case 'reset':
163
                    $friend = (int) $request->get('friend', 0);
164
                    $json = [
165
                        'status' => true,
166
                        'data' => $chat->readMessages(true, $friend),
167
                    ];
168
                    $log('reset.ok', ['friend' => $friend]);
169
170
                    break;
171
172
                case 'write':
173
                    $friend = (int) $request->get('friend', 0);
174
                    $msg = (string) $request->get('message', '');
175
                    $ok = $chat->saveMessage($msg, $friend);
176
177
                    $json = [
178
                        'status' => $ok,
179
                        'data' => ['writed' => $ok],
180
                    ];
181
                    $log('write.done', ['friend' => $friend, 'len' => \strlen($msg), 'ok' => $ok]);
182
183
                    break;
184
185
                default:
186
                    $log('action.unknown', ['name' => $action]);
187
                    $json = ['status' => false, 'error' => 'unknown_action'];
188
189
                    break;
190
            }
191
        } catch (Throwable $e) {
192
            $log('error', ['action' => $action, 'err' => $e->getMessage()]);
193
            $json = ['status' => false, 'error' => $e->getMessage()];
194
        } finally {
195
            $log('response', ['status' => $json['status'] ?? null]);
196
        }
197
198
        return new JsonResponse($json);
199
    }
200
201
    #[Route(path: '/account/chat', name: 'chamilo_core_global_chat_home', options: ['expose' => true])]
202
    public function globalHome(): Response
203
    {
204
        api_block_anonymous_users();
205
        if ('true' !== api_get_setting('allow_global_chat')) {
206
            return $this->redirectToRoute('homepage');
207
        }
208
209
        return $this->render('@ChamiloCore/Chat/chat.html.twig', []);
210
    }
211
212
    #[Route(path: '/account/chat/api/start', name: 'chamilo_core_chat_api_start', options: ['expose' => true], methods: ['GET'])]
213
    public function globalStart(): JsonResponse
214
    {
215
        api_block_anonymous_users();
216
        if ('true' !== api_get_setting('allow_global_chat')) {
217
            return new JsonResponse(['error' => 'disabled'], 403);
218
        }
219
220
        $chat = new \Chat();
221
222
        ob_start();
223
        $ret = $chat->startSession();
224
        $echoed = ob_get_clean();
225
226
        if ($echoed !== '') {
227
            return JsonResponse::fromJsonString($echoed);
228
        }
229
230
        if (is_string($ret)) {
231
            return JsonResponse::fromJsonString($ret);
232
        }
233
234
        return new JsonResponse($ret ?? []);
235
    }
236
237
    #[Route(path: '/account/chat/api/contacts', name: 'chamilo_core_chat_api_contacts', options: ['expose' => true], methods: ['POST'])]
238
    public function globalContacts(): Response
239
    {
240
        api_block_anonymous_users();
241
        if ('true' !== api_get_setting('allow_global_chat')) {
242
            return new Response('', 403);
243
        }
244
245
        $chat = new \Chat();
246
        $html = $chat->getContacts();
247
248
        return new Response($html, 200, ['Content-Type' => 'text/html; charset=UTF-8']);
249
    }
250
251
    #[Route(path: '/account/chat/api/heartbeat', name: 'chamilo_core_chat_api_heartbeat', options: ['expose' => true], methods: ['GET'])]
252
    public function globalHeartbeat(): JsonResponse
253
    {
254
        api_block_anonymous_users();
255
        if ('true' !== api_get_setting('allow_global_chat')) {
256
            return new JsonResponse(['error' => 'disabled'], 403);
257
        }
258
259
        $chat = new \Chat();
260
261
        ob_start();
262
        $ret = $chat->heartbeat();
263
        $echoed = ob_get_clean();
264
265
        if ($echoed !== '') {
266
            return JsonResponse::fromJsonString($echoed);
267
        }
268
269
        if (is_string($ret)) {
270
            return JsonResponse::fromJsonString($ret);
271
        }
272
273
        return new JsonResponse($ret ?? []);
274
    }
275
276
    #[Route(path: '/account/chat/api/send', name: 'chamilo_core_chat_api_send', options: ['expose' => true], methods: ['POST'])]
277
    public function globalSend(Request $req): JsonResponse
278
    {
279
        api_block_anonymous_users();
280
        if ('true' !== api_get_setting('allow_global_chat')) {
281
            return new JsonResponse(['error' => 'disabled'], 403);
282
        }
283
284
        $to      = (int) $req->request->get('to', 0);
285
        $message = (string) $req->request->get('message', '');
286
        $chat = new \Chat();
287
288
        ob_start();
289
        $ret = $chat->send(api_get_user_id(), $to, $message);
290
        $echoed = ob_get_clean();
291
292
        if ($echoed !== '') {
293
            return JsonResponse::fromJsonString($echoed);
294
        }
295
296
        if (is_string($ret)) {
297
            return JsonResponse::fromJsonString($ret);
298
        }
299
300
        return new JsonResponse($ret ?? ['id' => 0]);
301
    }
302
303
    #[Route(path: '/account/chat/api/status', name: 'chamilo_core_chat_api_status', options: ['expose' => true], methods: ['POST'])]
304
    public function globalStatus(Request $req): JsonResponse
305
    {
306
        api_block_anonymous_users();
307
        if ('true' !== api_get_setting('allow_global_chat')) {
308
            return new JsonResponse(['error' => 'disabled'], 403);
309
        }
310
311
        $status = (int) $req->request->get('status', 0);
312
313
        $chat = new \Chat();
314
        $chat->setUserStatus($status);
315
316
        return new JsonResponse(['ok' => true, 'status' => $status]);
317
    }
318
319
    #[Route(path: '/account/chat/api/history', name: 'chamilo_core_chat_api_history', options: ['expose' => true], methods: ['GET'])]
320
    public function globalHistory(Request $req): JsonResponse
321
    {
322
        api_block_anonymous_users();
323
        if ('true' !== api_get_setting('allow_global_chat')) {
324
            return new JsonResponse(['error' => 'disabled'], 403);
325
        }
326
327
        $peerId  = (int) $req->query->get('user_id', 0);
328
        $visible = (int) $req->query->get('visible_messages', 0);
329
330
        if (!$peerId) {
331
            return new JsonResponse([]);
332
        }
333
334
        $chat  = new \Chat();
335
        $items = $chat->getPreviousMessages($peerId, api_get_user_id(), $visible);
336
337
        if (!empty($items)) {
338
            sort($items);
339
            return new JsonResponse($items);
340
        }
341
342
        return new JsonResponse([]);
343
    }
344
345
    #[Route(path: '/account/chat/api/preview', name: 'chamilo_core_chat_api_preview', options: ['expose' => true], methods: ['POST'])]
346
    public function globalPreview(Request $req): Response
347
    {
348
        api_block_anonymous_users();
349
        if ('true' !== api_get_setting('allow_global_chat')) {
350
            return new Response('', 403);
351
        }
352
353
        $html = \CourseChatUtils::prepareMessage((string) $req->request->get('message', ''));
354
        return new Response($html, 200, ['Content-Type' => 'text/html; charset=UTF-8']);
355
    }
356
357
    #[Route(path: '/account/chat/api/presence', name: 'chamilo_core_chat_api_presence', options: ['expose' => true], methods: ['POST'])]
358
    public function globalPresence(Request $req): JsonResponse
359
    {
360
        api_block_anonymous_users();
361
        if ('true' !== api_get_setting('allow_global_chat')) {
362
            return new JsonResponse(['error' => 'disabled'], 403);
363
        }
364
365
        $raw = (string) $req->request->get('ids', '');
366
        $ids = [];
367
        if ($raw !== '') {
368
            $tryJson = json_decode($raw, true);
369
            if (is_array($tryJson)) {
370
                $ids = array_filter(array_map('intval', $tryJson));
371
            } else {
372
                $ids = array_filter(array_map('intval', preg_split('/[,\s]+/', $raw)));
373
            }
374
        }
375
376
        $map = [];
377
        foreach ($ids as $id) {
378
            $ui = api_get_user_info($id, true);
379
            $v = $ui['user_is_online_in_chat'] ?? $ui['user_is_online'] ?? $ui['online'] ?? null;
380
            $online = false;
381
            if ($v !== null) {
382
                if (is_string($v)) {
383
                    $online = preg_match('/^(1|true|online|on)$/i', $v) === 1;
384
                } else {
385
                    $online = !empty($v);
386
                }
387
            }
388
            if ($online === false && !empty($ui['last_connection'])) {
389
                $ts = api_strtotime($ui['last_connection'], 'UTC');
390
                $online = (time() - $ts) <= 120;
391
            }
392
            $map[$id] = $online ? 1 : 0;
393
        }
394
395
        return new JsonResponse(['presence' => $map]);
396
    }
397
398
    #[Route(path: '/account/chat/api/ack', name: 'chamilo_core_chat_api_ack', options: ['expose' => true], methods: ['POST'])]
399
    public function globalAck(Request $req): JsonResponse
400
    {
401
        api_block_anonymous_users();
402
        if ('true' !== api_get_setting('allow_global_chat')) {
403
            return new JsonResponse(['error' => 'disabled'], 403);
404
        }
405
406
        $peerId = (int) $req->request->get('peer_id', 0);
407
        $lastSeenId = (int) $req->request->get('last_seen_id', 0);
408
        if ($peerId <= 0 || $lastSeenId <= 0) {
409
            return new JsonResponse(['ok' => false, 'error' => 'bad_params'], 400);
410
        }
411
412
        $chat = new \Chat();
413
        try {
414
            $n = $chat->ackReadUpTo($peerId, api_get_user_id(), $lastSeenId);
415
            return new JsonResponse(['ok' => true, 'updated' => $n]);
416
        } catch (\Throwable $e) {
417
            return new JsonResponse(['ok' => false, 'error' => $e->getMessage()], 500);
418
        }
419
    }
420
}
421