Chamilo is a learning management system focused on ease of use and accessibility
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
chamilo-lms/main/inc/lib/CourseChatUtils.php

741 lines
24 KiB

<?php
/* For licensing terms, see /license.txt */
use Michelf\MarkdownExtra;
use Doctrine\Common\Collections\Criteria;
use Chamilo\CourseBundle\Entity\CChatConnected;
/**
* Class CourseChat
* Manage the chat for a course
*/
class CourseChatUtils
{
private $groupId;
private $courseId;
private $sessionId;
private $userId;
/**
* CourseChat constructor.
* @param int $courseId
* @param int $userId
* @param int $sessionId
* @param int $groupId
*/
public function __construct($courseId, $userId, $sessionId = 0, $groupId = 0)
{
$this->courseId = (int) $courseId;
$this->userId = (int) $userId;
$this->sessionId = (int) $sessionId;
$this->groupId = (int) $groupId;
}
/**
* Get the users subscriptions (SessionRelCourseRelUser array or CourseRelUser array) for chat
* @return \Doctrine\Common\Collections\ArrayCollection
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*/
private function getUsersSubscriptions()
{
$em = Database::getManager();
$course = $em->find('ChamiloCoreBundle:Course', $this->courseId);
if ($this->sessionId) {
$criteria = Criteria::create()->where(Criteria::expr()->eq('course', $course));
return $em
->find('ChamiloCoreBundle:Session', $this->sessionId)
->getUserCourseSubscriptions()
->matching($criteria);
}
return $em
->find('ChamiloCoreBundle:Course', $course)
->getUsers();
}
/**
* Prepare a message. Clean and insert emojis
* @param string $message The message to prepare
* @return string
*/
public static function prepareMessage($message)
{
if (empty($message)) {
return '';
}
Emojione\Emojione::$imagePathPNG = api_get_path(WEB_LIBRARY_PATH).'javascript/emojione/png/';
Emojione\Emojione::$ascii = true;
$message = trim($message);
$message = nl2br($message);
// Security XSS
$message = Security::remove_XSS($message);
//search urls
$message = preg_replace(
'@((https?://)?([-\w]+\.[-\w\.]+)+\w(:\d+)?(/([-\w/_\.]*(\?\S+)?)?)*)@',
'<a href="$1" target="_blank">$1</a>',
$message
);
// add "http://" if not set
$message = preg_replace(
'/<a\s[^>]*href\s*=\s*"((?!https?:\/\/)[^"]*)"[^>]*>/i',
'<a href="http://$1" target="_blank">',
$message
);
// Parsing emojis
$message = Emojione\Emojione::toImage($message);
// Parsing text to understand markdown (code highlight)
$message = MarkdownExtra::defaultTransform($message);
return $message;
}
/**
* Save a chat message in a HTML file
* @param string$message
* @param int $friendId
* @return bool
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException
*/
public function saveMessage($message, $friendId = 0)
{
if (empty($message)) {
return false;
}
$em = Database::getManager();
$user = $em->find('ChamiloUserBundle:User', $this->userId);
$courseInfo = api_get_course_info_by_id($this->courseId);
$isMaster = (bool) api_is_course_admin();
$document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
$basepath_chat = '/chat_files';
$group_info = [];
if (!$this->groupId) {
$group_info = GroupManager::get_group_properties($this->groupId);
$basepath_chat = $group_info['directory'].'/chat_files';
}
$chat_path = $document_path.$basepath_chat.'/';
if (!is_dir($chat_path)) {
if (is_file($chat_path)) {
@unlink($chat_path);
}
}
$date_now = date('Y-m-d');
$timeNow = date('d/m/y H:i:s');
$basename_chat = 'messages-'.$date_now;
if ($this->groupId && !$friendId) {
$basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
} elseif ($this->sessionId && !$friendId) {
$basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
} elseif ($friendId) {
if ($this->userId < $friendId) {
$basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
} else {
$basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
}
}
$message = self::prepareMessage($message);
$fileTitle = $basename_chat.'.log.html';
$filePath = $basepath_chat.'/'.$fileTitle;
$absoluteFilePath = $chat_path.$fileTitle;
if (!file_exists($absoluteFilePath)) {
$doc_id = add_document($courseInfo, $filePath, 'file', 0, $fileTitle);
$documentLogTypes = ['DocumentAdded', 'invisible'];
foreach ($documentLogTypes as $logType) {
api_item_property_update(
$courseInfo,
TOOL_DOCUMENT,
$doc_id,
$logType,
$this->userId,
$group_info,
null,
null,
null,
$this->sessionId
);
}
item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
} else {
$doc_id = DocumentManager::get_document_id($courseInfo, $filePath);
}
$fp = fopen($absoluteFilePath, 'a');
$userPhoto = UserManager::getUserPicture($this->userId, USER_IMAGE_SIZE_MEDIUM);
if ($isMaster) {
$fileContent = '
<div class="message-teacher">
<div class="content-message">
<div class="chat-message-block-name">' . $user->getCompleteName().'</div>
<div class="chat-message-block-content">' . $message.'</div>
<div class="message-date">' . $timeNow.'</div>
</div>
<div class="icon-message"></div>
<img class="chat-image" src="' . $userPhoto.'">
</div>
';
} else {
$fileContent = '
<div class="message-student">
<img class="chat-image" src="' . $userPhoto.'">
<div class="icon-message"></div>
<div class="content-message">
<div class="chat-message-block-name">' . $user->getCompleteName().'</div>
<div class="chat-message-block-content">' . $message.'</div>
<div class="message-date">' . $timeNow.'</div>
</div>
</div>
';
}
fputs($fp, $fileContent);
fclose($fp);
$chat_size = filesize($absoluteFilePath);
update_existing_document($courseInfo, $doc_id, $chat_size);
item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
return true;
}
/**
* Disconnect a user from course chats
* @param $userId
*/
public static function exitChat($userId)
{
$listCourse = CourseManager::get_courses_list_by_user_id($userId);
foreach ($listCourse as $course) {
Database::getManager()
->createQuery('
DELETE FROM ChamiloCourseBundle:CChatConnected ccc
WHERE ccc.cId = :course AND ccc.userId = :user
')
->execute([
'course' => intval($course['real_id']),
'user' => intval($userId)
]);
}
}
/**
* Disconnect users who are more than 5 seconds inactive
*/
public function disconnectInactiveUsers()
{
$em = Database::getManager();
$extraCondition = "AND ccc.toGroupId = {$this->groupId}";
if (empty($this->groupId)) {
$extraCondition = "AND ccc.sessionId = {$this->sessionId}";
}
$connectedUsers = $em
->createQuery("
SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
WHERE ccc.cId = :course $extraCondition
")
->setParameter('course', $this->courseId)
->getResult();
$now = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
$cd_count_time_seconds = $now->getTimestamp();
foreach ($connectedUsers as $connection) {
$date_count_time_seconds = $connection->getLastConnection()->getTimestamp();
if (strcmp($now->format('Y-m-d'), $connection->getLastConnection()->format('Y-m-d')) !== 0) {
continue;
}
if (($cd_count_time_seconds - $date_count_time_seconds) <= 5) {
continue;
}
$em
->createQuery('
DELETE FROM ChamiloCourseBundle:CChatConnected ccc
WHERE ccc.cId = :course AND ccc.userId = :user AND ccc.toGroupId = :group
')
->execute([
'course' => $this->courseId,
'user' => $connection->getUserId(),
'group' => $this->groupId
]);
}
}
/**
* Keep registered to a user as connected
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function keepUserAsConnected()
{
$em = Database::getManager();
$extraCondition = null;
if ($this->groupId) {
$extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
} else {
$extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
}
$currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
$connection = $em
->createQuery("
SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
WHERE ccc.userId = :user AND ccc.cId = :course $extraCondition
")
->setParameters([
'user' => $this->userId,
'course' => $this->courseId
])
->getOneOrNullResult();
if ($connection) {
$connection->setLastConnection($currentTime);
$em->merge($connection);
$em->flush();
return;
}
$connection = new CChatConnected();
$connection
->setCId($this->courseId)
->setUserId($this->userId)
->setLastConnection($currentTime)
->setSessionId($this->sessionId)
->setToGroupId($this->groupId);
$em->persist($connection);
$em->flush();
}
/**
* Get the emoji allowed on course chat
* @return array
*/
public static function getEmojiStrategy()
{
return require_once api_get_path(SYS_CODE_PATH).'chat/emoji_strategy.php';
}
/**
* Get the emoji list to include in chat
* @return array
*/
public static function getEmojisToInclude()
{
return [
':bowtie:',
':smile:' |
':laughing:',
':blush:',
':smiley:',
':relaxed:',
':smirk:',
':heart_eyes:',
':kissing_heart:',
':kissing_closed_eyes:',
':flushed:',
':relieved:',
':satisfied:',
':grin:',
':wink:',
':stuck_out_tongue_winking_eye:',
':stuck_out_tongue_closed_eyes:',
':grinning:',
':kissing:',
':kissing_smiling_eyes:',
':stuck_out_tongue:',
':sleeping:',
':worried:',
':frowning:',
':anguished:',
':open_mouth:',
':grimacing:',
':confused:',
':hushed:',
':expressionless:',
':unamused:',
':sweat_smile:',
':sweat:',
':disappointed_relieved:',
':weary:',
':pensive:',
':disappointed:',
':confounded:',
':fearful:',
':cold_sweat:',
':persevere:',
':cry:',
':sob:',
':joy:',
':astonished:',
':scream:',
':neckbeard:',
':tired_face:',
':angry:',
':rage:',
':triumph:',
':sleepy:',
':yum:',
':mask:',
':sunglasses:',
':dizzy_face:',
':imp:',
':smiling_imp:',
':neutral_face:',
':no_mouth:',
':innocent:',
':alien:'
];
}
/**
* Get the chat history file name
* @param bool $absolute Optional. Whether get the base or the absolute file path
* @param int $friendId Optional.
* @return string
*/
public function getFileName($absolute = false, $friendId = 0)
{
$date = date('Y-m-d');
$base = 'messages-'.$date.'.log.html';
if ($this->groupId && !$friendId) {
$base = 'messages-'.$date.'_gid-'.$this->groupId.'.log.html';
} elseif ($this->sessionId && !$friendId) {
$base = 'messages-'.$date.'_sid-'.$this->sessionId.'.log.html';
} elseif ($friendId) {
if ($this->userId < $friendId) {
$base = 'messages-'.$date.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
} else {
$base = 'messages-'.$date.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
}
}
if (!$absolute) {
return $base;
}
$courseInfo = api_get_course_info_by_id($this->courseId);
$document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
$chatPath = $document_path.'/chat_files/';
if ($this->groupId) {
$group_info = GroupManager::get_group_properties($this->groupId);
$chatPath = $document_path.$group_info['directory'].'/chat_files/';
}
return $chatPath.$base;
}
/**
* Get the chat history
* @param bool $reset
* @param int $friendId Optional.
* @return string
*/
public function readMessages($reset = false, $friendId = 0)
{
$courseInfo = api_get_course_info_by_id($this->courseId);
$date_now = date('Y-m-d');
$isMaster = (bool) api_is_course_admin();
$basepath_chat = '/chat_files';
$document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
$group_info = [];
if ($this->groupId) {
$group_info = GroupManager:: get_group_properties($this->groupId);
$basepath_chat = $group_info['directory'].'/chat_files';
}
$chat_path = $document_path.$basepath_chat.'/';
if (!is_dir($chat_path)) {
if (is_file($chat_path)) {
@unlink($chat_path);
}
if (!api_is_anonymous()) {
@mkdir($chat_path, api_get_permissions_for_new_directories());
// Save chat files document for group into item property
if ($this->groupId) {
$doc_id = add_document($courseInfo, $basepath_chat, 'folder', 0, 'chat_files');
api_item_property_update(
$courseInfo,
TOOL_DOCUMENT,
$doc_id,
'FolderCreated',
null,
$group_info,
null,
null,
null
);
}
}
}
$filename_chat = 'messages-'.$date_now.'.log.html';
if ($this->groupId && !$friendId) {
$filename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId.'.log.html';
} elseif ($this->sessionId && !$friendId) {
$filename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId.'.log.html';
} elseif ($friendId) {
if ($this->userId < $friendId) {
$filename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
} else {
$filename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
}
}
if (!file_exists($chat_path.$filename_chat)) {
@fclose(fopen($chat_path.$filename_chat, 'w'));
if (!api_is_anonymous()) {
$doc_id = add_document($courseInfo, $basepath_chat.'/'.$filename_chat, 'file', 0, $filename_chat);
api_item_property_update(
$courseInfo,
TOOL_DOCUMENT,
$doc_id,
'DocumentAdded',
$this->userId,
$group_info,
null,
null,
null,
$this->sessionId
);
api_item_property_update(
$courseInfo,
TOOL_DOCUMENT,
$doc_id,
'invisible',
$this->userId,
$group_info,
null,
null,
null,
$this->sessionId
);
item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
}
}
$basename_chat = 'messages-'.$date_now;
if ($this->groupId && !$friendId) {
$basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
} elseif ($this->sessionId && !$friendId) {
$basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
} elseif ($friendId) {
if ($this->userId < $friendId) {
$basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
} else {
$basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
}
}
if ($reset && $isMaster) {
$i = 1;
while (file_exists($chat_path.$basename_chat.'-'.$i.'.log.html')) {
$i++;
}
@rename($chat_path.$basename_chat.'.log.html', $chat_path.$basename_chat.'-'.$i.'.log.html');
@fclose(fopen($chat_path.$basename_chat.'.log.html', 'w'));
$doc_id = add_document(
$courseInfo,
$basepath_chat.'/'.$basename_chat.'-'.$i.'.log.html',
'file',
filesize($chat_path.$basename_chat.'-'.$i.'.log.html'),
$basename_chat.'-'.$i.'.log.html'
);
api_item_property_update(
$courseInfo,
TOOL_DOCUMENT,
$doc_id,
'DocumentAdded',
$this->userId,
$group_info,
null,
null,
null,
$this->sessionId
);
api_item_property_update(
$courseInfo,
TOOL_DOCUMENT,
$doc_id,
'invisible',
$this->userId,
$group_info,
null,
null,
null,
$this->sessionId
);
item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
$doc_id = DocumentManager::get_document_id(
$courseInfo,
$basepath_chat.'/'.$basename_chat.'.log.html'
);
update_existing_document($courseInfo, $doc_id, 0);
}
$remove = 0;
$content = array();
if (file_exists($chat_path.$basename_chat.'.log.html')) {
$content = file($chat_path.$basename_chat.'.log.html');
$nbr_lines = sizeof($content);
$remove = $nbr_lines - 100;
}
if ($remove < 0) {
$remove = 0;
}
array_splice($content, 0, $remove);
if (isset($_GET['origin']) && $_GET['origin'] == 'whoisonline') {
//the caller
$content[0] = get_lang('CallSent').'<br />'.$content[0];
}
$history = '<div id="content-chat">';
foreach ($content as $this_line) {
$history .= $this_line;
}
$history .= '</div>';
if ($isMaster || $GLOBALS['is_session_general_coach']) {
$history .= '
<div id="clear-chat">
<button type="button" id="chat-reset" class="btn btn-danger btn-sm">
' . get_lang('ClearList').'
</button>
</div>
';
}
return $history;
}
/**
* Get the number of users connected in chat
* @return mixed
*/
public function countUsersOnline()
{
$date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
$date->modify('-5 seconds');
$extraCondition = null;
if ($this->groupId) {
$extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
} else {
$extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
}
$number = Database::getManager()
->createQuery("
SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
WHERE ccc.lastConnection > :date AND ccc.cId = :course $extraCondition
")
->setParameters([
'date' => $date,
'course' => $this->courseId
])
->getSingleScalarResult();
return intval($number);
}
/**
* Check if a user is connected in course chat
* @param int $userId
* @return int
*/
private function userIsConnected($userId)
{
$date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
$date->modify('-5 seconds');
$extraCondition = null;
if ($this->groupId) {
$extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
} else {
$extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
}
$number = Database::getManager()
->createQuery("
SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
WHERE ccc.lastConnection > :date AND ccc.cId = :course AND ccc.userId = :user $extraCondition
")
->setParameters([
'date' => $date,
'course' => $this->courseId,
'user' => $userId
])
->getSingleScalarResult();
return intval($number);
}
/**
* Get the users online data
* @return string
*/
public function listUsersOnline()
{
$subscriptions = $this->getUsersSubscriptions();
$usersInfo = [];
foreach ($subscriptions as $subscription) {
$user = $subscription->getUser();
$usersInfo[] = [
'id' => $user->getId(),
'firstname' => $user->getFirstname(),
'lastname' => $user->getLastname(),
'status' => !$this->sessionId ? $subscription->getStatus() : $user->getStatus(),
'image_url' => UserManager::getUserPicture($user->getId(), USER_IMAGE_SIZE_MEDIUM),
'profile_url' => api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user->getId(),
'complete_name' => $user->getCompleteName(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
'isConnected' => $this->userIsConnected($user->getId())
];
}
return $usersInfo;
}
}