Issues (2034)

main/inc/lib/CourseChatUtils.php (3 issues)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\CourseRelUser;
7
use Chamilo\CoreBundle\Entity\Session;
8
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
9
use Chamilo\CourseBundle\Entity\CChatConnected;
10
use Chamilo\UserBundle\Entity\User;
11
use Doctrine\Common\Collections\Criteria;
12
use Michelf\MarkdownExtra;
13
14
/**
15
 * Class CourseChat
16
 * Manage the chat for a course.
17
 */
18
class CourseChatUtils
19
{
20
    private $groupId;
21
    private $courseId;
22
    private $sessionId;
23
    private $userId;
24
25
    /**
26
     * CourseChat constructor.
27
     *
28
     * @param int $courseId
29
     * @param int $userId
30
     * @param int $sessionId
31
     * @param int $groupId
32
     */
33
    public function __construct($courseId, $userId, $sessionId = 0, $groupId = 0)
34
    {
35
        $this->courseId = (int) $courseId;
36
        $this->userId = (int) $userId;
37
        $this->sessionId = (int) $sessionId;
38
        $this->groupId = (int) $groupId;
39
    }
40
41
    /**
42
     * Prepare a message. Clean and insert emojis.
43
     *
44
     * @param string $message The message to prepare
45
     *
46
     * @return string
47
     */
48
    public static function prepareMessage($message)
49
    {
50
        if (empty($message)) {
51
            return '';
52
        }
53
54
        Emojione\Emojione::$imagePathPNG = api_get_path(WEB_LIBRARY_PATH).'javascript/emojione/png/';
55
        Emojione\Emojione::$ascii = true;
56
57
        $message = trim($message);
58
        $message = nl2br($message);
59
        // Security XSS
60
        $message = Security::remove_XSS($message);
61
        //search urls
62
        $message = preg_replace(
63
            '@((https?://)?([-\w]+\.[-\w\.]+)+\w(:\d+)?(/([-\w/_\.]*(\?\S+)?)?)*)@',
64
            '<a href="$1" target="_blank">$1</a>',
65
            $message
66
        );
67
        // add "http://" if not set
68
        $message = preg_replace(
69
            '/<a\s[^>]*href\s*=\s*"((?!https?:\/\/)[^"]*)"[^>]*>/i',
70
            '<a href="http://$1" target="_blank">',
71
            $message
72
        );
73
        // Parsing emojis
74
        $message = Emojione\Emojione::toImage($message);
75
        // Parsing text to understand markdown (code highlight)
76
        $message = MarkdownExtra::defaultTransform($message);
77
78
        return $message;
79
    }
80
81
    /**
82
     * Save a chat message in a HTML file.
83
     *
84
     * @param string $message
85
     * @param int    $friendId
86
     *
87
     * @return bool
88
     */
89
    public function saveMessage($message, $friendId = 0)
90
    {
91
        if (empty($message)) {
92
            return false;
93
        }
94
        $friendId = (int) $friendId;
95
96
        $userInfo = api_get_user_info($this->userId);
97
        $courseInfo = api_get_course_info_by_id($this->courseId);
98
        $isMaster = api_is_course_admin();
99
        $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
100
        $basepath_chat = '/chat_files';
101
        $group_info = [];
102
        if ($this->groupId) {
103
            $group_info = GroupManager::get_group_properties($this->groupId);
104
            $basepath_chat = $group_info['directory'].'/chat_files';
105
        }
106
107
        $chat_path = $document_path.$basepath_chat.'/';
108
109
        if (!is_dir($chat_path)) {
110
            if (is_file($chat_path)) {
111
                @unlink($chat_path);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

111
                /** @scrutinizer ignore-unhandled */ @unlink($chat_path);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
112
            }
113
        }
114
115
        $date_now = date('Y-m-d');
116
        $timeNow = date('d/m/y H:i:s');
117
        $basename_chat = 'messages-'.$date_now;
118
119
        if ($this->groupId && !$friendId) {
120
            $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
121
        } elseif ($this->sessionId && !$friendId) {
122
            $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
123
        } elseif ($friendId) {
124
            if ($this->userId < $friendId) {
125
                $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
126
            } else {
127
                $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
128
            }
129
        }
130
131
        $message = self::prepareMessage($message);
132
133
        $fileTitle = $basename_chat.'.log.html';
134
        $filePath = $basepath_chat.'/'.$fileTitle;
135
        $absoluteFilePath = $chat_path.$fileTitle;
136
137
        if (!file_exists($absoluteFilePath)) {
138
            $doc_id = add_document(
139
                $courseInfo,
140
                $filePath,
141
                'file',
142
                0,
143
                $fileTitle,
144
                null,
145
                0,
146
                true,
147
                0,
148
                0,
149
                0,
150
                false
151
            );
152
            $documentLogTypes = ['DocumentAdded', 'invisible'];
153
            foreach ($documentLogTypes as $logType) {
154
                api_item_property_update(
155
                    $courseInfo,
156
                    TOOL_DOCUMENT,
157
                    $doc_id,
158
                    $logType,
159
                    $this->userId,
160
                    $group_info,
161
                    null,
162
                    null,
163
                    null,
164
                    $this->sessionId
165
                );
166
            }
167
168
            item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
169
        } else {
170
            $doc_id = DocumentManager::get_document_id($courseInfo, $filePath);
171
        }
172
173
        $fp = fopen($absoluteFilePath, 'a');
174
        $userPhoto = UserManager::getUserPicture($this->userId, USER_IMAGE_SIZE_MEDIUM, true, $userInfo);
175
176
        if ($isMaster) {
177
            $fileContent = '
178
                <div class="message-teacher">
179
                    <div class="content-message">
180
                        <div class="chat-message-block-name">'.$userInfo['complete_name'].'</div>
181
                        <div class="chat-message-block-content">'.$message.'</div>
182
                        <div class="message-date">'.$timeNow.'</div>
183
                    </div>
184
                    <div class="icon-message"></div>
185
                    <img class="chat-image" src="'.$userPhoto.'">
186
                </div>
187
            ';
188
        } else {
189
            $fileContent = '
190
                <div class="message-student">
191
                    <img class="chat-image" src="'.$userPhoto.'">
192
                    <div class="icon-message"></div>
193
                    <div class="content-message">
194
                        <div class="chat-message-block-name">'.$userInfo['complete_name'].'</div>
195
                        <div class="chat-message-block-content">'.$message.'</div>
196
                        <div class="message-date">'.$timeNow.'</div>
197
                    </div>
198
                </div>
199
            ';
200
        }
201
202
        fputs($fp, $fileContent);
203
        fclose($fp);
204
        $size = filesize($absoluteFilePath);
205
        update_existing_document($courseInfo, $doc_id, $size);
206
        item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
207
208
        return true;
209
    }
210
211
    /**
212
     * Disconnect a user from course chats.
213
     *
214
     * @param int $userId
215
     */
216
    public static function exitChat($userId)
217
    {
218
        $listCourse = CourseManager::get_courses_list_by_user_id($userId);
219
220
        foreach ($listCourse as $course) {
221
            Database::getManager()
222
                ->createQuery('
223
                    DELETE FROM ChamiloCourseBundle:CChatConnected ccc
224
                    WHERE ccc.cId = :course AND ccc.userId = :user
225
                ')
226
                ->execute([
227
                    'course' => intval($course['real_id']),
228
                    'user' => intval($userId),
229
                ]);
230
        }
231
    }
232
233
    /**
234
     * Disconnect users who are more than 5 seconds inactive.
235
     */
236
    public function disconnectInactiveUsers()
237
    {
238
        $em = Database::getManager();
239
        $extraCondition = "AND ccc.toGroupId = {$this->groupId}";
240
        if (empty($this->groupId)) {
241
            $extraCondition = "AND ccc.sessionId = {$this->sessionId}";
242
        }
243
244
        $connectedUsers = $em
245
            ->createQuery("
246
                SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
247
                WHERE ccc.cId = :course $extraCondition
248
            ")
249
            ->setParameter('course', $this->courseId)
250
            ->getResult();
251
252
        $now = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
253
        $cd_count_time_seconds = $now->getTimestamp();
254
        /** @var CChatConnected $connection */
255
        foreach ($connectedUsers as $connection) {
256
            $date_count_time_seconds = $connection->getLastConnection()->getTimestamp();
257
            if (strcmp($now->format('Y-m-d'), $connection->getLastConnection()->format('Y-m-d')) !== 0) {
258
                continue;
259
            }
260
261
            if (($cd_count_time_seconds - $date_count_time_seconds) <= 5) {
262
                continue;
263
            }
264
265
            $em
266
                ->createQuery('
267
                    DELETE FROM ChamiloCourseBundle:CChatConnected ccc
268
                    WHERE ccc.cId = :course AND ccc.userId = :user AND ccc.toGroupId = :group
269
                ')
270
                ->execute([
271
                    'course' => $this->courseId,
272
                    'user' => $connection->getUserId(),
273
                    'group' => $this->groupId,
274
                ]);
275
        }
276
    }
277
278
    /**
279
     * Keep registered to a user as connected.
280
     */
281
    public function keepUserAsConnected()
282
    {
283
        $em = Database::getManager();
284
        $extraCondition = null;
285
286
        if ($this->groupId) {
287
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
288
        } else {
289
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
290
        }
291
292
        $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
293
294
        $connection = $em
295
            ->createQuery("
296
                SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
297
                WHERE ccc.userId = :user AND ccc.cId = :course $extraCondition
298
            ")
299
            ->setParameters([
300
                'user' => $this->userId,
301
                'course' => $this->courseId,
302
            ])
303
            ->getOneOrNullResult();
304
305
        if ($connection) {
306
            $connection->setLastConnection($currentTime);
307
            $em->merge($connection);
308
            $em->flush();
309
310
            return;
311
        }
312
313
        $connection = new CChatConnected();
314
        $connection
315
            ->setCId($this->courseId)
316
            ->setUserId($this->userId)
317
            ->setLastConnection($currentTime)
318
            ->setSessionId($this->sessionId)
319
            ->setToGroupId($this->groupId);
320
321
        $em->persist($connection);
322
        $em->flush();
323
    }
324
325
    /**
326
     * Get the emoji allowed on course chat.
327
     *
328
     * @return array
329
     */
330
    public static function getEmojiStrategy()
331
    {
332
        return require_once api_get_path(SYS_CODE_PATH).'chat/emoji_strategy.php';
333
    }
334
335
    /**
336
     * Get the emoji list to include in chat.
337
     *
338
     * @return array
339
     */
340
    public static function getEmojisToInclude()
341
    {
342
        return [
343
            ':bowtie:',
344
            ':smile:' |
345
            ':laughing:',
346
            ':blush:',
347
            ':smiley:',
348
            ':relaxed:',
349
            ':smirk:',
350
            ':heart_eyes:',
351
            ':kissing_heart:',
352
            ':kissing_closed_eyes:',
353
            ':flushed:',
354
            ':relieved:',
355
            ':satisfied:',
356
            ':grin:',
357
            ':wink:',
358
            ':stuck_out_tongue_winking_eye:',
359
            ':stuck_out_tongue_closed_eyes:',
360
            ':grinning:',
361
            ':kissing:',
362
            ':kissing_smiling_eyes:',
363
            ':stuck_out_tongue:',
364
            ':sleeping:',
365
            ':worried:',
366
            ':frowning:',
367
            ':anguished:',
368
            ':open_mouth:',
369
            ':grimacing:',
370
            ':confused:',
371
            ':hushed:',
372
            ':expressionless:',
373
            ':unamused:',
374
            ':sweat_smile:',
375
            ':sweat:',
376
            ':disappointed_relieved:',
377
            ':weary:',
378
            ':pensive:',
379
            ':disappointed:',
380
            ':confounded:',
381
            ':fearful:',
382
            ':cold_sweat:',
383
            ':persevere:',
384
            ':cry:',
385
            ':sob:',
386
            ':joy:',
387
            ':astonished:',
388
            ':scream:',
389
            ':neckbeard:',
390
            ':tired_face:',
391
            ':angry:',
392
            ':rage:',
393
            ':triumph:',
394
            ':sleepy:',
395
            ':yum:',
396
            ':mask:',
397
            ':sunglasses:',
398
            ':dizzy_face:',
399
            ':imp:',
400
            ':smiling_imp:',
401
            ':neutral_face:',
402
            ':no_mouth:',
403
            ':innocent:',
404
            ':alien:',
405
        ];
406
    }
407
408
    /**
409
     * Get the chat history file name.
410
     *
411
     * @param bool $absolute Optional. Whether get the base or the absolute file path
412
     * @param int  $friendId optional
413
     *
414
     * @return string
415
     */
416
    public function getFileName($absolute = false, $friendId = 0)
417
    {
418
        $date = date('Y-m-d');
419
        $base = 'messages-'.$date.'.log.html';
420
421
        if ($this->groupId && !$friendId) {
422
            $base = 'messages-'.$date.'_gid-'.$this->groupId.'.log.html';
423
        } elseif ($this->sessionId && !$friendId) {
424
            $base = 'messages-'.$date.'_sid-'.$this->sessionId.'.log.html';
425
        } elseif ($friendId) {
426
            if ($this->userId < $friendId) {
427
                $base = 'messages-'.$date.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
428
            } else {
429
                $base = 'messages-'.$date.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
430
            }
431
        }
432
433
        if (!$absolute) {
434
            return $base;
435
        }
436
437
        $courseInfo = api_get_course_info_by_id($this->courseId);
438
        $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
439
        $chatPath = $document_path.'/chat_files/';
440
441
        if ($this->groupId) {
442
            $group_info = GroupManager::get_group_properties($this->groupId);
443
            $chatPath = $document_path.$group_info['directory'].'/chat_files/';
444
        }
445
446
        return $chatPath.$base;
447
    }
448
449
    /**
450
     * Get the chat history.
451
     *
452
     * @param bool $reset
453
     * @param int  $friendId optional
454
     *
455
     * @return string
456
     */
457
    public function readMessages($reset = false, $friendId = 0)
458
    {
459
        $courseInfo = api_get_course_info_by_id($this->courseId);
460
        $date_now = date('Y-m-d');
461
        $isMaster = (bool) api_is_course_admin();
462
        $basepath_chat = '/chat_files';
463
        $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
464
        $group_info = [];
465
        if ($this->groupId) {
466
            $group_info = GroupManager::get_group_properties($this->groupId);
467
            $basepath_chat = $group_info['directory'].'/chat_files';
468
        }
469
470
        $chat_path = $document_path.$basepath_chat.'/';
471
472
        if (!is_dir($chat_path)) {
473
            if (is_file($chat_path)) {
474
                @unlink($chat_path);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

474
                /** @scrutinizer ignore-unhandled */ @unlink($chat_path);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
475
            }
476
477
            if (!api_is_anonymous()) {
478
                @mkdir($chat_path, api_get_permissions_for_new_directories());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

478
                /** @scrutinizer ignore-unhandled */ @mkdir($chat_path, api_get_permissions_for_new_directories());

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
479
                // Save chat files document for group into item property
480
                if ($this->groupId) {
481
                    $doc_id = add_document(
482
                        $courseInfo,
483
                        $basepath_chat,
484
                        'folder',
485
                        0,
486
                        'chat_files',
487
                        null,
488
                        0,
489
                        true,
490
                        0,
491
                        0,
492
                        0,
493
                        false
494
                    );
495
                    api_item_property_update(
496
                        $courseInfo,
497
                        TOOL_DOCUMENT,
498
                        $doc_id,
499
                        'FolderCreated',
500
                        null,
501
                        $group_info,
502
                        null,
503
                        null,
504
                        null
505
                    );
506
                    api_item_property_update(
507
                        $courseInfo,
508
                        TOOL_DOCUMENT,
509
                        $doc_id,
510
                        'invisible',
511
                        null,
512
                        $group_info,
513
                        null,
514
                        null,
515
                        null
516
                    );
517
                }
518
            }
519
        }
520
521
        $filename_chat = 'messages-'.$date_now.'.log.html';
522
523
        if ($this->groupId && !$friendId) {
524
            $filename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId.'.log.html';
525
        } elseif ($this->sessionId && !$friendId) {
526
            $filename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId.'.log.html';
527
        } elseif ($friendId) {
528
            if ($this->userId < $friendId) {
529
                $filename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
530
            } else {
531
                $filename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
532
            }
533
        }
534
535
        if (!file_exists($chat_path.$filename_chat)) {
536
            @fclose(fopen($chat_path.$filename_chat, 'w'));
537
            if (!api_is_anonymous()) {
538
                $doc_id = add_document(
539
                    $courseInfo,
540
                    $basepath_chat.'/'.$filename_chat,
541
                    'file',
542
                    0,
543
                    $filename_chat,
544
                    null,
545
                    0,
546
                    true,
547
                    0,
548
                    0,
549
                    0,
550
                    false
551
                );
552
                if ($doc_id) {
553
                    api_item_property_update(
554
                        $courseInfo,
555
                        TOOL_DOCUMENT,
556
                        $doc_id,
557
                        'DocumentAdded',
558
                        $this->userId,
559
                        $group_info,
560
                        null,
561
                        null,
562
                        null,
563
                        $this->sessionId
564
                    );
565
                    api_item_property_update(
566
                        $courseInfo,
567
                        TOOL_DOCUMENT,
568
                        $doc_id,
569
                        'invisible',
570
                        $this->userId,
571
                        $group_info,
572
                        null,
573
                        null,
574
                        null,
575
                        $this->sessionId
576
                    );
577
                    item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
578
                }
579
            }
580
        }
581
582
        $basename_chat = 'messages-'.$date_now;
583
        if ($this->groupId && !$friendId) {
584
            $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
585
        } elseif ($this->sessionId && !$friendId) {
586
            $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
587
        } elseif ($friendId) {
588
            if ($this->userId < $friendId) {
589
                $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
590
            } else {
591
                $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
592
            }
593
        }
594
595
        if ($reset && $isMaster) {
596
            $i = 1;
597
            while (file_exists($chat_path.$basename_chat.'-'.$i.'.log.html')) {
598
                $i++;
599
            }
600
601
            @rename($chat_path.$basename_chat.'.log.html', $chat_path.$basename_chat.'-'.$i.'.log.html');
602
            @fclose(fopen($chat_path.$basename_chat.'.log.html', 'w'));
603
604
            $doc_id = add_document(
605
                $courseInfo,
606
                $basepath_chat.'/'.$basename_chat.'-'.$i.'.log.html',
607
                'file',
608
                filesize($chat_path.$basename_chat.'-'.$i.'.log.html'),
609
                $basename_chat.'-'.$i.'.log.html',
610
                null,
611
                0,
612
                true,
613
                0,
614
                0,
615
                0,
616
                false
617
            );
618
619
            api_item_property_update(
620
                $courseInfo,
621
                TOOL_DOCUMENT,
622
                $doc_id,
623
                'DocumentAdded',
624
                $this->userId,
625
                $group_info,
626
                null,
627
                null,
628
                null,
629
                $this->sessionId
630
            );
631
            api_item_property_update(
632
                $courseInfo,
633
                TOOL_DOCUMENT,
634
                $doc_id,
635
                'invisible',
636
                $this->userId,
637
                $group_info,
638
                null,
639
                null,
640
                null,
641
                $this->sessionId
642
            );
643
            item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
644
            $doc_id = DocumentManager::get_document_id(
645
                $courseInfo,
646
                $basepath_chat.'/'.$basename_chat.'.log.html'
647
            );
648
            update_existing_document($courseInfo, $doc_id, 0);
649
        }
650
651
        $remove = 0;
652
        $content = [];
653
654
        if (file_exists($chat_path.$basename_chat.'.log.html')) {
655
            $content = file($chat_path.$basename_chat.'.log.html');
656
            $nbr_lines = sizeof($content);
657
            $remove = $nbr_lines - 100;
658
        }
659
660
        if ($remove < 0) {
661
            $remove = 0;
662
        }
663
664
        array_splice($content, 0, $remove);
665
666
        if (isset($_GET['origin']) && $_GET['origin'] == 'whoisonline') {
667
            //the caller
668
            $content[0] = get_lang('CallSent').'<br />'.$content[0];
669
        }
670
671
        $history = '<div id="content-chat">';
672
        foreach ($content as $this_line) {
673
            $history .= $this_line;
674
        }
675
        $history .= '</div>';
676
677
        if ($isMaster || $GLOBALS['is_session_general_coach']) {
678
            $history .= '
679
                <div id="clear-chat">
680
                    <button type="button" id="chat-reset" class="btn btn-danger btn-sm">
681
                        '.get_lang('ClearList').'
682
                    </button>
683
                </div>
684
            ';
685
        }
686
687
        return $history;
688
    }
689
690
    /**
691
     * Get the number of users connected in chat.
692
     *
693
     * @return int
694
     */
695
    public function countUsersOnline()
696
    {
697
        $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
698
        $date->modify('-5 seconds');
699
700
        if ($this->groupId) {
701
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
702
        } else {
703
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
704
        }
705
706
        $number = Database::getManager()
707
            ->createQuery("
708
                SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
709
                WHERE ccc.lastConnection > :date AND ccc.cId = :course $extraCondition
710
            ")
711
            ->setParameters([
712
                'date' => $date,
713
                'course' => $this->courseId,
714
            ])
715
            ->getSingleScalarResult();
716
717
        return (int) $number;
718
    }
719
720
    /**
721
     * Get the users online data.
722
     *
723
     * @throws \Doctrine\ORM\ORMException
724
     * @throws \Doctrine\ORM\OptimisticLockException
725
     * @throws \Doctrine\ORM\TransactionRequiredException
726
     *
727
     * @return array
728
     */
729
    public function listUsersOnline()
730
    {
731
        $subscriptions = $this->getUsersSubscriptions();
732
        $usersInfo = [];
733
734
        if ($this->groupId) {
735
            /** @var User $groupUser */
736
            foreach ($subscriptions as $groupUser) {
737
                $usersInfo[] = $this->formatUser(
738
                    $groupUser,
739
                    $groupUser->getStatus()
740
                );
741
            }
742
        } else {
743
            /** @var CourseRelUser|SessionRelCourseRelUser $subscription */
744
            foreach ($subscriptions as $subscription) {
745
                $user = $subscription->getUser();
746
                $usersInfo[] = $this->formatUser(
747
                    $user,
748
                    $this->sessionId ? $user->getStatus() : $subscription->getStatus()
749
                );
750
            }
751
        }
752
753
        return $usersInfo;
754
    }
755
756
    /**
757
     * Format the user data to return it in the user list.
758
     *
759
     * @param int $status
760
     *
761
     * @return array
762
     */
763
    private function formatUser(User $user, $status)
764
    {
765
        return [
766
            'id' => $user->getId(),
767
            'firstname' => $user->getFirstname(),
768
            'lastname' => $user->getLastname(),
769
            'status' => $status,
770
            'image_url' => UserManager::getUserPicture($user->getId(), USER_IMAGE_SIZE_MEDIUM),
771
            'profile_url' => api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user->getId(),
772
            'complete_name' => UserManager::formatUserFullName($user),
773
            'username' => $user->getUsername(),
774
            'email' => $user->getEmail(),
775
            'isConnected' => $this->userIsConnected($user->getId()),
776
        ];
777
    }
778
779
    /**
780
     * Get the users subscriptions (SessionRelCourseRelUser array or CourseRelUser array) for chat.
781
     *
782
     * @throws \Doctrine\ORM\ORMException
783
     * @throws \Doctrine\ORM\OptimisticLockException
784
     * @throws \Doctrine\ORM\TransactionRequiredException
785
     *
786
     * @return \Doctrine\Common\Collections\ArrayCollection
787
     */
788
    private function getUsersSubscriptions()
789
    {
790
        $em = Database::getManager();
791
792
        if ($this->groupId) {
793
            $students = $em
794
                ->createQuery(
795
                    'SELECT u FROM ChamiloUserBundle:User u
796
                    INNER JOIN ChamiloCourseBundle:CGroupRelUser gru
797
                        WITH u.id = gru.userId AND gru.cId = :course
798
                    WHERE u.id != :user AND gru.groupId = :group
799
                        AND u.active = true'
800
                )
801
                ->setParameters(['course' => $this->courseId, 'user' => $this->userId, 'group' => $this->groupId])
802
                ->getResult();
803
            $tutors = $em
804
                ->createQuery(
805
                    'SELECT u FROM ChamiloUserBundle:User u
806
                    INNER JOIN ChamiloCourseBundle:CGroupRelTutor grt
807
                        WITH u.id = grt.userId AND grt.cId = :course
808
                    WHERE u.id != :user AND grt.groupId = :group
809
                        AND u.active = true'
810
                )
811
                ->setParameters(['course' => $this->courseId, 'user' => $this->userId, 'group' => $this->groupId])
812
                ->getResult();
813
814
            return array_merge($tutors, $students);
815
        }
816
817
        /** @var Course $course */
818
        $course = $em->find('ChamiloCoreBundle:Course', $this->courseId);
819
820
        if ($this->sessionId) {
821
            /** @var Session $session */
822
            $session = $em->find('ChamiloCoreBundle:Session', $this->sessionId);
823
            $criteria = Criteria::create()->where(Criteria::expr()->eq('course', $course));
824
            $userIsCoach = api_is_course_session_coach($this->userId, $course->getId(), $session->getId());
825
826
            if (api_get_configuration_value('course_chat_restrict_to_coach')) {
827
                if ($userIsCoach) {
828
                    $criteria->andWhere(
829
                        Criteria::expr()->eq('status', Session::STUDENT)
830
                    );
831
                } else {
832
                    $criteria->andWhere(
833
                        Criteria::expr()->eq('status', Session::COACH)
834
                    );
835
                }
836
            }
837
838
            $criteria->orderBy(['status' => Criteria::DESC]);
839
840
            return $session
841
                ->getUserCourseSubscriptions()
842
                ->matching($criteria)
843
                ->filter(function (SessionRelCourseRelUser $sessionRelCourseRelUser) {
844
                    return $sessionRelCourseRelUser->getUser()->isActive();
845
                });
846
        }
847
848
        return $course
849
            ->getUsers()
850
            ->filter(function (CourseRelUser $courseRelUser) {
851
                return $courseRelUser->getUser()->isActive();
852
            });
853
    }
854
855
    /**
856
     * Check if a user is connected in course chat.
857
     *
858
     * @param int $userId
859
     *
860
     * @return int
861
     */
862
    private function userIsConnected($userId)
863
    {
864
        $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
865
        $date->modify('-5 seconds');
866
867
        if ($this->groupId) {
868
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
869
        } else {
870
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
871
        }
872
873
        $number = Database::getManager()
874
            ->createQuery("
875
                SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
876
                WHERE ccc.lastConnection > :date AND ccc.cId = :course AND ccc.userId = :user $extraCondition
877
            ")
878
            ->setParameters([
879
                'date' => $date,
880
                'course' => $this->courseId,
881
                'user' => $userId,
882
            ])
883
            ->getSingleScalarResult();
884
885
        return (int) $number;
886
    }
887
}
888