Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Library of functions and constants for module chat 19 * 20 * @package mod_chat 21 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once($CFG->dirroot.'/calendar/lib.php'); 28 29 // Event types. 30 define('CHAT_EVENT_TYPE_CHATTIME', 'chattime'); 31 32 // Gap between sessions. 5 minutes or more of idleness between messages in a chat means the messages belong in different sessions. 33 define('CHAT_SESSION_GAP', 300); 34 // Don't publish next chat time 35 define('CHAT_SCHEDULE_NONE', 0); 36 // Publish the specified time only. 37 define('CHAT_SCHEDULE_SINGLE', 1); 38 // Repeat chat session at the same time daily. 39 define('CHAT_SCHEDULE_DAILY', 2); 40 // Repeat chat session at the same time weekly. 41 define('CHAT_SCHEDULE_WEEKLY', 3); 42 43 // The HTML head for the message window to start with (<!-- nix --> is used to get some browsers starting with output. 44 global $CHAT_HTMLHEAD; 45 $CHAT_HTMLHEAD = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\"><html><head></head>\n<body>\n\n".padding(200); 46 47 // The HTML head for the message window to start with (with js scrolling). 48 global $CHAT_HTMLHEAD_JS; 49 $CHAT_HTMLHEAD_JS = <<<EOD 50 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> 51 <html><head><script type="text/javascript"> 52 //<![CDATA[ 53 function move() { 54 if (scroll_active) 55 window.scroll(1,400000); 56 window.setTimeout("move()",100); 57 } 58 var scroll_active = true; 59 move(); 60 //]]> 61 </script> 62 </head> 63 <body onBlur="scroll_active = true" onFocus="scroll_active = false"> 64 EOD; 65 global $CHAT_HTMLHEAD_JS; 66 $CHAT_HTMLHEAD_JS .= padding(200); 67 68 // The HTML code for standard empty pages (e.g. if a user was kicked out). 69 global $CHAT_HTMLHEAD_OUT; 70 $CHAT_HTMLHEAD_OUT = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\"><html><head><title>You are out!</title></head><body></body></html>"; 71 72 // The HTML head for the message input page. 73 global $CHAT_HTMLHEAD_MSGINPUT; 74 $CHAT_HTMLHEAD_MSGINPUT = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\"><html><head><title>Message Input</title></head><body>"; 75 76 // The HTML code for the message input page, with JavaScript. 77 global $CHAT_HTMLHEAD_MSGINPUT_JS; 78 $CHAT_HTMLHEAD_MSGINPUT_JS = <<<EOD 79 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> 80 <html> 81 <head><title>Message Input</title> 82 <script type="text/javascript"> 83 //<![CDATA[ 84 scroll_active = true; 85 function empty_field_and_submit() { 86 document.fdummy.arsc_message.value=document.f.arsc_message.value; 87 document.fdummy.submit(); 88 document.f.arsc_message.focus(); 89 document.f.arsc_message.select(); 90 return false; 91 } 92 //]]> 93 </script> 94 </head><body OnLoad="document.f.arsc_message.focus();document.f.arsc_message.select();">; 95 EOD; 96 97 // Dummy data that gets output to the browser as needed, in order to make it show output. 98 global $CHAT_DUMMY_DATA; 99 $CHAT_DUMMY_DATA = padding(200); 100 101 /** 102 * @param int $n 103 * @return string 104 */ 105 function padding($n) { 106 $str = ''; 107 for ($i = 0; $i < $n; $i++) { 108 $str .= "<!-- nix -->\n"; 109 } 110 return $str; 111 } 112 113 /** 114 * Given an object containing all the necessary data, 115 * (defined by the form in mod_form.php) this function 116 * will create a new instance and return the id number 117 * of the new instance. 118 * 119 * @global object 120 * @param object $chat 121 * @return int 122 */ 123 function chat_add_instance($chat) { 124 global $DB; 125 126 $chat->timemodified = time(); 127 $chat->chattime = chat_calculate_next_chat_time($chat->schedule, $chat->chattime); 128 129 $returnid = $DB->insert_record("chat", $chat); 130 131 if ($chat->schedule > 0) { 132 $event = new stdClass(); 133 $event->type = CALENDAR_EVENT_TYPE_ACTION; 134 $event->name = $chat->name; 135 $event->description = format_module_intro('chat', $chat, $chat->coursemodule, false); 136 $event->format = FORMAT_HTML; 137 $event->courseid = $chat->course; 138 $event->groupid = 0; 139 $event->userid = 0; 140 $event->modulename = 'chat'; 141 $event->instance = $returnid; 142 $event->eventtype = CHAT_EVENT_TYPE_CHATTIME; 143 $event->timestart = $chat->chattime; 144 $event->timesort = $chat->chattime; 145 $event->timeduration = 0; 146 147 calendar_event::create($event, false); 148 } 149 150 if (!empty($chat->completionexpected)) { 151 \core_completion\api::update_completion_date_event($chat->coursemodule, 'chat', $returnid, $chat->completionexpected); 152 } 153 154 return $returnid; 155 } 156 157 /** 158 * Given an object containing all the necessary data, 159 * (defined by the form in mod_form.php) this function 160 * will update an existing instance with new data. 161 * 162 * @global object 163 * @param object $chat 164 * @return bool 165 */ 166 function chat_update_instance($chat) { 167 global $DB; 168 169 $chat->timemodified = time(); 170 $chat->id = $chat->instance; 171 $chat->chattime = chat_calculate_next_chat_time($chat->schedule, $chat->chattime); 172 173 $DB->update_record("chat", $chat); 174 175 $event = new stdClass(); 176 177 if ($event->id = $DB->get_field('event', 'id', array('modulename' => 'chat', 178 'instance' => $chat->id, 'eventtype' => CHAT_EVENT_TYPE_CHATTIME))) { 179 180 if ($chat->schedule > 0) { 181 $event->type = CALENDAR_EVENT_TYPE_ACTION; 182 $event->name = $chat->name; 183 $event->description = format_module_intro('chat', $chat, $chat->coursemodule, false); 184 $event->format = FORMAT_HTML; 185 $event->timestart = $chat->chattime; 186 $event->timesort = $chat->chattime; 187 188 $calendarevent = calendar_event::load($event->id); 189 $calendarevent->update($event, false); 190 } else { 191 // Do not publish this event, so delete it. 192 $calendarevent = calendar_event::load($event->id); 193 $calendarevent->delete(); 194 } 195 } else { 196 // No event, do we need to create one? 197 if ($chat->schedule > 0) { 198 $event = new stdClass(); 199 $event->type = CALENDAR_EVENT_TYPE_ACTION; 200 $event->name = $chat->name; 201 $event->description = format_module_intro('chat', $chat, $chat->coursemodule, false); 202 $event->format = FORMAT_HTML; 203 $event->courseid = $chat->course; 204 $event->groupid = 0; 205 $event->userid = 0; 206 $event->modulename = 'chat'; 207 $event->instance = $chat->id; 208 $event->eventtype = CHAT_EVENT_TYPE_CHATTIME; 209 $event->timestart = $chat->chattime; 210 $event->timesort = $chat->chattime; 211 $event->timeduration = 0; 212 213 calendar_event::create($event, false); 214 } 215 } 216 217 $completionexpected = (!empty($chat->completionexpected)) ? $chat->completionexpected : null; 218 \core_completion\api::update_completion_date_event($chat->coursemodule, 'chat', $chat->id, $completionexpected); 219 220 return true; 221 } 222 223 /** 224 * Given an ID of an instance of this module, 225 * this function will permanently delete the instance 226 * and any data that depends on it. 227 * 228 * @global object 229 * @param int $id 230 * @return bool 231 */ 232 function chat_delete_instance($id) { 233 global $DB; 234 235 if (! $chat = $DB->get_record('chat', array('id' => $id))) { 236 return false; 237 } 238 239 $result = true; 240 241 // Delete any dependent records here. 242 243 if (! $DB->delete_records('chat', array('id' => $chat->id))) { 244 $result = false; 245 } 246 if (! $DB->delete_records('chat_messages', array('chatid' => $chat->id))) { 247 $result = false; 248 } 249 if (! $DB->delete_records('chat_messages_current', array('chatid' => $chat->id))) { 250 $result = false; 251 } 252 if (! $DB->delete_records('chat_users', array('chatid' => $chat->id))) { 253 $result = false; 254 } 255 256 if (! $DB->delete_records('event', array('modulename' => 'chat', 'instance' => $chat->id))) { 257 $result = false; 258 } 259 260 return $result; 261 } 262 263 /** 264 * Given a course and a date, prints a summary of all chat rooms past and present 265 * This function is called from block_recent_activity 266 * 267 * @global object 268 * @global object 269 * @global object 270 * @param object $course 271 * @param bool $viewfullnames 272 * @param int|string $timestart Timestamp 273 * @return bool 274 */ 275 function chat_print_recent_activity($course, $viewfullnames, $timestart) { 276 global $CFG, $USER, $DB, $OUTPUT; 277 278 // This is approximate only, but it is really fast. 279 $timeout = $CFG->chat_old_ping * 10; 280 281 if (!$mcms = $DB->get_records_sql("SELECT cm.id, MAX(chm.timestamp) AS lasttime 282 FROM {course_modules} cm 283 JOIN {modules} md ON md.id = cm.module 284 JOIN {chat} ch ON ch.id = cm.instance 285 JOIN {chat_messages} chm ON chm.chatid = ch.id 286 WHERE chm.timestamp > ? AND ch.course = ? AND md.name = 'chat' 287 GROUP BY cm.id 288 ORDER BY lasttime ASC", array($timestart, $course->id))) { 289 return false; 290 } 291 292 $past = array(); 293 $current = array(); 294 $modinfo = get_fast_modinfo($course); // Reference needed because we might load the groups. 295 296 foreach ($mcms as $cmid => $mcm) { 297 if (!array_key_exists($cmid, $modinfo->cms)) { 298 continue; 299 } 300 $cm = $modinfo->cms[$cmid]; 301 if (!$modinfo->cms[$cm->id]->uservisible) { 302 continue; 303 } 304 305 if (groups_get_activity_groupmode($cm) != SEPARATEGROUPS 306 or has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) { 307 if ($timeout > time() - $mcm->lasttime) { 308 $current[] = $cm; 309 } else { 310 $past[] = $cm; 311 } 312 313 continue; 314 } 315 316 // Verify groups in separate mode. 317 if (!$mygroupids = $modinfo->get_groups($cm->groupingid)) { 318 continue; 319 } 320 321 // Ok, last post was not for my group - we have to query db to get last message from one of my groups. 322 // The only minor problem is that the order will not be correct. 323 $mygroupids = implode(',', $mygroupids); 324 325 if (!$mcm = $DB->get_record_sql("SELECT cm.id, MAX(chm.timestamp) AS lasttime 326 FROM {course_modules} cm 327 JOIN {chat} ch ON ch.id = cm.instance 328 JOIN {chat_messages_current} chm ON chm.chatid = ch.id 329 WHERE chm.timestamp > ? AND cm.id = ? AND 330 (chm.groupid IN ($mygroupids) OR chm.groupid = 0) 331 GROUP BY cm.id", array($timestart, $cm->id))) { 332 continue; 333 } 334 335 $mcms[$cmid]->lasttime = $mcm->lasttime; 336 if ($timeout > time() - $mcm->lasttime) { 337 $current[] = $cm; 338 } else { 339 $past[] = $cm; 340 } 341 } 342 343 if (!$past and !$current) { 344 return false; 345 } 346 347 $strftimerecent = get_string('strftimerecent'); 348 349 if ($past) { 350 echo $OUTPUT->heading(get_string("pastchats", 'chat') . ':', 6); 351 352 foreach ($past as $cm) { 353 $link = $CFG->wwwroot.'/mod/chat/view.php?id='.$cm->id; 354 $date = userdate($mcms[$cm->id]->lasttime, $strftimerecent); 355 echo '<div class="head"><div class="date">'.$date.'</div></div>'; 356 echo '<div class="info"><a href="'.$link.'">'.format_string($cm->name, true).'</a></div>'; 357 } 358 } 359 360 if ($current) { 361 echo $OUTPUT->heading(get_string("currentchats", 'chat') . ':', 6); 362 363 $oldest = floor((time() - $CFG->chat_old_ping) / 10) * 10; // Better db caching. 364 365 $timeold = time() - $CFG->chat_old_ping; 366 $timeold = floor($timeold / 10) * 10; // Better db caching. 367 $timeoldext = time() - ($CFG->chat_old_ping * 10); // JSless gui_basic needs much longer timeouts. 368 $timeoldext = floor($timeoldext / 10) * 10; // Better db caching. 369 370 $params = array('timeold' => $timeold, 'timeoldext' => $timeoldext, 'cmid' => $cm->id); 371 372 $timeout = "AND ((chu.version<>'basic' AND chu.lastping>:timeold) OR (chu.version='basic' AND chu.lastping>:timeoldext))"; 373 374 foreach ($current as $cm) { 375 // Count users first. 376 $mygroupids = $modinfo->groups[$cm->groupingid]; 377 if (!empty($mygroupids)) { 378 list($subquery, $subparams) = $DB->get_in_or_equal($mygroupids, SQL_PARAMS_NAMED, 'gid'); 379 $params += $subparams; 380 $groupselect = "AND (chu.groupid $subquery OR chu.groupid = 0)"; 381 } else { 382 $groupselect = ""; 383 } 384 385 $userfieldsapi = \core_user\fields::for_userpic(); 386 $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; 387 if (!$users = $DB->get_records_sql("SELECT $userfields 388 FROM {course_modules} cm 389 JOIN {chat} ch ON ch.id = cm.instance 390 JOIN {chat_users} chu ON chu.chatid = ch.id 391 JOIN {user} u ON u.id = chu.userid 392 WHERE cm.id = :cmid $timeout $groupselect 393 GROUP BY $userfields", $params)) { 394 } 395 396 $link = $CFG->wwwroot.'/mod/chat/view.php?id='.$cm->id; 397 $date = userdate($mcms[$cm->id]->lasttime, $strftimerecent); 398 399 echo '<div class="head"><div class="date">'.$date.'</div></div>'; 400 echo '<div class="info"><a href="'.$link.'">'.format_string($cm->name, true).'</a></div>'; 401 echo '<div class="userlist">'; 402 if ($users) { 403 echo '<ul>'; 404 foreach ($users as $user) { 405 echo '<li>'.fullname($user, $viewfullnames).'</li>'; 406 } 407 echo '</ul>'; 408 } 409 echo '</div>'; 410 } 411 } 412 413 return true; 414 } 415 416 /** 417 * This standard function will check all instances of this module 418 * and make sure there are up-to-date events created for each of them. 419 * If courseid = 0, then every chat event in the site is checked, else 420 * only chat events belonging to the course specified are checked. 421 * This function is used, in its new format, by restore_refresh_events() 422 * 423 * @global object 424 * @param int $courseid 425 * @param int|stdClass $instance Chat module instance or ID. 426 * @param int|stdClass $cm Course module object or ID. 427 * @return bool 428 */ 429 function chat_refresh_events($courseid = 0, $instance = null, $cm = null) { 430 global $DB; 431 432 // If we have instance information then we can just update the one event instead of updating all events. 433 if (isset($instance)) { 434 if (!is_object($instance)) { 435 $instance = $DB->get_record('chat', array('id' => $instance), '*', MUST_EXIST); 436 } 437 if (isset($cm)) { 438 if (!is_object($cm)) { 439 chat_prepare_update_events($instance); 440 return true; 441 } else { 442 chat_prepare_update_events($instance, $cm); 443 return true; 444 } 445 } 446 } 447 448 if ($courseid) { 449 if (! $chats = $DB->get_records("chat", array("course" => $courseid))) { 450 return true; 451 } 452 } else { 453 if (! $chats = $DB->get_records("chat")) { 454 return true; 455 } 456 } 457 foreach ($chats as $chat) { 458 chat_prepare_update_events($chat); 459 } 460 return true; 461 } 462 463 /** 464 * Updates both the normal and completion calendar events for chat. 465 * 466 * @param stdClass $chat The chat object (from the DB) 467 * @param stdClass $cm The course module object. 468 */ 469 function chat_prepare_update_events($chat, $cm = null) { 470 global $DB; 471 if (!isset($cm)) { 472 $cm = get_coursemodule_from_instance('chat', $chat->id, $chat->course); 473 } 474 $event = new stdClass(); 475 $event->name = $chat->name; 476 $event->type = CALENDAR_EVENT_TYPE_ACTION; 477 $event->description = format_module_intro('chat', $chat, $cm->id, false); 478 $event->format = FORMAT_HTML; 479 $event->timestart = $chat->chattime; 480 $event->timesort = $chat->chattime; 481 if ($event->id = $DB->get_field('event', 'id', array('modulename' => 'chat', 'instance' => $chat->id, 482 'eventtype' => CHAT_EVENT_TYPE_CHATTIME))) { 483 $calendarevent = calendar_event::load($event->id); 484 $calendarevent->update($event, false); 485 } else if ($chat->schedule > 0) { 486 // The chat is scheduled and the event should be published. 487 $event->courseid = $chat->course; 488 $event->groupid = 0; 489 $event->userid = 0; 490 $event->modulename = 'chat'; 491 $event->instance = $chat->id; 492 $event->eventtype = CHAT_EVENT_TYPE_CHATTIME; 493 $event->timeduration = 0; 494 $event->visible = $cm->visible; 495 calendar_event::create($event, false); 496 } 497 } 498 499 // Functions that require some SQL. 500 501 /** 502 * @global object 503 * @param int $chatid 504 * @param int $groupid 505 * @param int $groupingid 506 * @return array 507 */ 508 function chat_get_users($chatid, $groupid=0, $groupingid=0) { 509 global $DB; 510 511 $params = array('chatid' => $chatid, 'groupid' => $groupid, 'groupingid' => $groupingid); 512 513 if ($groupid) { 514 $groupselect = " AND (c.groupid=:groupid OR c.groupid='0')"; 515 } else { 516 $groupselect = ""; 517 } 518 519 if (!empty($groupingid)) { 520 $groupingjoin = "JOIN {groups_members} gm ON u.id = gm.userid 521 JOIN {groupings_groups} gg ON gm.groupid = gg.groupid AND gg.groupingid = :groupingid "; 522 523 } else { 524 $groupingjoin = ''; 525 } 526 527 $userfieldsapi = \core_user\fields::for_userpic(); 528 $ufields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; 529 return $DB->get_records_sql("SELECT DISTINCT $ufields, c.lastmessageping, c.firstping 530 FROM {chat_users} c 531 JOIN {user} u ON u.id = c.userid $groupingjoin 532 WHERE c.chatid = :chatid $groupselect 533 ORDER BY c.firstping ASC", $params); 534 } 535 536 /** 537 * @global object 538 * @param int $chatid 539 * @param int $groupid 540 * @return array 541 */ 542 function chat_get_latest_message($chatid, $groupid=0) { 543 global $DB; 544 545 $params = array('chatid' => $chatid, 'groupid' => $groupid); 546 547 if ($groupid) { 548 $groupselect = "AND (groupid=:groupid OR groupid=0)"; 549 } else { 550 $groupselect = ""; 551 } 552 553 $sql = "SELECT * 554 FROM {chat_messages_current} WHERE chatid = :chatid $groupselect 555 ORDER BY timestamp DESC, id DESC"; 556 557 // Return the lastest one message. 558 return $DB->get_record_sql($sql, $params, true); 559 } 560 561 /** 562 * login if not already logged in 563 * 564 * @global object 565 * @global object 566 * @param int $chatid 567 * @param string $version 568 * @param int $groupid 569 * @param object $course 570 * @return bool|int Returns the chat users sid or false 571 */ 572 function chat_login_user($chatid, $version, $groupid, $course) { 573 global $USER, $DB; 574 575 if (($version != 'sockets') and $chatuser = $DB->get_record('chat_users', array('chatid' => $chatid, 576 'userid' => $USER->id, 577 'groupid' => $groupid))) { 578 // This will update logged user information. 579 $chatuser->version = $version; 580 $chatuser->ip = $USER->lastip; 581 $chatuser->lastping = time(); 582 $chatuser->lang = current_language(); 583 584 // Sometimes $USER->lastip is not setup properly during login. 585 // Update with current value if possible or provide a dummy value for the db. 586 if (empty($chatuser->ip)) { 587 $chatuser->ip = getremoteaddr(); 588 } 589 590 if (($chatuser->course != $course->id) or ($chatuser->userid != $USER->id)) { 591 return false; 592 } 593 $DB->update_record('chat_users', $chatuser); 594 595 } else { 596 $chatuser = new stdClass(); 597 $chatuser->chatid = $chatid; 598 $chatuser->userid = $USER->id; 599 $chatuser->groupid = $groupid; 600 $chatuser->version = $version; 601 $chatuser->ip = $USER->lastip; 602 $chatuser->lastping = $chatuser->firstping = $chatuser->lastmessageping = time(); 603 $chatuser->sid = random_string(32); 604 $chatuser->course = $course->id; // Caching - needed for current_language too. 605 $chatuser->lang = current_language(); // Caching - to resource intensive to find out later. 606 607 // Sometimes $USER->lastip is not setup properly during login. 608 // Update with current value if possible or provide a dummy value for the db. 609 if (empty($chatuser->ip)) { 610 $chatuser->ip = getremoteaddr(); 611 } 612 613 $DB->insert_record('chat_users', $chatuser); 614 615 if ($version == 'sockets') { 616 // Do not send 'enter' message, chatd will do it. 617 } else { 618 chat_send_chatmessage($chatuser, 'enter', true); 619 } 620 } 621 622 return $chatuser->sid; 623 } 624 625 /** 626 * Delete the old and in the way 627 * 628 * @global object 629 * @global object 630 */ 631 function chat_delete_old_users() { 632 // Delete the old and in the way. 633 global $CFG, $DB; 634 635 $timeold = time() - $CFG->chat_old_ping; 636 $timeoldext = time() - ($CFG->chat_old_ping * 10); // JSless gui_basic needs much longer timeouts. 637 638 $query = "(version<>'basic' AND lastping<?) OR (version='basic' AND lastping<?)"; 639 $params = array($timeold, $timeoldext); 640 641 if ($oldusers = $DB->get_records_select('chat_users', $query, $params) ) { 642 $DB->delete_records_select('chat_users', $query, $params); 643 foreach ($oldusers as $olduser) { 644 chat_send_chatmessage($olduser, 'exit', true); 645 } 646 } 647 } 648 649 /** 650 * Calculate next chat session time based on schedule. 651 * 652 * @param int $schedule 653 * @param int $chattime 654 * 655 * @return int timestamp 656 */ 657 function chat_calculate_next_chat_time(int $schedule, int $chattime): int { 658 $timenow = time(); 659 660 switch ($schedule) { 661 case CHAT_SCHEDULE_DAILY: { // Repeat daily. 662 while ($chattime <= $timenow) { 663 $chattime += DAYSECS; 664 } 665 break; 666 } 667 case CHAT_SCHEDULE_WEEKLY: { // Repeat weekly. 668 while ($chattime <= $timenow) { 669 $chattime += WEEKSECS; 670 } 671 break; 672 } 673 } 674 675 return $chattime; 676 } 677 678 /** 679 * Updates chat records so that the next chat time is correct 680 * 681 * @global object 682 * @param int $chatid 683 * @return void 684 */ 685 function chat_update_chat_times($chatid=0) { 686 // Updates chat records so that the next chat time is correct. 687 global $DB; 688 689 $timenow = time(); 690 691 $params = array('timenow' => $timenow, 'chatid' => $chatid); 692 693 if ($chatid) { 694 if (!$chats[] = $DB->get_record_select("chat", "id = :chatid AND chattime <= :timenow AND schedule > 0", $params)) { 695 return; 696 } 697 } else { 698 if (!$chats = $DB->get_records_select("chat", "chattime <= :timenow AND schedule > 0", $params)) { 699 return; 700 } 701 } 702 703 $courseids = []; 704 foreach ($chats as $chat) { 705 $originalchattime = $chat->chattime; 706 $chat->chattime = chat_calculate_next_chat_time($chat->schedule, $chat->chattime); 707 if ($originalchattime != $chat->chattime) { 708 $courseids[] = $chat->course; 709 } 710 $DB->update_record("chat", $chat); 711 $event = new stdClass(); // Update calendar too. 712 713 $cond = "modulename='chat' AND eventtype = :eventtype AND instance = :chatid AND timestart <> :chattime"; 714 $params = ['chattime' => $chat->chattime, 'eventtype' => CHAT_EVENT_TYPE_CHATTIME, 'chatid' => $chat->id]; 715 716 if ($event->id = $DB->get_field_select('event', 'id', $cond, $params)) { 717 $event->timestart = $chat->chattime; 718 $event->timesort = $chat->chattime; 719 $calendarevent = calendar_event::load($event->id); 720 $calendarevent->update($event, false); 721 } 722 } 723 724 $courseids = array_unique($courseids); 725 foreach ($courseids as $courseid) { 726 rebuild_course_cache($courseid, true); 727 } 728 } 729 730 /** 731 * Send a message on the chat. 732 * 733 * @param object $chatuser The chat user record. 734 * @param string $messagetext The message to be sent. 735 * @param bool $issystem False for non-system messages, true for system messages. 736 * @param object $cm The course module object, pass it to save a database query when we trigger the event. 737 * @return int The message ID. 738 * @since Moodle 2.6 739 */ 740 function chat_send_chatmessage($chatuser, $messagetext, $issystem = false, $cm = null) { 741 global $DB; 742 743 $message = new stdClass(); 744 $message->chatid = $chatuser->chatid; 745 $message->userid = $chatuser->userid; 746 $message->groupid = $chatuser->groupid; 747 $message->message = $messagetext; 748 $message->issystem = $issystem ? 1 : 0; 749 $message->timestamp = time(); 750 751 $messageid = $DB->insert_record('chat_messages', $message); 752 $DB->insert_record('chat_messages_current', $message); 753 $message->id = $messageid; 754 755 if (!$issystem) { 756 757 if (empty($cm)) { 758 $cm = get_coursemodule_from_instance('chat', $chatuser->chatid, $chatuser->course); 759 } 760 761 $params = array( 762 'context' => context_module::instance($cm->id), 763 'objectid' => $message->id, 764 // We set relateduserid, because when triggered from the chat daemon, the event userid is null. 765 'relateduserid' => $chatuser->userid 766 ); 767 $event = \mod_chat\event\message_sent::create($params); 768 $event->add_record_snapshot('chat_messages', $message); 769 $event->trigger(); 770 } 771 772 return $message->id; 773 } 774 775 /** 776 * @global object 777 * @global object 778 * @param object $message 779 * @param int $courseid 780 * @param object $sender 781 * @param object $currentuser 782 * @param string $chatlastrow 783 * @return bool|string Returns HTML or false 784 */ 785 function chat_format_message_manually($message, $courseid, $sender, $currentuser, $chatlastrow = null) { 786 global $CFG, $USER, $OUTPUT; 787 788 $output = new stdClass(); 789 $output->beep = false; // By default. 790 $output->refreshusers = false; // By default. 791 792 // Find the correct timezone for displaying this message. 793 $tz = core_date::get_user_timezone($currentuser); 794 795 $message->strtime = userdate($message->timestamp, get_string('strftimemessage', 'chat'), $tz); 796 797 $message->picture = $OUTPUT->user_picture($sender, array('size' => false, 'courseid' => $courseid, 'link' => false)); 798 799 if ($courseid) { 800 $message->picture = "<a onclick=\"window.open('$CFG->wwwroot/user/view.php?id=$sender->id&course=$courseid')\"". 801 " href=\"$CFG->wwwroot/user/view.php?id=$sender->id&course=$courseid\">$message->picture</a>"; 802 } 803 804 // Calculate the row class. 805 if ($chatlastrow !== null) { 806 $rowclass = ' class="r'.$chatlastrow.'" '; 807 } else { 808 $rowclass = ''; 809 } 810 811 // Start processing the message. 812 813 if (!empty($message->issystem)) { 814 // System event. 815 $output->text = $message->strtime.': '.get_string('message'.$message->message, 'chat', fullname($sender)); 816 $output->html = '<table class="chat-event"><tr'.$rowclass.'><td class="picture">'.$message->picture.'</td>'; 817 $output->html .= '<td class="text"><span class="event">'.$output->text.'</span></td></tr></table>'; 818 $output->basic = '<tr class="r1"> 819 <th scope="row" class="cell c1 title"></th> 820 <td class="cell c2 text">' . get_string('message'.$message->message, 'chat', fullname($sender)) . '</td> 821 <td class="cell c3">' . $message->strtime . '</td> 822 </tr>'; 823 if ($message->message == 'exit' or $message->message == 'enter') { 824 $output->refreshusers = true; // Force user panel refresh ASAP. 825 } 826 return $output; 827 } 828 829 // It's not a system event. 830 $rawtext = trim($message->message); 831 832 // Options for format_text, when we get to it... 833 // format_text call will parse the text to clean and filter it. 834 // It cannot be called here as HTML-isation interferes with special case 835 // recognition, but *must* be called on any user-sourced text to be inserted 836 // into $outmain. 837 $options = new stdClass(); 838 $options->para = false; 839 $options->blanktarget = true; 840 841 // And now check for special cases. 842 $patternto = '#^\s*To\s([^:]+):(.*)#'; 843 $special = false; 844 845 if (substr($rawtext, 0, 5) == 'beep ') { 846 // It's a beep! 847 $special = true; 848 $beepwho = trim(substr($rawtext, 5)); 849 850 if ($beepwho == 'all') { // Everyone. 851 $outinfobasic = get_string('messagebeepseveryone', 'chat', fullname($sender)); 852 $outinfo = $message->strtime . ': ' . $outinfobasic; 853 $outmain = ''; 854 855 $output->beep = true; // Eventually this should be set to a filename uploaded by the user. 856 857 } else if ($beepwho == $currentuser->id) { // Current user. 858 $outinfobasic = get_string('messagebeepsyou', 'chat', fullname($sender)); 859 $outinfo = $message->strtime . ': ' . $outinfobasic; 860 $outmain = ''; 861 $output->beep = true; 862 863 } else { // Something is not caught? 864 return false; 865 } 866 } else if (substr($rawtext, 0, 1) == '/') { // It's a user command. 867 $special = true; 868 $pattern = '#(^\/)(\w+).*#'; 869 preg_match($pattern, $rawtext, $matches); 870 $command = isset($matches[2]) ? $matches[2] : false; 871 // Support some IRC commands. 872 switch ($command) { 873 case 'me': 874 $outinfo = $message->strtime; 875 $text = '*** <b>'.$sender->firstname.' '.substr($rawtext, 4).'</b>'; 876 $outmain = format_text($text, FORMAT_MOODLE, $options, $courseid); 877 break; 878 default: 879 // Error, we set special back to false to use the classic message output. 880 $special = false; 881 break; 882 } 883 } else if (preg_match($patternto, $rawtext)) { 884 $special = true; 885 $matches = array(); 886 preg_match($patternto, $rawtext, $matches); 887 if (isset($matches[1]) && isset($matches[2])) { 888 $text = format_text($matches[2], FORMAT_MOODLE, $options, $courseid); 889 $outinfo = $message->strtime; 890 $outmain = $sender->firstname.' '.get_string('saidto', 'chat').' <i>'.$matches[1].'</i>: '.$text; 891 } else { 892 // Error, we set special back to false to use the classic message output. 893 $special = false; 894 } 895 } 896 897 if (!$special) { 898 $text = format_text($rawtext, FORMAT_MOODLE, $options, $courseid); 899 $outinfo = $message->strtime.' '.$sender->firstname; 900 $outmain = $text; 901 } 902 903 // Format the message as a small table. 904 905 $output->text = strip_tags($outinfo.': '.$outmain); 906 907 $output->html = "<table class=\"chat-message\"><tr$rowclass><td class=\"picture\" valign=\"top\">$message->picture</td>"; 908 $output->html .= "<td class=\"text\"><span class=\"title\">$outinfo</span>"; 909 if ($outmain) { 910 $output->html .= ": $outmain"; 911 $output->basic = '<tr class="r0"> 912 <th scope="row" class="cell c1 title">' . $sender->firstname . '</th> 913 <td class="cell c2 text">' . $outmain . '</td> 914 <td class="cell c3">' . $message->strtime . '</td> 915 </tr>'; 916 } else { 917 $output->basic = '<tr class="r1"> 918 <th scope="row" class="cell c1 title"></th> 919 <td class="cell c2 text">' . $outinfobasic . '</td> 920 <td class="cell c3">' . $message->strtime . '</td> 921 </tr>'; 922 } 923 $output->html .= "</td></tr></table>"; 924 return $output; 925 } 926 927 /** 928 * Given a message object this function formats it appropriately into text and html then returns the formatted data 929 * @global object 930 * @param object $message 931 * @param int $courseid 932 * @param object $currentuser 933 * @param string $chatlastrow 934 * @return bool|string Returns HTML or false 935 */ 936 function chat_format_message($message, $courseid, $currentuser, $chatlastrow=null) { 937 global $DB; 938 939 static $users; // Cache user lookups. 940 941 if (isset($users[$message->userid])) { 942 $user = $users[$message->userid]; 943 } else if ($user = $DB->get_record('user', ['id' => $message->userid], implode(',', \core_user\fields::get_picture_fields()))) { 944 $users[$message->userid] = $user; 945 } else { 946 return null; 947 } 948 return chat_format_message_manually($message, $courseid, $user, $currentuser, $chatlastrow); 949 } 950 951 /** 952 * @global object 953 * @param object $message message to be displayed. 954 * @param mixed $chatuser user chat data 955 * @param object $currentuser current user for whom the message should be displayed. 956 * @param int $groupingid course module grouping id 957 * @param string $theme name of the chat theme. 958 * @return bool|string Returns HTML or false 959 */ 960 function chat_format_message_theme ($message, $chatuser, $currentuser, $groupingid, $theme = 'bubble') { 961 global $CFG, $USER, $OUTPUT, $COURSE, $DB, $PAGE; 962 require_once($CFG->dirroot.'/mod/chat/locallib.php'); 963 964 static $users; // Cache user lookups. 965 966 $result = new stdClass(); 967 968 if (file_exists($CFG->dirroot . '/mod/chat/gui_ajax/theme/'.$theme.'/config.php')) { 969 include($CFG->dirroot . '/mod/chat/gui_ajax/theme/'.$theme.'/config.php'); 970 } 971 972 if (isset($users[$message->userid])) { 973 $sender = $users[$message->userid]; 974 } else if ($sender = $DB->get_record('user', array('id' => $message->userid), 975 implode(',', \core_user\fields::get_picture_fields()))) { 976 $users[$message->userid] = $sender; 977 } else { 978 return null; 979 } 980 981 // Find the correct timezone for displaying this message. 982 $tz = core_date::get_user_timezone($currentuser); 983 984 if (empty($chatuser->course)) { 985 $courseid = $COURSE->id; 986 } else { 987 $courseid = $chatuser->course; 988 } 989 990 $message->strtime = userdate($message->timestamp, get_string('strftimemessage', 'chat'), $tz); 991 $message->picture = $OUTPUT->user_picture($sender, array('courseid' => $courseid)); 992 993 $message->picture = "<a target='_blank'". 994 " href=\"$CFG->wwwroot/user/view.php?id=$sender->id&course=$courseid\">$message->picture</a>"; 995 996 // Start processing the message. 997 if (!empty($message->issystem)) { 998 $result->type = 'system'; 999 1000 $senderprofile = $CFG->wwwroot.'/user/view.php?id='.$sender->id.'&course='.$courseid; 1001 $event = get_string('message'.$message->message, 'chat', fullname($sender)); 1002 $eventmessage = new event_message($senderprofile, fullname($sender), $message->strtime, $event, $theme); 1003 1004 $output = $PAGE->get_renderer('mod_chat'); 1005 $result->html = $output->render($eventmessage); 1006 1007 return $result; 1008 } 1009 1010 // It's not a system event. 1011 $rawtext = trim($message->message); 1012 1013 // Options for format_text, when we get to it... 1014 // format_text call will parse the text to clean and filter it. 1015 // It cannot be called here as HTML-isation interferes with special case 1016 // recognition, but *must* be called on any user-sourced text to be inserted 1017 // into $outmain. 1018 $options = new stdClass(); 1019 $options->para = false; 1020 $options->blanktarget = true; 1021 1022 // And now check for special cases. 1023 $special = false; 1024 $outtime = $message->strtime; 1025 1026 // Initialise variables. 1027 $outmain = ''; 1028 $patternto = '#^\s*To\s([^:]+):(.*)#'; 1029 1030 if (substr($rawtext, 0, 5) == 'beep ') { 1031 $special = true; 1032 // It's a beep! 1033 $result->type = 'beep'; 1034 $beepwho = trim(substr($rawtext, 5)); 1035 1036 if ($beepwho == 'all') { // Everyone. 1037 $outmain = get_string('messagebeepseveryone', 'chat', fullname($sender)); 1038 } else if ($beepwho == $currentuser->id) { // Current user. 1039 $outmain = get_string('messagebeepsyou', 'chat', fullname($sender)); 1040 } else if ($sender->id == $currentuser->id) { // Something is not caught? 1041 // Allow beep for a active chat user only, else user can beep anyone and get fullname. 1042 if (!empty($chatuser) && is_numeric($beepwho)) { 1043 $chatusers = chat_get_users($chatuser->chatid, $chatuser->groupid, $groupingid); 1044 if (array_key_exists($beepwho, $chatusers)) { 1045 $outmain = get_string('messageyoubeep', 'chat', fullname($chatusers[$beepwho])); 1046 } else { 1047 $outmain = get_string('messageyoubeep', 'chat', $beepwho); 1048 } 1049 } else { 1050 $outmain = get_string('messageyoubeep', 'chat', $beepwho); 1051 } 1052 } 1053 } else if (substr($rawtext, 0, 1) == '/') { // It's a user command. 1054 $special = true; 1055 $result->type = 'command'; 1056 $pattern = '#(^\/)(\w+).*#'; 1057 preg_match($pattern, $rawtext, $matches); 1058 $command = isset($matches[2]) ? $matches[2] : false; 1059 // Support some IRC commands. 1060 switch ($command) { 1061 case 'me': 1062 $text = '*** <b>'.$sender->firstname.' '.substr($rawtext, 4).'</b>'; 1063 $outmain = format_text($text, FORMAT_MOODLE, $options, $courseid); 1064 break; 1065 default: 1066 // Error, we set special back to false to use the classic message output. 1067 $special = false; 1068 break; 1069 } 1070 } else if (preg_match($patternto, $rawtext)) { 1071 $special = true; 1072 $result->type = 'dialogue'; 1073 $matches = array(); 1074 preg_match($patternto, $rawtext, $matches); 1075 if (isset($matches[1]) && isset($matches[2])) { 1076 $text = format_text($matches[2], FORMAT_MOODLE, $options, $courseid); 1077 $outmain = $sender->firstname.' <b>'.get_string('saidto', 'chat').'</b> <i>'.$matches[1].'</i>: '.$text; 1078 } else { 1079 // Error, we set special back to false to use the classic message output. 1080 $special = false; 1081 } 1082 } 1083 1084 if (!$special) { 1085 $text = format_text($rawtext, FORMAT_MOODLE, $options, $courseid); 1086 $outmain = $text; 1087 } 1088 1089 $result->text = strip_tags($outtime.': '.$outmain); 1090 1091 $mymessageclass = ''; 1092 if ($sender->id == $USER->id) { 1093 $mymessageclass = 'chat-message-mymessage'; 1094 } 1095 1096 $senderprofile = $CFG->wwwroot.'/user/view.php?id='.$sender->id.'&course='.$courseid; 1097 $usermessage = new user_message($senderprofile, fullname($sender), $message->picture, 1098 $mymessageclass, $outtime, $outmain, $theme); 1099 1100 $output = $PAGE->get_renderer('mod_chat'); 1101 $result->html = $output->render($usermessage); 1102 1103 // When user beeps other user, then don't show any timestamp to other users in chat. 1104 if (('' === $outmain) && $special) { 1105 return false; 1106 } else { 1107 return $result; 1108 } 1109 } 1110 1111 /** 1112 * @global object $DB 1113 * @global object $CFG 1114 * @global object $COURSE 1115 * @global object $OUTPUT 1116 * @param object $users 1117 * @param object $course 1118 * @return array return formatted user list 1119 */ 1120 function chat_format_userlist($users, $course) { 1121 global $CFG, $DB, $COURSE, $OUTPUT; 1122 $result = array(); 1123 foreach ($users as $user) { 1124 $item = array(); 1125 $item['name'] = fullname($user); 1126 $item['url'] = $CFG->wwwroot.'/user/view.php?id='.$user->id.'&course='.$course->id; 1127 $item['picture'] = $OUTPUT->user_picture($user); 1128 $item['id'] = $user->id; 1129 $result[] = $item; 1130 } 1131 return $result; 1132 } 1133 1134 /** 1135 * Print json format error 1136 * @param string $level 1137 * @param string $msg 1138 */ 1139 function chat_print_error($level, $msg) { 1140 header('Content-Length: ' . ob_get_length() ); 1141 $error = new stdClass(); 1142 $error->level = $level; 1143 $error->msg = $msg; 1144 $response['error'] = $error; 1145 echo json_encode($response); 1146 ob_end_flush(); 1147 exit; 1148 } 1149 1150 /** 1151 * List the actions that correspond to a view of this module. 1152 * This is used by the participation report. 1153 * 1154 * Note: This is not used by new logging system. Event with 1155 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will 1156 * be considered as view action. 1157 * 1158 * @return array 1159 */ 1160 function chat_get_view_actions() { 1161 return array('view', 'view all', 'report'); 1162 } 1163 1164 /** 1165 * List the actions that correspond to a post of this module. 1166 * This is used by the participation report. 1167 * 1168 * Note: This is not used by new logging system. Event with 1169 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING 1170 * will be considered as post action. 1171 * 1172 * @return array 1173 */ 1174 function chat_get_post_actions() { 1175 return array('talk'); 1176 } 1177 1178 /** 1179 * @deprecated since Moodle 3.3, when the block_course_overview block was removed. 1180 */ 1181 function chat_print_overview() { 1182 throw new coding_exception('chat_print_overview() can not be used any more and is obsolete.'); 1183 } 1184 1185 1186 /** 1187 * Implementation of the function for printing the form elements that control 1188 * whether the course reset functionality affects the chat. 1189 * 1190 * @param object $mform form passed by reference 1191 */ 1192 function chat_reset_course_form_definition(&$mform) { 1193 $mform->addElement('header', 'chatheader', get_string('modulenameplural', 'chat')); 1194 $mform->addElement('advcheckbox', 'reset_chat', get_string('removemessages', 'chat')); 1195 } 1196 1197 /** 1198 * Course reset form defaults. 1199 * 1200 * @param object $course 1201 * @return array 1202 */ 1203 function chat_reset_course_form_defaults($course) { 1204 return array('reset_chat' => 1); 1205 } 1206 1207 /** 1208 * Actual implementation of the reset course functionality, delete all the 1209 * chat messages for course $data->courseid. 1210 * 1211 * @global object 1212 * @global object 1213 * @param object $data the data submitted from the reset course. 1214 * @return array status array 1215 */ 1216 function chat_reset_userdata($data) { 1217 global $CFG, $DB; 1218 1219 $componentstr = get_string('modulenameplural', 'chat'); 1220 $status = array(); 1221 1222 if (!empty($data->reset_chat)) { 1223 $chatessql = "SELECT ch.id 1224 FROM {chat} ch 1225 WHERE ch.course=?"; 1226 $params = array($data->courseid); 1227 1228 $DB->delete_records_select('chat_messages', "chatid IN ($chatessql)", $params); 1229 $DB->delete_records_select('chat_messages_current', "chatid IN ($chatessql)", $params); 1230 $DB->delete_records_select('chat_users', "chatid IN ($chatessql)", $params); 1231 $status[] = array('component' => $componentstr, 'item' => get_string('removemessages', 'chat'), 'error' => false); 1232 } 1233 1234 // Updating dates - shift may be negative too. 1235 if ($data->timeshift) { 1236 // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. 1237 // See MDL-9367. 1238 shift_course_mod_dates('chat', array('chattime'), $data->timeshift, $data->courseid); 1239 $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false); 1240 } 1241 1242 return $status; 1243 } 1244 1245 /** 1246 * @param string $feature FEATURE_xx constant for requested feature 1247 * @return mixed True if module supports feature, null if doesn't know 1248 */ 1249 function chat_supports($feature) { 1250 switch($feature) { 1251 case FEATURE_GROUPS: 1252 return true; 1253 case FEATURE_GROUPINGS: 1254 return true; 1255 case FEATURE_MOD_INTRO: 1256 return true; 1257 case FEATURE_BACKUP_MOODLE2: 1258 return true; 1259 case FEATURE_COMPLETION_TRACKS_VIEWS: 1260 return true; 1261 case FEATURE_GRADE_HAS_GRADE: 1262 return false; 1263 case FEATURE_GRADE_OUTCOMES: 1264 return true; 1265 case FEATURE_SHOW_DESCRIPTION: 1266 return true; 1267 default: 1268 return null; 1269 } 1270 } 1271 1272 function chat_extend_navigation($navigation, $course, $module, $cm) { 1273 global $CFG; 1274 1275 $currentgroup = groups_get_activity_group($cm, true); 1276 1277 if (has_capability('mod/chat:chat', context_module::instance($cm->id))) { 1278 $strenterchat = get_string('enterchat', 'chat'); 1279 1280 $target = $CFG->wwwroot.'/mod/chat/'; 1281 $params = array('id' => $cm->instance); 1282 1283 if ($currentgroup) { 1284 $params['groupid'] = $currentgroup; 1285 } 1286 1287 $links = array(); 1288 1289 $url = new moodle_url($target.'gui_'.$CFG->chat_method.'/index.php', $params); 1290 $action = new popup_action('click', $url, 'chat'.$course->id.$cm->instance.$currentgroup, 1291 array('height' => 500, 'width' => 700)); 1292 $links[] = new action_link($url, $strenterchat, $action); 1293 1294 $url = new moodle_url($target.'gui_basic/index.php', $params); 1295 $action = new popup_action('click', $url, 'chat'.$course->id.$cm->instance.$currentgroup, 1296 array('height' => 500, 'width' => 700)); 1297 $links[] = new action_link($url, get_string('noframesjs', 'message'), $action); 1298 1299 foreach ($links as $link) { 1300 $navigation->add($link->text, $link, navigation_node::TYPE_SETTING, null , null, new pix_icon('i/group' , '')); 1301 } 1302 } 1303 1304 $chatusers = chat_get_users($cm->instance, $currentgroup, $cm->groupingid); 1305 if (is_array($chatusers) && count($chatusers) > 0) { 1306 $users = $navigation->add(get_string('currentusers', 'chat')); 1307 foreach ($chatusers as $chatuser) { 1308 $userlink = new moodle_url('/user/view.php', array('id' => $chatuser->id, 'course' => $course->id)); 1309 $users->add(fullname($chatuser).' '.format_time(time() - $chatuser->lastmessageping), 1310 $userlink, navigation_node::TYPE_USER, null, null, new pix_icon('i/user', '')); 1311 } 1312 } 1313 } 1314 1315 /** 1316 * Adds module specific settings to the settings block 1317 * 1318 * @param settings_navigation $settings The settings navigation object 1319 * @param navigation_node $chatnode The node to add module settings to 1320 */ 1321 function chat_extend_settings_navigation(settings_navigation $settings, navigation_node $chatnode) { 1322 global $DB, $PAGE, $USER; 1323 $chat = $DB->get_record("chat", array("id" => $PAGE->cm->instance)); 1324 1325 if ($chat->chattime && $chat->schedule) { 1326 $nextsessionnode = $chatnode->add(get_string('nextsession', 'chat'). 1327 ': '.userdate($chat->chattime). 1328 ' ('.usertimezone($USER->timezone).')'); 1329 $nextsessionnode->add_class('note'); 1330 } 1331 1332 $currentgroup = groups_get_activity_group($PAGE->cm, true); 1333 if ($currentgroup) { 1334 $groupselect = " AND groupid = '$currentgroup'"; 1335 } else { 1336 $groupselect = ''; 1337 } 1338 1339 if ($chat->studentlogs || has_capability('mod/chat:readlog', $PAGE->cm->context)) { 1340 if ($DB->get_records_select('chat_messages', "chatid = ? $groupselect", array($chat->id))) { 1341 $chatnode->add(get_string('viewreport', 'chat'), new moodle_url('/mod/chat/report.php', array('id' => $PAGE->cm->id))); 1342 } 1343 } 1344 } 1345 1346 /** 1347 * user logout event handler 1348 * 1349 * @param \core\event\user_loggedout $event The event. 1350 * @return void 1351 */ 1352 function chat_user_logout(\core\event\user_loggedout $event) { 1353 global $DB; 1354 $DB->delete_records('chat_users', array('userid' => $event->objectid)); 1355 } 1356 1357 /** 1358 * Return a list of page types 1359 * @param string $pagetype current page type 1360 * @param stdClass $parentcontext Block's parent context 1361 * @param stdClass $currentcontext Current context of block 1362 */ 1363 function chat_page_type_list($pagetype, $parentcontext, $currentcontext) { 1364 $modulepagetype = array('mod-chat-*' => get_string('page-mod-chat-x', 'chat')); 1365 return $modulepagetype; 1366 } 1367 1368 /** 1369 * Return a list of the latest messages in the given chat session. 1370 * 1371 * @param stdClass $chatuser chat user session data 1372 * @param int $chatlasttime last time messages were retrieved 1373 * @return array list of messages 1374 * @since Moodle 3.0 1375 */ 1376 function chat_get_latest_messages($chatuser, $chatlasttime) { 1377 global $DB; 1378 1379 $params = array('groupid' => $chatuser->groupid, 'chatid' => $chatuser->chatid, 'lasttime' => $chatlasttime); 1380 1381 $groupselect = $chatuser->groupid ? " AND (groupid=" . $chatuser->groupid . " OR groupid=0) " : ""; 1382 1383 return $DB->get_records_select('chat_messages_current', 'chatid = :chatid AND timestamp > :lasttime ' . $groupselect, 1384 $params, 'timestamp ASC'); 1385 } 1386 1387 /** 1388 * Mark the activity completed (if required) and trigger the course_module_viewed event. 1389 * 1390 * @param stdClass $chat chat object 1391 * @param stdClass $course course object 1392 * @param stdClass $cm course module object 1393 * @param stdClass $context context object 1394 * @since Moodle 3.0 1395 */ 1396 function chat_view($chat, $course, $cm, $context) { 1397 1398 // Trigger course_module_viewed event. 1399 $params = array( 1400 'context' => $context, 1401 'objectid' => $chat->id 1402 ); 1403 1404 $event = \mod_chat\event\course_module_viewed::create($params); 1405 $event->add_record_snapshot('course_modules', $cm); 1406 $event->add_record_snapshot('course', $course); 1407 $event->add_record_snapshot('chat', $chat); 1408 $event->trigger(); 1409 1410 // Completion. 1411 $completion = new completion_info($course); 1412 $completion->set_module_viewed($cm); 1413 } 1414 1415 /** 1416 * This function receives a calendar event and returns the action associated with it, or null if there is none. 1417 * 1418 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event 1419 * is not displayed on the block. 1420 * 1421 * @param calendar_event $event 1422 * @param \core_calendar\action_factory $factory 1423 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). 1424 * @return \core_calendar\local\event\entities\action_interface|null 1425 */ 1426 function mod_chat_core_calendar_provide_event_action(calendar_event $event, 1427 \core_calendar\action_factory $factory, 1428 int $userid = 0) { 1429 global $USER, $DB; 1430 1431 if ($userid) { 1432 $user = core_user::get_user($userid, 'id, timezone'); 1433 } else { 1434 $user = $USER; 1435 } 1436 1437 $cm = get_fast_modinfo($event->courseid, $user->id)->instances['chat'][$event->instance]; 1438 1439 if (!$cm->uservisible) { 1440 // The module is not visible to the user for any reason. 1441 return null; 1442 } 1443 1444 $completion = new \completion_info($cm->get_course()); 1445 1446 $completiondata = $completion->get_data($cm, false, $userid); 1447 1448 if ($completiondata->completionstate != COMPLETION_INCOMPLETE) { 1449 return null; 1450 } 1451 1452 $chattime = $DB->get_field('chat', 'chattime', array('id' => $event->instance)); 1453 $usertimezone = core_date::get_user_timezone($user); 1454 $chattimemidnight = usergetmidnight($chattime, $usertimezone); 1455 $todaymidnight = usergetmidnight(time(), $usertimezone); 1456 1457 if ($chattime < $todaymidnight) { 1458 // The chat is before today. Do not show at all. 1459 return null; 1460 } else { 1461 // The chat is actionable if it is at some point today. 1462 $actionable = $chattimemidnight == $todaymidnight; 1463 1464 return $factory->create_instance( 1465 get_string('enterchat', 'chat'), 1466 new \moodle_url('/mod/chat/view.php', array('id' => $cm->id)), 1467 1, 1468 $actionable 1469 ); 1470 } 1471 } 1472 1473 /** 1474 * Given a set of messages for a chat, return the completed chat sessions (including optionally not completed ones). 1475 * 1476 * @param array $messages list of messages from a chat. It is assumed that these are sorted by timestamp in DESCENDING order. 1477 * @param bool $showall whether to include incomplete sessions or not 1478 * @return array the list of sessions 1479 * @since Moodle 3.5 1480 */ 1481 function chat_get_sessions($messages, $showall = false) { 1482 $sessions = []; 1483 $start = 0; 1484 $end = 0; 1485 $sessiontimes = []; 1486 1487 // Group messages by session times. 1488 foreach ($messages as $message) { 1489 // Initialise values start-end times if necessary. 1490 if (empty($start)) { 1491 $start = $message->timestamp; 1492 } 1493 if (empty($end)) { 1494 $end = $message->timestamp; 1495 } 1496 1497 // If this message's timestamp has been more than the gap, it means it's been idle. 1498 if ($start - $message->timestamp > CHAT_SESSION_GAP) { 1499 // Mark this as the session end of the next session. 1500 $end = $message->timestamp; 1501 } 1502 // Use this time as the session's start (until it gets overwritten on the next iteration, if needed). 1503 $start = $message->timestamp; 1504 1505 // Set this start-end pair in our list of session times. 1506 $sessiontimes[$end]['sessionstart'] = $start; 1507 if (!isset($sessiontimes[$end]['sessionend'])) { 1508 $sessiontimes[$end]['sessionend'] = $end; 1509 } 1510 if ($message->userid && !$message->issystem) { 1511 if (!isset($sessiontimes[$end]['sessionusers'][$message->userid])) { 1512 $sessiontimes[$end]['sessionusers'][$message->userid] = 1; 1513 } else { 1514 $sessiontimes[$end]['sessionusers'][$message->userid]++; 1515 } 1516 } 1517 } 1518 1519 // Go through each session time and prepare the session data to be returned. 1520 foreach ($sessiontimes as $sessionend => $sessiondata) { 1521 if (!isset($sessiondata['sessionusers'])) { 1522 $sessiondata['sessionusers'] = []; 1523 } 1524 $sessionusers = $sessiondata['sessionusers']; 1525 $sessionstart = $sessiondata['sessionstart']; 1526 1527 $iscomplete = $sessionend - $sessionstart > 60 && count($sessionusers) > 1; 1528 if ($showall || $iscomplete) { 1529 $sessions[] = (object) ($sessiondata + ['iscomplete' => $iscomplete]); 1530 } 1531 } 1532 1533 return $sessions; 1534 } 1535 1536 /** 1537 * Return the messages of the given chat session. 1538 * 1539 * @param int $chatid the chat id 1540 * @param mixed $group false if groups not used, int if groups used, 0 means all groups 1541 * @param int $start the session start timestamp (0 to not filter by time) 1542 * @param int $end the session end timestamp (0 to not filter by time) 1543 * @param string $sort an order to sort the results in (optional, a valid SQL ORDER BY parameter) 1544 * @return array session messages 1545 * @since Moodle 3.5 1546 */ 1547 function chat_get_session_messages($chatid, $group = false, $start = 0, $end = 0, $sort = '') { 1548 global $DB; 1549 1550 $params = array('chatid' => $chatid); 1551 1552 // If the user is allocated to a group, only show messages from people in the same group, or no group. 1553 if ($group) { 1554 $groupselect = " AND (groupid = :currentgroup OR groupid = 0)"; 1555 $params['currentgroup'] = $group; 1556 } else { 1557 $groupselect = ""; 1558 } 1559 1560 $select = "chatid = :chatid $groupselect"; 1561 if (!empty($start)) { 1562 $select .= ' AND timestamp >= :start'; 1563 $params['start'] = $start; 1564 } 1565 if (!empty($end)) { 1566 $select .= ' AND timestamp <= :end'; 1567 $params['end'] = $end; 1568 } 1569 1570 return $DB->get_records_select('chat_messages', $select, $params, $sort); 1571 } 1572 1573 /** 1574 * Add a get_coursemodule_info function in case chat instance wants to add 'extra' information 1575 * for the course (see resource). 1576 * 1577 * Given a course_module object, this function returns any "extra" information that may be needed 1578 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php. 1579 * 1580 * @param stdClass $coursemodule The coursemodule object (record). 1581 * @return cached_cm_info An object on information that the courses 1582 * will know about (most noticeably, an icon). 1583 */ 1584 function chat_get_coursemodule_info($coursemodule) { 1585 global $DB; 1586 1587 $dbparams = ['id' => $coursemodule->instance]; 1588 $fields = 'id, name, intro, introformat, chattime, schedule'; 1589 if (!$chat = $DB->get_record('chat', $dbparams, $fields)) { 1590 return false; 1591 } 1592 1593 $result = new cached_cm_info(); 1594 $result->name = $chat->name; 1595 if ($coursemodule->showdescription) { 1596 // Convert intro to html. Do not filter cached version, filters run at display time. 1597 $result->content = format_module_intro('chat', $chat, $coursemodule->id, false); 1598 } 1599 1600 // Populate some other values that can be used in calendar or on dashboard. 1601 if ($chat->chattime) { 1602 $result->customdata['chattime'] = $chat->chattime; 1603 $result->customdata['schedule'] = $chat->schedule; 1604 } 1605 1606 return $result; 1607 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body