Completed
Push — master ( b77cfe...000f93 )
by Julito
22:58 queued 11:19
created

CourseChatUtils::countUsersOnline()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 0
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Course;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Course. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
5
use Chamilo\CoreBundle\Entity\CourseRelUser;
6
use Chamilo\CoreBundle\Entity\Session;
7
use Chamilo\CourseBundle\Entity\CChatConnected;
8
use Doctrine\Common\Collections\Criteria;
9
use Michelf\MarkdownExtra;
10
11
/**
12
 * Class CourseChat
13
 * Manage the chat for a course.
14
 */
15
class CourseChatUtils
16
{
17
    private $groupId;
18
    private $courseId;
19
    private $sessionId;
20
    private $userId;
21
22
    /**
23
     * CourseChat constructor.
24
     *
25
     * @param int $courseId
26
     * @param int $userId
27
     * @param int $sessionId
28
     * @param int $groupId
29
     */
30
    public function __construct($courseId, $userId, $sessionId = 0, $groupId = 0)
31
    {
32
        $this->courseId = (int) $courseId;
33
        $this->userId = (int) $userId;
34
        $this->sessionId = (int) $sessionId;
35
        $this->groupId = (int) $groupId;
36
    }
37
38
    /**
39
     * Prepare a message. Clean and insert emojis.
40
     *
41
     * @param string $message The message to prepare
42
     *
43
     * @return string
44
     */
45
    public static function prepareMessage($message)
46
    {
47
        if (empty($message)) {
48
            return '';
49
        }
50
51
        Emojione\Emojione::$imagePathPNG = api_get_path(WEB_LIBRARY_PATH).'javascript/emojione/png/';
52
        Emojione\Emojione::$ascii = true;
53
54
        $message = trim($message);
55
        $message = nl2br($message);
56
        // Security XSS
57
        $message = Security::remove_XSS($message);
58
        //search urls
59
        $message = preg_replace(
60
            '@((https?://)?([-\w]+\.[-\w\.]+)+\w(:\d+)?(/([-\w/_\.]*(\?\S+)?)?)*)@',
61
            '<a href="$1" target="_blank">$1</a>',
62
            $message
63
        );
64
        // add "http://" if not set
65
        $message = preg_replace(
66
            '/<a\s[^>]*href\s*=\s*"((?!https?:\/\/)[^"]*)"[^>]*>/i',
67
            '<a href="http://$1" target="_blank">',
68
            $message
69
        );
70
        // Parsing emojis
71
        $message = Emojione\Emojione::toImage($message);
72
        // Parsing text to understand markdown (code highlight)
73
        $message = MarkdownExtra::defaultTransform($message);
74
75
        return $message;
76
    }
77
78
    /**
79
     * Save a chat message in a HTML file.
80
     *
81
     * @param string $message
82
     * @param int    $friendId
83
     *
84
     * @return bool
85
     */
86
    public function saveMessage($message, $friendId = 0)
87
    {
88
        if (empty($message)) {
89
            return false;
90
        }
91
        $user = api_get_user_entity($this->userId);
92
        $courseInfo = api_get_course_info_by_id($this->courseId);
93
        $isMaster = (bool) api_is_course_admin();
94
        $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
95
        $basepath_chat = '/chat_files';
96
        $group_info = [];
97
        if (!$this->groupId) {
98
            $group_info = GroupManager::get_group_properties($this->groupId);
99
            $basepath_chat = $group_info['directory'].'/chat_files';
100
        }
101
102
        $chat_path = $document_path.$basepath_chat.'/';
103
104
        if (!is_dir($chat_path)) {
105
            if (is_file($chat_path)) {
106
                @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

106
                /** @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...
107
            }
108
        }
109
110
        $date_now = date('Y-m-d');
111
        $timeNow = date('d/m/y H:i:s');
112
        $basename_chat = 'messages-'.$date_now;
113
114
        if ($this->groupId && !$friendId) {
115
            $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
116
        } elseif ($this->sessionId && !$friendId) {
117
            $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
118
        } elseif ($friendId) {
119
            if ($this->userId < $friendId) {
120
                $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
121
            } else {
122
                $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
123
            }
124
        }
125
126
        $message = self::prepareMessage($message);
127
128
        $fileTitle = $basename_chat.'.log.html';
129
        $filePath = $basepath_chat.'/'.$fileTitle;
130
        $absoluteFilePath = $chat_path.$fileTitle;
131
132
        if (!file_exists($absoluteFilePath)) {
133
            $doc_id = add_document(
134
                $courseInfo,
135
                $filePath,
136
                'file',
137
                0,
138
                $fileTitle,
139
                null,
140
                0,
141
                true,
142
                0,
143
                0,
144
                0,
145
                false
146
            );
147
            $documentLogTypes = ['DocumentAdded', 'invisible'];
148
149
            foreach ($documentLogTypes as $logType) {
150
                api_item_property_update(
151
                    $courseInfo,
152
                    TOOL_DOCUMENT,
153
                    $doc_id,
154
                    $logType,
155
                    $this->userId,
156
                    $group_info,
157
                    null,
158
                    null,
159
                    null,
160
                    $this->sessionId
161
                );
162
            }
163
164
            item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
165
        } else {
166
            $doc_id = DocumentManager::get_document_id($courseInfo, $filePath);
167
        }
168
169
        $fp = fopen($absoluteFilePath, 'a');
170
        $userPhoto = UserManager::getUserPicture($this->userId, USER_IMAGE_SIZE_MEDIUM);
171
172
        if ($isMaster) {
173
            $fileContent = '
174
                <div class="message-teacher">
175
                    <div class="content-message">
176
                        <div class="chat-message-block-name">'.$user->getCompleteName().'</div>
177
                        <div class="chat-message-block-content">'.$message.'</div>
178
                        <div class="message-date">'.$timeNow.'</div>
179
                    </div>
180
                    <div class="icon-message"></div>
181
                    <img class="chat-image" src="'.$userPhoto.'">
182
                </div>
183
            ';
184
        } else {
185
            $fileContent = '
186
                <div class="message-student">
187
                    <img class="chat-image" src="'.$userPhoto.'">
188
                    <div class="icon-message"></div>
189
                    <div class="content-message">
190
                        <div class="chat-message-block-name">'.$user->getCompleteName().'</div>
191
                        <div class="chat-message-block-content">'.$message.'</div>
192
                        <div class="message-date">'.$timeNow.'</div>
193
                    </div>
194
                </div>
195
            ';
196
        }
197
198
        fputs($fp, $fileContent);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fputs() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

198
        fputs(/** @scrutinizer ignore-type */ $fp, $fileContent);
Loading history...
199
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

199
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
200
        $size = filesize($absoluteFilePath);
201
        update_existing_document($courseInfo, $doc_id, $size);
202
        item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
203
204
        return true;
205
    }
206
207
    /**
208
     * Disconnect a user from course chats.
209
     *
210
     * @param int $userId
211
     */
212
    public static function exitChat($userId)
213
    {
214
        $listCourse = CourseManager::get_courses_list_by_user_id($userId);
215
216
        foreach ($listCourse as $course) {
217
            Database::getManager()
218
                ->createQuery('
219
                    DELETE FROM ChamiloCourseBundle:CChatConnected ccc
220
                    WHERE ccc.cId = :course AND ccc.userId = :user
221
                ')
222
                ->execute([
223
                    'course' => intval($course['real_id']),
224
                    'user' => intval($userId),
225
                ]);
226
        }
227
    }
228
229
    /**
230
     * Disconnect users who are more than 5 seconds inactive.
231
     */
232
    public function disconnectInactiveUsers()
233
    {
234
        $em = Database::getManager();
235
        $extraCondition = "AND ccc.toGroupId = {$this->groupId}";
236
        if (empty($this->groupId)) {
237
            $extraCondition = "AND ccc.sessionId = {$this->sessionId}";
238
        }
239
240
        $connectedUsers = $em
241
            ->createQuery("
242
                SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
243
                WHERE ccc.cId = :course $extraCondition
244
            ")
245
            ->setParameter('course', $this->courseId)
246
            ->getResult();
247
248
        $now = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
249
        $cd_count_time_seconds = $now->getTimestamp();
250
        /** @var CChatConnected $connection */
251
        foreach ($connectedUsers as $connection) {
252
            $date_count_time_seconds = $connection->getLastConnection()->getTimestamp();
253
            if (strcmp($now->format('Y-m-d'), $connection->getLastConnection()->format('Y-m-d')) !== 0) {
254
                continue;
255
            }
256
257
            if (($cd_count_time_seconds - $date_count_time_seconds) <= 5) {
258
                continue;
259
            }
260
261
            $em
262
                ->createQuery('
263
                    DELETE FROM ChamiloCourseBundle:CChatConnected ccc
264
                    WHERE ccc.cId = :course AND ccc.userId = :user AND ccc.toGroupId = :group
265
                ')
266
                ->execute([
267
                    'course' => $this->courseId,
268
                    'user' => $connection->getUserId(),
269
                    'group' => $this->groupId,
270
                ]);
271
        }
272
    }
273
274
    /**
275
     * Keep registered to a user as connected.
276
     *
277
     * @throws \Doctrine\ORM\NonUniqueResultException
278
     */
279
    public function keepUserAsConnected()
280
    {
281
        $em = Database::getManager();
282
        $extraCondition = null;
283
284
        if ($this->groupId) {
285
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
286
        } else {
287
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
288
        }
289
290
        $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
291
292
        $connection = $em
293
            ->createQuery("
294
                SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
295
                WHERE ccc.userId = :user AND ccc.cId = :course $extraCondition 
296
            ")
297
            ->setParameters([
298
                'user' => $this->userId,
299
                'course' => $this->courseId,
300
            ])
301
            ->getOneOrNullResult();
302
303
        if ($connection) {
304
            $connection->setLastConnection($currentTime);
305
            $em->merge($connection);
306
            $em->flush();
307
308
            return;
309
        }
310
311
        $connection = new CChatConnected();
312
        $connection
313
            ->setCId($this->courseId)
314
            ->setUserId($this->userId)
315
            ->setLastConnection($currentTime)
316
            ->setSessionId($this->sessionId)
317
            ->setToGroupId($this->groupId);
318
319
        $em->persist($connection);
320
        $em->flush();
321
    }
322
323
    /**
324
     * Get the emoji allowed on course chat.
325
     *
326
     * @return array
327
     */
328
    public static function getEmojiStrategy()
329
    {
330
        return require_once api_get_path(SYS_CODE_PATH).'chat/emoji_strategy.php';
331
    }
332
333
    /**
334
     * Get the emoji list to include in chat.
335
     *
336
     * @return array
337
     */
338
    public static function getEmojisToInclude()
339
    {
340
        return [
341
            ':bowtie:',
342
            ':smile:' |
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
343
            ':laughing:',
344
            ':blush:',
345
            ':smiley:',
346
            ':relaxed:',
347
            ':smirk:',
348
            ':heart_eyes:',
349
            ':kissing_heart:',
350
            ':kissing_closed_eyes:',
351
            ':flushed:',
352
            ':relieved:',
353
            ':satisfied:',
354
            ':grin:',
355
            ':wink:',
356
            ':stuck_out_tongue_winking_eye:',
357
            ':stuck_out_tongue_closed_eyes:',
358
            ':grinning:',
359
            ':kissing:',
360
            ':kissing_smiling_eyes:',
361
            ':stuck_out_tongue:',
362
            ':sleeping:',
363
            ':worried:',
364
            ':frowning:',
365
            ':anguished:',
366
            ':open_mouth:',
367
            ':grimacing:',
368
            ':confused:',
369
            ':hushed:',
370
            ':expressionless:',
371
            ':unamused:',
372
            ':sweat_smile:',
373
            ':sweat:',
374
            ':disappointed_relieved:',
375
            ':weary:',
376
            ':pensive:',
377
            ':disappointed:',
378
            ':confounded:',
379
            ':fearful:',
380
            ':cold_sweat:',
381
            ':persevere:',
382
            ':cry:',
383
            ':sob:',
384
            ':joy:',
385
            ':astonished:',
386
            ':scream:',
387
            ':neckbeard:',
388
            ':tired_face:',
389
            ':angry:',
390
            ':rage:',
391
            ':triumph:',
392
            ':sleepy:',
393
            ':yum:',
394
            ':mask:',
395
            ':sunglasses:',
396
            ':dizzy_face:',
397
            ':imp:',
398
            ':smiling_imp:',
399
            ':neutral_face:',
400
            ':no_mouth:',
401
            ':innocent:',
402
            ':alien:',
403
        ];
404
    }
405
406
    /**
407
     * Get the chat history file name.
408
     *
409
     * @param bool $absolute Optional. Whether get the base or the absolute file path
410
     * @param int  $friendId optional
411
     *
412
     * @return string
413
     */
414
    public function getFileName($absolute = false, $friendId = 0)
415
    {
416
        $date = date('Y-m-d');
417
        $base = 'messages-'.$date.'.log.html';
418
419
        if ($this->groupId && !$friendId) {
420
            $base = 'messages-'.$date.'_gid-'.$this->groupId.'.log.html';
421
        } elseif ($this->sessionId && !$friendId) {
422
            $base = 'messages-'.$date.'_sid-'.$this->sessionId.'.log.html';
423
        } elseif ($friendId) {
424
            if ($this->userId < $friendId) {
425
                $base = 'messages-'.$date.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
426
            } else {
427
                $base = 'messages-'.$date.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
428
            }
429
        }
430
431
        if (!$absolute) {
432
            return $base;
433
        }
434
435
        $courseInfo = api_get_course_info_by_id($this->courseId);
436
        $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
437
        $chatPath = $document_path.'/chat_files/';
438
439
        if ($this->groupId) {
440
            $group_info = GroupManager::get_group_properties($this->groupId);
441
            $chatPath = $document_path.$group_info['directory'].'/chat_files/';
442
        }
443
444
        return $chatPath.$base;
445
    }
446
447
    /**
448
     * Get the chat history.
449
     *
450
     * @param bool $reset
451
     * @param int  $friendId optional
452
     *
453
     * @return string
454
     */
455
    public function readMessages($reset = false, $friendId = 0)
456
    {
457
        $courseInfo = api_get_course_info_by_id($this->courseId);
458
        $date_now = date('Y-m-d');
459
        $isMaster = (bool) api_is_course_admin();
460
        $basepath_chat = '/chat_files';
461
        $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
462
        $group_info = [];
463
        if ($this->groupId) {
464
            $group_info = GroupManager:: get_group_properties($this->groupId);
465
            $basepath_chat = $group_info['directory'].'/chat_files';
466
        }
467
468
        $chat_path = $document_path.$basepath_chat.'/';
469
470
        if (!is_dir($chat_path)) {
471
            if (is_file($chat_path)) {
472
                @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

472
                /** @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...
473
            }
474
475
            if (!api_is_anonymous()) {
476
                @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

476
                /** @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...
477
                // Save chat files document for group into item property
478
                if ($this->groupId) {
479
                    $doc_id = add_document(
480
                        $courseInfo,
481
                        $basepath_chat,
482
                        'folder',
483
                        0,
484
                        'chat_files',
485
                        null,
486
                        0,
487
                        true,
488
                        0,
489
                        0,
490
                        0,
491
                        false
492
                    );
493
                    api_item_property_update(
494
                        $courseInfo,
495
                        TOOL_DOCUMENT,
496
                        $doc_id,
497
                        'FolderCreated',
498
                        null,
499
                        $group_info,
500
                        null,
501
                        null,
502
                        null
503
                    );
504
                }
505
            }
506
        }
507
508
        $filename_chat = 'messages-'.$date_now.'.log.html';
509
510
        if ($this->groupId && !$friendId) {
511
            $filename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId.'.log.html';
512
        } elseif ($this->sessionId && !$friendId) {
513
            $filename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId.'.log.html';
514
        } elseif ($friendId) {
515
            if ($this->userId < $friendId) {
516
                $filename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
517
            } else {
518
                $filename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
519
            }
520
        }
521
522
        if (!file_exists($chat_path.$filename_chat)) {
523
            @fclose(fopen($chat_path.$filename_chat, 'w'));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). 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

523
            /** @scrutinizer ignore-unhandled */ @fclose(fopen($chat_path.$filename_chat, 'w'));

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...
Bug introduced by
It seems like fopen($chat_path . $filename_chat, 'w') can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

523
            @fclose(/** @scrutinizer ignore-type */ fopen($chat_path.$filename_chat, 'w'));
Loading history...
524
            if (!api_is_anonymous()) {
525
                $doc_id = add_document(
526
                    $courseInfo,
527
                    $basepath_chat.'/'.$filename_chat,
528
                    'file',
529
                    0,
530
                    $filename_chat,
531
                    null,
532
                    0,
533
                    true,
534
                    0,
535
                    0,
536
                    0,
537
                    false
538
                );
539
                if ($doc_id) {
540
                    api_item_property_update(
541
                        $courseInfo,
542
                        TOOL_DOCUMENT,
543
                        $doc_id,
544
                        'DocumentAdded',
545
                        $this->userId,
546
                        $group_info,
547
                        null,
548
                        null,
549
                        null,
550
                        $this->sessionId
551
                    );
552
                    api_item_property_update(
553
                        $courseInfo,
554
                        TOOL_DOCUMENT,
555
                        $doc_id,
556
                        'invisible',
557
                        $this->userId,
558
                        $group_info,
559
                        null,
560
                        null,
561
                        null,
562
                        $this->sessionId
563
                    );
564
                    item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
565
                }
566
            }
567
        }
568
569
        $basename_chat = 'messages-'.$date_now;
570
        if ($this->groupId && !$friendId) {
571
            $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
572
        } elseif ($this->sessionId && !$friendId) {
573
            $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
574
        } elseif ($friendId) {
575
            if ($this->userId < $friendId) {
576
                $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
577
            } else {
578
                $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
579
            }
580
        }
581
582
        if ($reset && $isMaster) {
583
            $i = 1;
584
            while (file_exists($chat_path.$basename_chat.'-'.$i.'.log.html')) {
585
                $i++;
586
            }
587
588
            @rename($chat_path.$basename_chat.'.log.html', $chat_path.$basename_chat.'-'.$i.'.log.html');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). 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

588
            /** @scrutinizer ignore-unhandled */ @rename($chat_path.$basename_chat.'.log.html', $chat_path.$basename_chat.'-'.$i.'.log.html');

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...
589
            @fclose(fopen($chat_path.$basename_chat.'.log.html', 'w'));
590
591
            $doc_id = add_document(
592
                $courseInfo,
593
                $basepath_chat.'/'.$basename_chat.'-'.$i.'.log.html',
594
                'file',
595
                filesize($chat_path.$basename_chat.'-'.$i.'.log.html'),
596
                $basename_chat.'-'.$i.'.log.html',
597
                null,
598
                0,
599
                true,
600
                0,
601
                0,
602
                0,
603
                false
604
            );
605
606
            api_item_property_update(
607
                $courseInfo,
608
                TOOL_DOCUMENT,
609
                $doc_id,
610
                'DocumentAdded',
611
                $this->userId,
612
                $group_info,
613
                null,
614
                null,
615
                null,
616
                $this->sessionId
617
            );
618
            api_item_property_update(
619
                $courseInfo,
620
                TOOL_DOCUMENT,
621
                $doc_id,
622
                'invisible',
623
                $this->userId,
624
                $group_info,
625
                null,
626
                null,
627
                null,
628
                $this->sessionId
629
            );
630
            item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
631
            $doc_id = DocumentManager::get_document_id(
632
                $courseInfo,
633
                $basepath_chat.'/'.$basename_chat.'.log.html'
634
            );
635
            update_existing_document($courseInfo, $doc_id, 0);
636
        }
637
638
        $remove = 0;
639
        $content = [];
640
641
        if (file_exists($chat_path.$basename_chat.'.log.html')) {
642
            $content = file($chat_path.$basename_chat.'.log.html');
643
            $nbr_lines = sizeof($content);
644
            $remove = $nbr_lines - 100;
645
        }
646
647
        if ($remove < 0) {
648
            $remove = 0;
649
        }
650
651
        array_splice($content, 0, $remove);
0 ignored issues
show
Bug introduced by
It seems like $content can also be of type false; however, parameter $input of array_splice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

651
        array_splice(/** @scrutinizer ignore-type */ $content, 0, $remove);
Loading history...
652
653
        if (isset($_GET['origin']) && $_GET['origin'] == 'whoisonline') {
654
            //the caller
655
            $content[0] = get_lang('CallSent').'<br />'.$content[0];
656
        }
657
658
        $history = '<div id="content-chat">';
659
        foreach ($content as $this_line) {
660
            $history .= $this_line;
661
        }
662
        $history .= '</div>';
663
664
        if ($isMaster || $GLOBALS['is_session_general_coach']) {
665
            $history .= '
666
                <div id="clear-chat">
667
                    <button type="button" id="chat-reset" class="btn btn-danger btn-sm">
668
                        '.get_lang('ClearList').'
669
                    </button>
670
                </div>
671
            ';
672
        }
673
674
        return $history;
675
    }
676
677
    /**
678
     * Get the number of users connected in chat.
679
     *
680
     * @return int
681
     */
682
    public function countUsersOnline()
683
    {
684
        $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
685
        $date->modify('-5 seconds');
686
687
        if ($this->groupId) {
688
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
689
        } else {
690
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
691
        }
692
693
        $number = Database::getManager()
694
            ->createQuery("
695
                SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
696
                WHERE ccc.lastConnection > :date AND ccc.cId = :course $extraCondition
697
            ")
698
            ->setParameters([
699
                'date' => $date,
700
                'course' => $this->courseId,
701
            ])
702
            ->getSingleScalarResult();
703
704
        return (int) $number;
705
    }
706
707
    /**
708
     * Get the users online data.
709
     *
710
     * @return array
711
     */
712
    public function listUsersOnline()
713
    {
714
        $subscriptions = $this->getUsersSubscriptions();
715
        $usersInfo = [];
716
        /** @var CourseRelUser $subscription */
717
        foreach ($subscriptions as $subscription) {
718
            $user = $subscription->getUser();
719
            $usersInfo[] = [
720
                'id' => $user->getId(),
721
                'firstname' => $user->getFirstname(),
722
                'lastname' => $user->getLastname(),
723
                'status' => !$this->sessionId ? $subscription->getStatus() : $user->getStatus(),
724
                'image_url' => UserManager::getUserPicture($user->getId(), USER_IMAGE_SIZE_MEDIUM),
725
                'profile_url' => api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user->getId(),
726
                'complete_name' => $user->getCompleteName(),
727
                'username' => $user->getUsername(),
728
                'email' => $user->getEmail(),
729
                'isConnected' => $this->userIsConnected($user->getId()),
730
            ];
731
        }
732
733
        return $usersInfo;
734
    }
735
736
    /**
737
     * Get the users subscriptions (SessionRelCourseRelUser array or CourseRelUser array) for chat.
738
     *
739
     * @throws \Doctrine\ORM\ORMException
740
     * @throws \Doctrine\ORM\OptimisticLockException
741
     * @throws \Doctrine\ORM\TransactionRequiredException
742
     *
743
     * @return \Doctrine\Common\Collections\ArrayCollection
744
     */
745
    private function getUsersSubscriptions()
746
    {
747
        $em = Database::getManager();
748
        /** @var Course $course */
749
        $course = $em->find('ChamiloCoreBundle:Course', $this->courseId);
750
751
        if ($this->sessionId) {
752
            /** @var Session $session */
753
            $session = $em->find('ChamiloCoreBundle:Session', $this->sessionId);
754
            $criteria = Criteria::create()->where(Criteria::expr()->eq('course', $course));
755
            $userIsCoach = api_is_course_session_coach($this->userId, $course->getId(), $session->getId());
756
757
            if (api_get_configuration_value('course_chat_restrict_to_coach')) {
758
                if ($userIsCoach) {
759
                    $criteria->andWhere(
760
                        Criteria::expr()->eq('status', Session::STUDENT)
761
                    );
762
                } else {
763
                    $criteria->andWhere(
764
                        Criteria::expr()->eq('status', Session::COACH)
765
                    );
766
                }
767
            }
768
769
            $criteria->orderBy(['status' => Criteria::DESC]);
770
771
            return $session
772
                ->getUserCourseSubscriptions()
773
                ->matching($criteria);
774
        }
775
776
        return $course->getUsers();
777
    }
778
779
    /**
780
     * Check if a user is connected in course chat.
781
     *
782
     * @param int $userId
783
     *
784
     * @return int
785
     */
786
    private function userIsConnected($userId)
787
    {
788
        $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
789
        $date->modify('-5 seconds');
790
791
        if ($this->groupId) {
792
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
793
        } else {
794
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
795
        }
796
797
        $number = Database::getManager()
798
            ->createQuery("
799
                SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
800
                WHERE ccc.lastConnection > :date AND ccc.cId = :course AND ccc.userId = :user $extraCondition
801
            ")
802
            ->setParameters([
803
                'date' => $date,
804
                'course' => $this->courseId,
805
                'user' => $userId,
806
            ])
807
            ->getSingleScalarResult();
808
809
        return (int) $number;
810
    }
811
}
812