CourseChatUtils.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use Chamilo\CoreBundle\Entity\Course;
  4. use Chamilo\CoreBundle\Entity\CourseRelUser;
  5. use Chamilo\CoreBundle\Entity\Session;
  6. use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
  7. use Chamilo\CourseBundle\Entity\CChatConnected;
  8. use Chamilo\UserBundle\Entity\User;
  9. use Doctrine\Common\Collections\Criteria;
  10. use Michelf\MarkdownExtra;
  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. * CourseChat constructor.
  23. *
  24. * @param int $courseId
  25. * @param int $userId
  26. * @param int $sessionId
  27. * @param int $groupId
  28. */
  29. public function __construct($courseId, $userId, $sessionId = 0, $groupId = 0)
  30. {
  31. $this->courseId = (int) $courseId;
  32. $this->userId = (int) $userId;
  33. $this->sessionId = (int) $sessionId;
  34. $this->groupId = (int) $groupId;
  35. }
  36. /**
  37. * Prepare a message. Clean and insert emojis.
  38. *
  39. * @param string $message The message to prepare
  40. *
  41. * @return string
  42. */
  43. public static function prepareMessage($message)
  44. {
  45. if (empty($message)) {
  46. return '';
  47. }
  48. Emojione\Emojione::$imagePathPNG = api_get_path(WEB_LIBRARY_PATH).'javascript/emojione/png/';
  49. Emojione\Emojione::$ascii = true;
  50. $message = trim($message);
  51. $message = nl2br($message);
  52. // Security XSS
  53. $message = Security::remove_XSS($message);
  54. //search urls
  55. $message = preg_replace(
  56. '@((https?://)?([-\w]+\.[-\w\.]+)+\w(:\d+)?(/([-\w/_\.]*(\?\S+)?)?)*)@',
  57. '<a href="$1" target="_blank">$1</a>',
  58. $message
  59. );
  60. // add "http://" if not set
  61. $message = preg_replace(
  62. '/<a\s[^>]*href\s*=\s*"((?!https?:\/\/)[^"]*)"[^>]*>/i',
  63. '<a href="http://$1" target="_blank">',
  64. $message
  65. );
  66. // Parsing emojis
  67. $message = Emojione\Emojione::toImage($message);
  68. // Parsing text to understand markdown (code highlight)
  69. $message = MarkdownExtra::defaultTransform($message);
  70. return $message;
  71. }
  72. /**
  73. * Save a chat message in a HTML file.
  74. *
  75. * @param string $message
  76. * @param int $friendId
  77. *
  78. * @return bool
  79. */
  80. public function saveMessage($message, $friendId = 0)
  81. {
  82. if (empty($message)) {
  83. return false;
  84. }
  85. $friendId = (int) $friendId;
  86. $user = api_get_user_entity($this->userId);
  87. $courseInfo = api_get_course_info_by_id($this->courseId);
  88. $isMaster = api_is_course_admin();
  89. $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
  90. $basepath_chat = '/chat_files';
  91. $group_info = [];
  92. if ($this->groupId) {
  93. $group_info = GroupManager::get_group_properties($this->groupId);
  94. $basepath_chat = $group_info['directory'].'/chat_files';
  95. }
  96. $chat_path = $document_path.$basepath_chat.'/';
  97. if (!is_dir($chat_path)) {
  98. if (is_file($chat_path)) {
  99. @unlink($chat_path);
  100. }
  101. }
  102. $date_now = date('Y-m-d');
  103. $timeNow = date('d/m/y H:i:s');
  104. $basename_chat = 'messages-'.$date_now;
  105. if ($this->groupId && !$friendId) {
  106. $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
  107. } elseif ($this->sessionId && !$friendId) {
  108. $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
  109. } elseif ($friendId) {
  110. if ($this->userId < $friendId) {
  111. $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
  112. } else {
  113. $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
  114. }
  115. }
  116. $message = self::prepareMessage($message);
  117. $fileTitle = $basename_chat.'.log.html';
  118. $filePath = $basepath_chat.'/'.$fileTitle;
  119. $absoluteFilePath = $chat_path.$fileTitle;
  120. if (!file_exists($absoluteFilePath)) {
  121. $doc_id = add_document(
  122. $courseInfo,
  123. $filePath,
  124. 'file',
  125. 0,
  126. $fileTitle,
  127. null,
  128. 0,
  129. true,
  130. 0,
  131. 0,
  132. 0,
  133. false
  134. );
  135. $documentLogTypes = ['DocumentAdded', 'invisible'];
  136. foreach ($documentLogTypes as $logType) {
  137. api_item_property_update(
  138. $courseInfo,
  139. TOOL_DOCUMENT,
  140. $doc_id,
  141. $logType,
  142. $this->userId,
  143. $group_info,
  144. null,
  145. null,
  146. null,
  147. $this->sessionId
  148. );
  149. }
  150. item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
  151. } else {
  152. $doc_id = DocumentManager::get_document_id($courseInfo, $filePath);
  153. }
  154. $fp = fopen($absoluteFilePath, 'a');
  155. $userPhoto = UserManager::getUserPicture($this->userId, USER_IMAGE_SIZE_MEDIUM);
  156. if ($isMaster) {
  157. $fileContent = '
  158. <div class="message-teacher">
  159. <div class="content-message">
  160. <div class="chat-message-block-name">'.UserManager::formatUserFullName($user).'</div>
  161. <div class="chat-message-block-content">'.$message.'</div>
  162. <div class="message-date">'.$timeNow.'</div>
  163. </div>
  164. <div class="icon-message"></div>
  165. <img class="chat-image" src="'.$userPhoto.'">
  166. </div>
  167. ';
  168. } else {
  169. $fileContent = '
  170. <div class="message-student">
  171. <img class="chat-image" src="'.$userPhoto.'">
  172. <div class="icon-message"></div>
  173. <div class="content-message">
  174. <div class="chat-message-block-name">'.UserManager::formatUserFullName($user).'</div>
  175. <div class="chat-message-block-content">'.$message.'</div>
  176. <div class="message-date">'.$timeNow.'</div>
  177. </div>
  178. </div>
  179. ';
  180. }
  181. fputs($fp, $fileContent);
  182. fclose($fp);
  183. $size = filesize($absoluteFilePath);
  184. update_existing_document($courseInfo, $doc_id, $size);
  185. item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
  186. return true;
  187. }
  188. /**
  189. * Disconnect a user from course chats.
  190. *
  191. * @param int $userId
  192. */
  193. public static function exitChat($userId)
  194. {
  195. $listCourse = CourseManager::get_courses_list_by_user_id($userId);
  196. foreach ($listCourse as $course) {
  197. Database::getManager()
  198. ->createQuery('
  199. DELETE FROM ChamiloCourseBundle:CChatConnected ccc
  200. WHERE ccc.cId = :course AND ccc.userId = :user
  201. ')
  202. ->execute([
  203. 'course' => intval($course['real_id']),
  204. 'user' => intval($userId),
  205. ]);
  206. }
  207. }
  208. /**
  209. * Disconnect users who are more than 5 seconds inactive.
  210. */
  211. public function disconnectInactiveUsers()
  212. {
  213. $em = Database::getManager();
  214. $extraCondition = "AND ccc.toGroupId = {$this->groupId}";
  215. if (empty($this->groupId)) {
  216. $extraCondition = "AND ccc.sessionId = {$this->sessionId}";
  217. }
  218. $connectedUsers = $em
  219. ->createQuery("
  220. SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
  221. WHERE ccc.cId = :course $extraCondition
  222. ")
  223. ->setParameter('course', $this->courseId)
  224. ->getResult();
  225. $now = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
  226. $cd_count_time_seconds = $now->getTimestamp();
  227. /** @var CChatConnected $connection */
  228. foreach ($connectedUsers as $connection) {
  229. $date_count_time_seconds = $connection->getLastConnection()->getTimestamp();
  230. if (strcmp($now->format('Y-m-d'), $connection->getLastConnection()->format('Y-m-d')) !== 0) {
  231. continue;
  232. }
  233. if (($cd_count_time_seconds - $date_count_time_seconds) <= 5) {
  234. continue;
  235. }
  236. $em
  237. ->createQuery('
  238. DELETE FROM ChamiloCourseBundle:CChatConnected ccc
  239. WHERE ccc.cId = :course AND ccc.userId = :user AND ccc.toGroupId = :group
  240. ')
  241. ->execute([
  242. 'course' => $this->courseId,
  243. 'user' => $connection->getUserId(),
  244. 'group' => $this->groupId,
  245. ]);
  246. }
  247. }
  248. /**
  249. * Keep registered to a user as connected.
  250. */
  251. public function keepUserAsConnected()
  252. {
  253. $em = Database::getManager();
  254. $extraCondition = null;
  255. if ($this->groupId) {
  256. $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
  257. } else {
  258. $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
  259. }
  260. $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
  261. $connection = $em
  262. ->createQuery("
  263. SELECT ccc FROM ChamiloCourseBundle:CChatConnected ccc
  264. WHERE ccc.userId = :user AND ccc.cId = :course $extraCondition
  265. ")
  266. ->setParameters([
  267. 'user' => $this->userId,
  268. 'course' => $this->courseId,
  269. ])
  270. ->getOneOrNullResult();
  271. if ($connection) {
  272. $connection->setLastConnection($currentTime);
  273. $em->merge($connection);
  274. $em->flush();
  275. return;
  276. }
  277. $connection = new CChatConnected();
  278. $connection
  279. ->setCId($this->courseId)
  280. ->setUserId($this->userId)
  281. ->setLastConnection($currentTime)
  282. ->setSessionId($this->sessionId)
  283. ->setToGroupId($this->groupId);
  284. $em->persist($connection);
  285. $em->flush();
  286. }
  287. /**
  288. * Get the emoji allowed on course chat.
  289. *
  290. * @return array
  291. */
  292. public static function getEmojiStrategy()
  293. {
  294. return require_once api_get_path(SYS_CODE_PATH).'chat/emoji_strategy.php';
  295. }
  296. /**
  297. * Get the emoji list to include in chat.
  298. *
  299. * @return array
  300. */
  301. public static function getEmojisToInclude()
  302. {
  303. return [
  304. ':bowtie:',
  305. ':smile:' |
  306. ':laughing:',
  307. ':blush:',
  308. ':smiley:',
  309. ':relaxed:',
  310. ':smirk:',
  311. ':heart_eyes:',
  312. ':kissing_heart:',
  313. ':kissing_closed_eyes:',
  314. ':flushed:',
  315. ':relieved:',
  316. ':satisfied:',
  317. ':grin:',
  318. ':wink:',
  319. ':stuck_out_tongue_winking_eye:',
  320. ':stuck_out_tongue_closed_eyes:',
  321. ':grinning:',
  322. ':kissing:',
  323. ':kissing_smiling_eyes:',
  324. ':stuck_out_tongue:',
  325. ':sleeping:',
  326. ':worried:',
  327. ':frowning:',
  328. ':anguished:',
  329. ':open_mouth:',
  330. ':grimacing:',
  331. ':confused:',
  332. ':hushed:',
  333. ':expressionless:',
  334. ':unamused:',
  335. ':sweat_smile:',
  336. ':sweat:',
  337. ':disappointed_relieved:',
  338. ':weary:',
  339. ':pensive:',
  340. ':disappointed:',
  341. ':confounded:',
  342. ':fearful:',
  343. ':cold_sweat:',
  344. ':persevere:',
  345. ':cry:',
  346. ':sob:',
  347. ':joy:',
  348. ':astonished:',
  349. ':scream:',
  350. ':neckbeard:',
  351. ':tired_face:',
  352. ':angry:',
  353. ':rage:',
  354. ':triumph:',
  355. ':sleepy:',
  356. ':yum:',
  357. ':mask:',
  358. ':sunglasses:',
  359. ':dizzy_face:',
  360. ':imp:',
  361. ':smiling_imp:',
  362. ':neutral_face:',
  363. ':no_mouth:',
  364. ':innocent:',
  365. ':alien:',
  366. ];
  367. }
  368. /**
  369. * Get the chat history file name.
  370. *
  371. * @param bool $absolute Optional. Whether get the base or the absolute file path
  372. * @param int $friendId optional
  373. *
  374. * @return string
  375. */
  376. public function getFileName($absolute = false, $friendId = 0)
  377. {
  378. $date = date('Y-m-d');
  379. $base = 'messages-'.$date.'.log.html';
  380. if ($this->groupId && !$friendId) {
  381. $base = 'messages-'.$date.'_gid-'.$this->groupId.'.log.html';
  382. } elseif ($this->sessionId && !$friendId) {
  383. $base = 'messages-'.$date.'_sid-'.$this->sessionId.'.log.html';
  384. } elseif ($friendId) {
  385. if ($this->userId < $friendId) {
  386. $base = 'messages-'.$date.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
  387. } else {
  388. $base = 'messages-'.$date.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
  389. }
  390. }
  391. if (!$absolute) {
  392. return $base;
  393. }
  394. $courseInfo = api_get_course_info_by_id($this->courseId);
  395. $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
  396. $chatPath = $document_path.'/chat_files/';
  397. if ($this->groupId) {
  398. $group_info = GroupManager::get_group_properties($this->groupId);
  399. $chatPath = $document_path.$group_info['directory'].'/chat_files/';
  400. }
  401. return $chatPath.$base;
  402. }
  403. /**
  404. * Get the chat history.
  405. *
  406. * @param bool $reset
  407. * @param int $friendId optional
  408. *
  409. * @return string
  410. */
  411. public function readMessages($reset = false, $friendId = 0)
  412. {
  413. $courseInfo = api_get_course_info_by_id($this->courseId);
  414. $date_now = date('Y-m-d');
  415. $isMaster = (bool) api_is_course_admin();
  416. $basepath_chat = '/chat_files';
  417. $document_path = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
  418. $group_info = [];
  419. if ($this->groupId) {
  420. $group_info = GroupManager:: get_group_properties($this->groupId);
  421. $basepath_chat = $group_info['directory'].'/chat_files';
  422. }
  423. $chat_path = $document_path.$basepath_chat.'/';
  424. if (!is_dir($chat_path)) {
  425. if (is_file($chat_path)) {
  426. @unlink($chat_path);
  427. }
  428. if (!api_is_anonymous()) {
  429. @mkdir($chat_path, api_get_permissions_for_new_directories());
  430. // Save chat files document for group into item property
  431. if ($this->groupId) {
  432. $doc_id = add_document(
  433. $courseInfo,
  434. $basepath_chat,
  435. 'folder',
  436. 0,
  437. 'chat_files',
  438. null,
  439. 0,
  440. true,
  441. 0,
  442. 0,
  443. 0,
  444. false
  445. );
  446. api_item_property_update(
  447. $courseInfo,
  448. TOOL_DOCUMENT,
  449. $doc_id,
  450. 'FolderCreated',
  451. null,
  452. $group_info,
  453. null,
  454. null,
  455. null
  456. );
  457. }
  458. }
  459. }
  460. $filename_chat = 'messages-'.$date_now.'.log.html';
  461. if ($this->groupId && !$friendId) {
  462. $filename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId.'.log.html';
  463. } elseif ($this->sessionId && !$friendId) {
  464. $filename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId.'.log.html';
  465. } elseif ($friendId) {
  466. if ($this->userId < $friendId) {
  467. $filename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId.'.log.html';
  468. } else {
  469. $filename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId.'.log.html';
  470. }
  471. }
  472. if (!file_exists($chat_path.$filename_chat)) {
  473. @fclose(fopen($chat_path.$filename_chat, 'w'));
  474. if (!api_is_anonymous()) {
  475. $doc_id = add_document(
  476. $courseInfo,
  477. $basepath_chat.'/'.$filename_chat,
  478. 'file',
  479. 0,
  480. $filename_chat,
  481. null,
  482. 0,
  483. true,
  484. 0,
  485. 0,
  486. 0,
  487. false
  488. );
  489. if ($doc_id) {
  490. api_item_property_update(
  491. $courseInfo,
  492. TOOL_DOCUMENT,
  493. $doc_id,
  494. 'DocumentAdded',
  495. $this->userId,
  496. $group_info,
  497. null,
  498. null,
  499. null,
  500. $this->sessionId
  501. );
  502. api_item_property_update(
  503. $courseInfo,
  504. TOOL_DOCUMENT,
  505. $doc_id,
  506. 'invisible',
  507. $this->userId,
  508. $group_info,
  509. null,
  510. null,
  511. null,
  512. $this->sessionId
  513. );
  514. item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
  515. }
  516. }
  517. }
  518. $basename_chat = 'messages-'.$date_now;
  519. if ($this->groupId && !$friendId) {
  520. $basename_chat = 'messages-'.$date_now.'_gid-'.$this->groupId;
  521. } elseif ($this->sessionId && !$friendId) {
  522. $basename_chat = 'messages-'.$date_now.'_sid-'.$this->sessionId;
  523. } elseif ($friendId) {
  524. if ($this->userId < $friendId) {
  525. $basename_chat = 'messages-'.$date_now.'_uid-'.$this->userId.'-'.$friendId;
  526. } else {
  527. $basename_chat = 'messages-'.$date_now.'_uid-'.$friendId.'-'.$this->userId;
  528. }
  529. }
  530. if ($reset && $isMaster) {
  531. $i = 1;
  532. while (file_exists($chat_path.$basename_chat.'-'.$i.'.log.html')) {
  533. $i++;
  534. }
  535. @rename($chat_path.$basename_chat.'.log.html', $chat_path.$basename_chat.'-'.$i.'.log.html');
  536. @fclose(fopen($chat_path.$basename_chat.'.log.html', 'w'));
  537. $doc_id = add_document(
  538. $courseInfo,
  539. $basepath_chat.'/'.$basename_chat.'-'.$i.'.log.html',
  540. 'file',
  541. filesize($chat_path.$basename_chat.'-'.$i.'.log.html'),
  542. $basename_chat.'-'.$i.'.log.html',
  543. null,
  544. 0,
  545. true,
  546. 0,
  547. 0,
  548. 0,
  549. false
  550. );
  551. api_item_property_update(
  552. $courseInfo,
  553. TOOL_DOCUMENT,
  554. $doc_id,
  555. 'DocumentAdded',
  556. $this->userId,
  557. $group_info,
  558. null,
  559. null,
  560. null,
  561. $this->sessionId
  562. );
  563. api_item_property_update(
  564. $courseInfo,
  565. TOOL_DOCUMENT,
  566. $doc_id,
  567. 'invisible',
  568. $this->userId,
  569. $group_info,
  570. null,
  571. null,
  572. null,
  573. $this->sessionId
  574. );
  575. item_property_update_on_folder($courseInfo, $basepath_chat, $this->userId);
  576. $doc_id = DocumentManager::get_document_id(
  577. $courseInfo,
  578. $basepath_chat.'/'.$basename_chat.'.log.html'
  579. );
  580. update_existing_document($courseInfo, $doc_id, 0);
  581. }
  582. $remove = 0;
  583. $content = [];
  584. if (file_exists($chat_path.$basename_chat.'.log.html')) {
  585. $content = file($chat_path.$basename_chat.'.log.html');
  586. $nbr_lines = sizeof($content);
  587. $remove = $nbr_lines - 100;
  588. }
  589. if ($remove < 0) {
  590. $remove = 0;
  591. }
  592. array_splice($content, 0, $remove);
  593. if (isset($_GET['origin']) && $_GET['origin'] == 'whoisonline') {
  594. //the caller
  595. $content[0] = get_lang('CallSent').'<br />'.$content[0];
  596. }
  597. $history = '<div id="content-chat">';
  598. foreach ($content as $this_line) {
  599. $history .= $this_line;
  600. }
  601. $history .= '</div>';
  602. if ($isMaster || $GLOBALS['is_session_general_coach']) {
  603. $history .= '
  604. <div id="clear-chat">
  605. <button type="button" id="chat-reset" class="btn btn-danger btn-sm">
  606. '.get_lang('ClearList').'
  607. </button>
  608. </div>
  609. ';
  610. }
  611. return $history;
  612. }
  613. /**
  614. * Get the number of users connected in chat.
  615. *
  616. * @return int
  617. */
  618. public function countUsersOnline()
  619. {
  620. $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
  621. $date->modify('-5 seconds');
  622. if ($this->groupId) {
  623. $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
  624. } else {
  625. $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
  626. }
  627. $number = Database::getManager()
  628. ->createQuery("
  629. SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
  630. WHERE ccc.lastConnection > :date AND ccc.cId = :course $extraCondition
  631. ")
  632. ->setParameters([
  633. 'date' => $date,
  634. 'course' => $this->courseId,
  635. ])
  636. ->getSingleScalarResult();
  637. return (int) $number;
  638. }
  639. /**
  640. * Get the users online data.
  641. *
  642. * @throws \Doctrine\ORM\ORMException
  643. * @throws \Doctrine\ORM\OptimisticLockException
  644. * @throws \Doctrine\ORM\TransactionRequiredException
  645. *
  646. * @return array
  647. */
  648. public function listUsersOnline()
  649. {
  650. $subscriptions = $this->getUsersSubscriptions();
  651. $usersInfo = [];
  652. if ($this->groupId) {
  653. /** @var User $groupUser */
  654. foreach ($subscriptions as $groupUser) {
  655. $usersInfo[] = $this->formatUser(
  656. $groupUser,
  657. $groupUser->getStatus()
  658. );
  659. }
  660. } else {
  661. /** @var CourseRelUser|SessionRelCourseRelUser $subscription */
  662. foreach ($subscriptions as $subscription) {
  663. $user = $subscription->getUser();
  664. $usersInfo[] = $this->formatUser(
  665. $user,
  666. $this->sessionId ? $user->getStatus() : $subscription->getStatus()
  667. );
  668. }
  669. }
  670. return $usersInfo;
  671. }
  672. /**
  673. * Format the user data to return it in the user list.
  674. *
  675. * @param User $user
  676. * @param int $status
  677. *
  678. * @return array
  679. */
  680. private function formatUser(User $user, $status)
  681. {
  682. return [
  683. 'id' => $user->getId(),
  684. 'firstname' => $user->getFirstname(),
  685. 'lastname' => $user->getLastname(),
  686. 'status' => $status,
  687. 'image_url' => UserManager::getUserPicture($user->getId(), USER_IMAGE_SIZE_MEDIUM),
  688. 'profile_url' => api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user->getId(),
  689. 'complete_name' => UserManager::formatUserFullName($user),
  690. 'username' => $user->getUsername(),
  691. 'email' => $user->getEmail(),
  692. 'isConnected' => $this->userIsConnected($user->getId()),
  693. ];
  694. }
  695. /**
  696. * Get the users subscriptions (SessionRelCourseRelUser array or CourseRelUser array) for chat.
  697. *
  698. * @throws \Doctrine\ORM\ORMException
  699. * @throws \Doctrine\ORM\OptimisticLockException
  700. * @throws \Doctrine\ORM\TransactionRequiredException
  701. *
  702. * @return \Doctrine\Common\Collections\ArrayCollection
  703. */
  704. private function getUsersSubscriptions()
  705. {
  706. $em = Database::getManager();
  707. if ($this->groupId) {
  708. $students = $em
  709. ->createQuery(
  710. 'SELECT u FROM ChamiloUserBundle:User u
  711. INNER JOIN ChamiloCourseBundle:CGroupRelUser gru
  712. WITH u.id = gru.userId AND gru.cId = :course
  713. WHERE u.id != :user AND gru.groupId = :group
  714. AND u.active = true'
  715. )
  716. ->setParameters(['course' => $this->courseId, 'user' => $this->userId, 'group' => $this->groupId])
  717. ->getResult();
  718. $tutors = $em
  719. ->createQuery(
  720. 'SELECT u FROM ChamiloUserBundle:User u
  721. INNER JOIN ChamiloCourseBundle:CGroupRelTutor grt
  722. WITH u.id = grt.userId AND grt.cId = :course
  723. WHERE u.id != :user AND grt.groupId = :group
  724. AND u.active = true'
  725. )
  726. ->setParameters(['course' => $this->courseId, 'user' => $this->userId, 'group' => $this->groupId])
  727. ->getResult();
  728. return array_merge($tutors, $students);
  729. }
  730. /** @var Course $course */
  731. $course = $em->find('ChamiloCoreBundle:Course', $this->courseId);
  732. if ($this->sessionId) {
  733. /** @var Session $session */
  734. $session = $em->find('ChamiloCoreBundle:Session', $this->sessionId);
  735. $criteria = Criteria::create()->where(Criteria::expr()->eq('course', $course));
  736. $userIsCoach = api_is_course_session_coach($this->userId, $course->getId(), $session->getId());
  737. if (api_get_configuration_value('course_chat_restrict_to_coach')) {
  738. if ($userIsCoach) {
  739. $criteria->andWhere(
  740. Criteria::expr()->eq('status', Session::STUDENT)
  741. );
  742. } else {
  743. $criteria->andWhere(
  744. Criteria::expr()->eq('status', Session::COACH)
  745. );
  746. }
  747. }
  748. $criteria->orderBy(['status' => Criteria::DESC]);
  749. return $session
  750. ->getUserCourseSubscriptions()
  751. ->matching($criteria)
  752. ->filter(function (SessionRelCourseRelUser $sessionRelCourseRelUser) {
  753. return $sessionRelCourseRelUser->getUser()->isActive();
  754. });
  755. }
  756. return $course
  757. ->getUsers()
  758. ->filter(function (CourseRelUser $courseRelUser) {
  759. return $courseRelUser->getUser()->isActive();
  760. });
  761. }
  762. /**
  763. * Check if a user is connected in course chat.
  764. *
  765. * @param int $userId
  766. *
  767. * @return int
  768. */
  769. private function userIsConnected($userId)
  770. {
  771. $date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
  772. $date->modify('-5 seconds');
  773. if ($this->groupId) {
  774. $extraCondition = 'AND ccc.toGroupId = '.intval($this->groupId);
  775. } else {
  776. $extraCondition = 'AND ccc.sessionId = '.intval($this->sessionId);
  777. }
  778. $number = Database::getManager()
  779. ->createQuery("
  780. SELECT COUNT(ccc.userId) FROM ChamiloCourseBundle:CChatConnected ccc
  781. WHERE ccc.lastConnection > :date AND ccc.cId = :course AND ccc.userId = :user $extraCondition
  782. ")
  783. ->setParameters([
  784. 'date' => $date,
  785. 'course' => $this->courseId,
  786. 'user' => $userId,
  787. ])
  788. ->getSingleScalarResult();
  789. return (int) $number;
  790. }
  791. }