Completed
Push — master ( bdb7fb...d3d147 )
by Julito
25:16
created

CourseChatUtils   F

Complexity

Total Complexity 79

Size/Duplication

Total Lines 741
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 741
rs 1.6438
c 0
b 0
f 0
wmc 79

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B prepareMessage() 0 31 2
A getEmojiStrategy() 0 3 1
D saveMessage() 0 108 14
B getUsersSubscriptions() 0 32 4
A exitChat() 0 13 2
B countUsersOnline() 0 25 2
B disconnectInactiveUsers() 0 40 5
B userIsConnected() 0 26 2
A listUsersOnline() 0 23 3
D getFileName() 0 32 9
F readMessages() 0 185 30
A getEmojisToInclude() 0 65 1
B keepUserAsConnected() 0 42 3

How to fix   Complexity   

Complex Class

Complex classes like CourseChatUtils often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CourseChatUtils, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Michelf\MarkdownExtra;
5
use Doctrine\Common\Collections\Criteria;
6
use Chamilo\CourseBundle\Entity\CChatConnected;
7
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...
8
use Chamilo\CoreBundle\Entity\Session;
9
10
/**
11
 * Class CourseChat
12
 * Manage the chat for a course
13
 */
14
class CourseChatUtils
15
{
16
    private $groupId;
17
    private $courseId;
18
    private $sessionId;
19
    private $userId;
20
21
    /**
22
     * CourseChat constructor.
23
     * @param int $courseId
24
     * @param int $userId
25
     * @param int $sessionId
26
     * @param int $groupId
27
     */
28
    public function __construct($courseId, $userId, $sessionId = 0, $groupId = 0)
29
    {
30
        $this->courseId = (int) $courseId;
31
        $this->userId = (int) $userId;
32
        $this->sessionId = (int) $sessionId;
33
        $this->groupId = (int) $groupId;
34
    }
35
36
    /**
37
     * Get the users subscriptions (SessionRelCourseRelUser array or CourseRelUser array) for chat
38
     * @return \Doctrine\Common\Collections\ArrayCollection
39
     * @throws \Doctrine\ORM\ORMException
40
     * @throws \Doctrine\ORM\OptimisticLockException
41
     * @throws \Doctrine\ORM\TransactionRequiredException
42
     */
43
    private function getUsersSubscriptions()
44
    {
45
        $em = Database::getManager();
46
        /** @var Course $course */
47
        $course = $em->find('ChamiloCoreBundle:Course', $this->courseId);
48
49
        if ($this->sessionId) {
50
            /** @var Session $session */
51
            $session = $em->find('ChamiloCoreBundle:Session', $this->sessionId);
52
            $criteria = Criteria::create()->where(Criteria::expr()->eq('course', $course));
53
            $userIsCoach = api_is_course_session_coach($this->userId, $course->getId(), $session->getId());
54
55
            if (api_get_configuration_value('course_chat_restrict_to_coach')) {
56
                if ($userIsCoach) {
57
                    $criteria->andWhere(
58
                        Criteria::expr()->eq('status', Session::STUDENT)
59
                    );
60
                } else {
61
                    $criteria->andWhere(
62
                        Criteria::expr()->eq('status', Session::COACH)
63
                    );
64
                }
65
            }
66
67
            $criteria->orderBy(['status' => Criteria::DESC]);
68
69
            return $session
70
                ->getUserCourseSubscriptions()
71
                ->matching($criteria);
72
        }
73
74
        return $course->getUsers();
75
    }
76
77
    /**
78
     * Prepare a message. Clean and insert emojis
79
     * @param string $message The message to prepare
80
     * @return string
81
     */
82
    public static function prepareMessage($message)
83
    {
84
        if (empty($message)) {
85
            return '';
86
        }
87
88
        Emojione\Emojione::$imagePathPNG = api_get_path(WEB_LIBRARY_PATH).'javascript/emojione/png/';
89
        Emojione\Emojione::$ascii = true;
90
91
        $message = trim($message);
92
        $message = nl2br($message);
93
        // Security XSS
94
        $message = Security::remove_XSS($message);
95
        //search urls
96
        $message = preg_replace(
97
            '@((https?://)?([-\w]+\.[-\w\.]+)+\w(:\d+)?(/([-\w/_\.]*(\?\S+)?)?)*)@',
98
            '<a href="$1" target="_blank">$1</a>',
99
            $message
100
        );
101
        // add "http://" if not set
102
        $message = preg_replace(
103
            '/<a\s[^>]*href\s*=\s*"((?!https?:\/\/)[^"]*)"[^>]*>/i',
104
            '<a href="http://$1" target="_blank">',
105
            $message
106
        );
107
        // Parsing emojis
108
        $message = Emojione\Emojione::toImage($message);
109
        // Parsing text to understand markdown (code highlight)
110
        $message = MarkdownExtra::defaultTransform($message);
111
112
        return $message;
113
    }
114
115
    /**
116
     * Save a chat message in a HTML file
117
     * @param string$message
118
     * @param int $friendId
119
     * @return bool
120
     * @throws \Doctrine\ORM\ORMException
121
     * @throws \Doctrine\ORM\OptimisticLockException
122
     * @throws \Doctrine\ORM\TransactionRequiredException
123
     */
124
    public function saveMessage($message, $friendId = 0)
125
    {
126
        if (empty($message)) {
127
            return false;
128
        }
129
        $user = api_get_user_entity($this->userId);
130
        $courseInfo = api_get_course_info_by_id($this->courseId);
131
        $isMaster = (bool) api_is_course_admin();
132
        $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
133
        $basepath_chat = '/chat_files';
134
        $group_info = [];
135
        if (!$this->groupId) {
136
            $group_info = GroupManager::get_group_properties($this->groupId);
137
            $basepath_chat = $group_info['directory'].'/chat_files';
138
        }
139
140
        $chat_path = $document_path.$basepath_chat.'/';
141
142
        if (!is_dir($chat_path)) {
143
            if (is_file($chat_path)) {
144
                @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

144
                /** @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...
145
            }
146
        }
147
148
        $date_now = date('Y-m-d');
149
        $timeNow = date('d/m/y H:i:s');
150
        $basename_chat = 'messages-'.$date_now;
151
152
        if ($this->groupId && !$friendId) {
153
            $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
154
        } elseif ($this->sessionId && !$friendId) {
155
            $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
156
        } elseif ($friendId) {
157
            if ($this->userId < $friendId) {
158
                $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
159
            } else {
160
                $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
161
            }
162
        }
163
164
        $message = self::prepareMessage($message);
165
166
        $fileTitle = $basename_chat.'.log.html';
167
        $filePath = $basepath_chat.'/'.$fileTitle;
168
        $absoluteFilePath = $chat_path.$fileTitle;
169
170
        if (!file_exists($absoluteFilePath)) {
171
            $doc_id = add_document($courseInfo, $filePath, 'file', 0, $fileTitle);
172
            $documentLogTypes = ['DocumentAdded', 'invisible'];
173
174
            foreach ($documentLogTypes as $logType) {
175
                api_item_property_update(
176
                    $courseInfo,
177
                    TOOL_DOCUMENT,
178
                    $doc_id,
179
                    $logType,
180
                    $this->userId,
181
                    $group_info,
182
                    null,
183
                    null,
184
                    null,
185
                    $this->sessionId
186
                );
187
            }
188
189
            item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
190
        } else {
191
            $doc_id = DocumentManager::get_document_id($courseInfo, $filePath);
192
        }
193
194
        $fp = fopen($absoluteFilePath, 'a');
195
        $userPhoto = UserManager::getUserPicture($this->userId, USER_IMAGE_SIZE_MEDIUM);
196
197
        if ($isMaster) {
198
            $fileContent = '
199
                <div class="message-teacher">
200
                    <div class="content-message">
201
                        <div class="chat-message-block-name">' . $user->getCompleteName().'</div>
202
                        <div class="chat-message-block-content">' . $message.'</div>
203
                        <div class="message-date">' . $timeNow.'</div>
204
                    </div>
205
                    <div class="icon-message"></div>
206
                    <img class="chat-image" src="' . $userPhoto.'">
207
                </div>
208
            ';
209
        } else {
210
            $fileContent = '
211
                <div class="message-student">
212
                    <img class="chat-image" src="' . $userPhoto.'">
213
                    <div class="icon-message"></div>
214
                    <div class="content-message">
215
                        <div class="chat-message-block-name">' . $user->getCompleteName().'</div>
216
                        <div class="chat-message-block-content">' . $message.'</div>
217
                        <div class="message-date">' . $timeNow.'</div>
218
                    </div>
219
                </div>
220
            ';
221
        }
222
223
        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

223
        fputs(/** @scrutinizer ignore-type */ $fp, $fileContent);
Loading history...
224
        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

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

494
                /** @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...
495
            }
496
497
            if (!api_is_anonymous()) {
498
                @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

498
                /** @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...
499
                // Save chat files document for group into item property
500
                if ($this->groupId) {
501
                    $doc_id = add_document($courseInfo, $basepath_chat, 'folder', 0, 'chat_files');
502
                    api_item_property_update(
503
                        $courseInfo,
504
                        TOOL_DOCUMENT,
505
                        $doc_id,
506
                        'FolderCreated',
507
                        null,
508
                        $group_info,
509
                        null,
510
                        null,
511
                        null
512
                    );
513
                }
514
            }
515
        }
516
517
        $filename_chat = 'messages-'.$date_now.'.log.html';
518
519
        if ($this->groupId && !$friendId) {
520
            $filename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId.'.log.html';
521
        } elseif ($this->sessionId && !$friendId) {
522
            $filename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId.'.log.html';
523
        } elseif ($friendId) {
524
            if ($this->userId < $friendId) {
525
                $filename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
526
            } else {
527
                $filename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
528
            }
529
        }
530
531
        if (!file_exists($chat_path.$filename_chat)) {
532
            @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

532
            /** @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

532
            @fclose(/** @scrutinizer ignore-type */ fopen($chat_path.$filename_chat, 'w'));
Loading history...
533
            if (!api_is_anonymous()) {
534
                $doc_id = add_document($courseInfo, $basepath_chat.'/'.$filename_chat, 'file', 0, $filename_chat);
535
                api_item_property_update(
536
                    $courseInfo,
537
                    TOOL_DOCUMENT,
538
                    $doc_id,
539
                    'DocumentAdded',
540
                    $this->userId,
541
                    $group_info,
542
                    null,
543
                    null,
544
                    null,
545
                    $this->sessionId
546
                );
547
                api_item_property_update(
548
                    $courseInfo,
549
                    TOOL_DOCUMENT,
550
                    $doc_id,
551
                    'invisible',
552
                    $this->userId,
553
                    $group_info,
554
                    null,
555
                    null,
556
                    null,
557
                    $this->sessionId
558
                );
559
                item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
560
            }
561
        }
562
563
        $basename_chat = 'messages-'.$date_now;
564
        if ($this->groupId && !$friendId) {
565
            $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
566
        } elseif ($this->sessionId && !$friendId) {
567
            $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
568
        } elseif ($friendId) {
569
            if ($this->userId < $friendId) {
570
                $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
571
            } else {
572
                $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
573
            }
574
        }
575
576
        if ($reset && $isMaster) {
577
            $i = 1;
578
            while (file_exists($chat_path.$basename_chat.'-'.$i.'.log.html')) {
579
                $i++;
580
            }
581
582
            @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

582
            /** @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...
583
            @fclose(fopen($chat_path.$basename_chat.'.log.html', 'w'));
584
585
            $doc_id = add_document(
586
                $courseInfo,
587
                $basepath_chat.'/'.$basename_chat.'-'.$i.'.log.html',
588
                'file',
589
                filesize($chat_path.$basename_chat.'-'.$i.'.log.html'),
590
                $basename_chat.'-'.$i.'.log.html'
591
            );
592
593
            api_item_property_update(
594
                $courseInfo,
595
                TOOL_DOCUMENT,
596
                $doc_id,
597
                'DocumentAdded',
598
                $this->userId,
599
                $group_info,
600
                null,
601
                null,
602
                null,
603
                $this->sessionId
604
            );
605
            api_item_property_update(
606
                $courseInfo,
607
                TOOL_DOCUMENT,
608
                $doc_id,
609
                'invisible',
610
                $this->userId,
611
                $group_info,
612
                null,
613
                null,
614
                null,
615
                $this->sessionId
616
            );
617
            item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
618
            $doc_id = DocumentManager::get_document_id(
619
                $courseInfo,
620
                $basepath_chat.'/'.$basename_chat.'.log.html'
621
            );
622
            update_existing_document($courseInfo, $doc_id, 0);
623
        }
624
625
        $remove = 0;
626
        $content = [];
627
628
        if (file_exists($chat_path.$basename_chat.'.log.html')) {
629
            $content = file($chat_path.$basename_chat.'.log.html');
630
            $nbr_lines = sizeof($content);
631
            $remove = $nbr_lines - 100;
632
        }
633
634
        if ($remove < 0) {
635
            $remove = 0;
636
        }
637
638
        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

638
        array_splice(/** @scrutinizer ignore-type */ $content, 0, $remove);
Loading history...
639
640
        if (isset($_GET['origin']) && $_GET['origin'] == 'whoisonline') {
641
            //the caller
642
            $content[0] = get_lang('CallSent').'<br />'.$content[0];
643
        }
644
645
        $history = '<div id="content-chat">';
646
        foreach ($content as $this_line) {
647
            $history .= $this_line;
648
        }
649
        $history .= '</div>';
650
651
        if ($isMaster || $GLOBALS['is_session_general_coach']) {
652
            $history .= '
653
                <div id="clear-chat">
654
                    <button type="button" id="chat-reset" class="btn btn-danger btn-sm">
655
                        ' . get_lang('ClearList').'
656
                    </button>
657
                </div>
658
            ';
659
        }
660
661
        return $history;
662
    }
663
664
    /**
665
     * Get the number of users connected in chat
666
     * @return mixed
667
     */
668
    public function countUsersOnline()
669
    {
670
        $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
671
        $date->modify('-5 seconds');
672
673
        $extraCondition = null;
674
675
        if ($this->groupId) {
676
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
677
        } else {
678
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
679
        }
680
681
        $number = Database::getManager()
682
            ->createQuery("
683
                SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
684
                WHERE ccc.lastConnection > :date AND ccc.cId = :course $extraCondition
685
            ")
686
            ->setParameters([
687
                'date' => $date,
688
                'course' => $this->courseId
689
            ])
690
            ->getSingleScalarResult();
691
692
        return intval($number);
693
    }
694
695
    /**
696
     * Check if a user is connected in course chat
697
     * @param int $userId
698
     * @return int
699
     */
700
    private function userIsConnected($userId)
701
    {
702
        $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
703
        $date->modify('-5 seconds');
704
705
        $extraCondition = null;
706
707
        if ($this->groupId) {
708
            $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
709
        } else {
710
            $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
711
        }
712
713
        $number = Database::getManager()
714
            ->createQuery("
715
                SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
716
                WHERE ccc.lastConnection > :date AND ccc.cId = :course AND ccc.userId = :user $extraCondition
717
            ")
718
            ->setParameters([
719
                'date' => $date,
720
                'course' => $this->courseId,
721
                'user' => $userId
722
            ])
723
            ->getSingleScalarResult();
724
725
        return intval($number);
726
    }
727
728
    /**
729
     * Get the users online data
730
     * @return string
731
     */
732
    public function listUsersOnline()
733
    {
734
        $subscriptions = $this->getUsersSubscriptions();
735
        $usersInfo = [];
736
737
        foreach ($subscriptions as $subscription) {
738
            $user = $subscription->getUser();
739
740
            $usersInfo[] = [
741
                'id' => $user->getId(),
742
                'firstname' => $user->getFirstname(),
743
                'lastname' => $user->getLastname(),
744
                'status' => !$this->sessionId ? $subscription->getStatus() : $user->getStatus(),
745
                'image_url' => UserManager::getUserPicture($user->getId(), USER_IMAGE_SIZE_MEDIUM),
746
                'profile_url' => api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user->getId(),
747
                'complete_name' => $user->getCompleteName(),
748
                'username' => $user->getUsername(),
749
                'email' => $user->getEmail(),
750
                'isConnected' => $this->userIsConnected($user->getId())
751
            ];
752
        }
753
754
        return $usersInfo;
755
    }
756
}
757