Differences Between: [Versions 310 and 400] [Versions 39 and 400] [Versions 400 and 402] [Versions 400 and 403]
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 * This file contains functions used by the log reports 19 * 20 * This files lists the functions that are used during the log report generation. 21 * 22 * @package report_log 23 * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 defined('MOODLE_INTERNAL') || die; 28 29 if (!defined('REPORT_LOG_MAX_DISPLAY')) { 30 define('REPORT_LOG_MAX_DISPLAY', 150); // days 31 } 32 33 require_once (__DIR__.'/lib.php'); 34 35 /** 36 * This function is used to generate and display the log activity graph 37 * 38 * @global stdClass $CFG 39 * @param stdClass $course course instance 40 * @param int|stdClass $user id/object of the user whose logs are needed 41 * @param string $typeormode type of logs graph needed (usercourse.png/userday.png) or the mode (today, all). 42 * @param int $date timestamp in GMT (seconds since epoch) 43 * @param string $logreader Log reader. 44 * @return void 45 */ 46 function report_log_print_graph($course, $user, $typeormode, $date=0, $logreader='') { 47 global $CFG, $OUTPUT; 48 49 if (!is_object($user)) { 50 $user = core_user::get_user($user); 51 } 52 53 $logmanager = get_log_manager(); 54 $readers = $logmanager->get_readers(); 55 56 if (empty($logreader)) { 57 $reader = reset($readers); 58 } else { 59 $reader = $readers[$logreader]; 60 } 61 // If reader is not a sql_internal_table_reader and not legacy store then don't show graph. 62 if (!($reader instanceof \core\log\sql_internal_table_reader) && !($reader instanceof logstore_legacy\log\store)) { 63 return array(); 64 } 65 $coursecontext = context_course::instance($course->id); 66 67 $a = new stdClass(); 68 $a->coursename = format_string($course->shortname, true, array('context' => $coursecontext)); 69 $a->username = fullname($user, true); 70 71 if ($typeormode == 'today' || $typeormode == 'userday.png') { 72 $logs = report_log_usertoday_data($course, $user, $date, $logreader); 73 $title = get_string("hitsoncoursetoday", "", $a); 74 } else if ($typeormode == 'all' || $typeormode == 'usercourse.png') { 75 $logs = report_log_userall_data($course, $user, $logreader); 76 $title = get_string("hitsoncourse", "", $a); 77 } 78 79 if (!empty($CFG->preferlinegraphs)) { 80 $chart = new \core\chart_line(); 81 } else { 82 $chart = new \core\chart_bar(); 83 } 84 85 $series = new \core\chart_series(get_string("hits"), $logs['series']); 86 $chart->add_series($series); 87 $chart->set_title($title); 88 $chart->set_labels($logs['labels']); 89 $yaxis = $chart->get_yaxis(0, true); 90 $yaxis->set_label(get_string("hits")); 91 $yaxis->set_stepsize(max(1, round(max($logs['series']) / 10))); 92 93 echo $OUTPUT->render($chart); 94 } 95 96 /** 97 * Select all log records for a given course and user 98 * 99 * @param int $userid The id of the user as found in the 'user' table. 100 * @param int $courseid The id of the course as found in the 'course' table. 101 * @param string $coursestart unix timestamp representing course start date and time. 102 * @param string $logreader log reader to use. 103 * @return array 104 */ 105 function report_log_usercourse($userid, $courseid, $coursestart, $logreader = '') { 106 global $DB; 107 108 $logmanager = get_log_manager(); 109 $readers = $logmanager->get_readers(); 110 if (empty($logreader)) { 111 $reader = reset($readers); 112 } else { 113 $reader = $readers[$logreader]; 114 } 115 116 // If reader is not a sql_internal_table_reader and not legacy store then return. 117 if (!($reader instanceof \core\log\sql_internal_table_reader) && !($reader instanceof logstore_legacy\log\store)) { 118 return array(); 119 } 120 121 $coursestart = (int)$coursestart; // Note: unfortunately pg complains if you use name parameter or column alias in GROUP BY. 122 if ($reader instanceof logstore_legacy\log\store) { 123 $logtable = 'log'; 124 $timefield = 'time'; 125 $coursefield = 'course'; 126 // Anonymous actions are never logged in legacy log. 127 $nonanonymous = ''; 128 } else { 129 $logtable = $reader->get_internal_log_table_name(); 130 $timefield = 'timecreated'; 131 $coursefield = 'courseid'; 132 $nonanonymous = 'AND anonymous = 0'; 133 } 134 135 $params = array(); 136 $courseselect = ''; 137 if ($courseid) { 138 $courseselect = "AND $coursefield = :courseid"; 139 $params['courseid'] = $courseid; 140 } 141 $params['userid'] = $userid; 142 return $DB->get_records_sql("SELECT FLOOR(($timefield - $coursestart)/" . DAYSECS . ") AS day, COUNT(*) AS num 143 FROM {" . $logtable . "} 144 WHERE userid = :userid 145 AND $timefield > $coursestart $courseselect $nonanonymous 146 GROUP BY FLOOR(($timefield - $coursestart)/" . DAYSECS .")", $params); 147 } 148 149 /** 150 * Select all log records for a given course, user, and day 151 * 152 * @param int $userid The id of the user as found in the 'user' table. 153 * @param int $courseid The id of the course as found in the 'course' table. 154 * @param string $daystart unix timestamp of the start of the day for which the logs needs to be retrived 155 * @param string $logreader log reader to use. 156 * @return array 157 */ 158 function report_log_userday($userid, $courseid, $daystart, $logreader = '') { 159 global $DB; 160 $logmanager = get_log_manager(); 161 $readers = $logmanager->get_readers(); 162 if (empty($logreader)) { 163 $reader = reset($readers); 164 } else { 165 $reader = $readers[$logreader]; 166 } 167 168 // If reader is not a sql_internal_table_reader and not legacy store then return. 169 if (!($reader instanceof \core\log\sql_internal_table_reader) && !($reader instanceof logstore_legacy\log\store)) { 170 return array(); 171 } 172 173 $daystart = (int)$daystart; // Note: unfortunately pg complains if you use name parameter or column alias in GROUP BY. 174 175 if ($reader instanceof logstore_legacy\log\store) { 176 $logtable = 'log'; 177 $timefield = 'time'; 178 $coursefield = 'course'; 179 // Anonymous actions are never logged in legacy log. 180 $nonanonymous = ''; 181 } else { 182 $logtable = $reader->get_internal_log_table_name(); 183 $timefield = 'timecreated'; 184 $coursefield = 'courseid'; 185 $nonanonymous = 'AND anonymous = 0'; 186 } 187 $params = array('userid' => $userid); 188 189 $courseselect = ''; 190 if ($courseid) { 191 $courseselect = "AND $coursefield = :courseid"; 192 $params['courseid'] = $courseid; 193 } 194 return $DB->get_records_sql("SELECT FLOOR(($timefield - $daystart)/" . HOURSECS . ") AS hour, COUNT(*) AS num 195 FROM {" . $logtable . "} 196 WHERE userid = :userid 197 AND $timefield > $daystart $courseselect $nonanonymous 198 GROUP BY FLOOR(($timefield - $daystart)/" . HOURSECS . ") ", $params); 199 } 200 201 /** 202 * This function is used to generate and display Mnet selector form 203 * 204 * @global stdClass $USER 205 * @global stdClass $CFG 206 * @global stdClass $SITE 207 * @global moodle_database $DB 208 * @global core_renderer $OUTPUT 209 * @global stdClass $SESSION 210 * @uses CONTEXT_SYSTEM 211 * @uses COURSE_MAX_COURSES_PER_DROPDOWN 212 * @uses CONTEXT_COURSE 213 * @uses SEPARATEGROUPS 214 * @param int $hostid host id 215 * @param stdClass $course course instance 216 * @param int $selecteduser id of the selected user 217 * @param string $selecteddate Date selected 218 * @param string $modname course_module->id 219 * @param string $modid number or 'site_errors' 220 * @param string $modaction an action as recorded in the logs 221 * @param int $selectedgroup Group to display 222 * @param int $showcourses whether to show courses if we're over our limit. 223 * @param int $showusers whether to show users if we're over our limit. 224 * @param string $logformat Format of the logs (downloadascsv, showashtml, downloadasods, downloadasexcel) 225 * @return void 226 */ 227 function report_log_print_mnet_selector_form($hostid, $course, $selecteduser=0, $selecteddate='today', 228 $modname="", $modid=0, $modaction='', $selectedgroup=-1, $showcourses=0, $showusers=0, $logformat='showashtml') { 229 230 global $USER, $CFG, $SITE, $DB, $OUTPUT, $SESSION; 231 require_once $CFG->dirroot.'/mnet/peer.php'; 232 233 $mnet_peer = new mnet_peer(); 234 $mnet_peer->set_id($hostid); 235 236 $sql = "SELECT DISTINCT course, hostid, coursename FROM {mnet_log}"; 237 $courses = $DB->get_records_sql($sql); 238 $remotecoursecount = count($courses); 239 240 // first check to see if we can override showcourses and showusers 241 $numcourses = $remotecoursecount + $DB->count_records('course'); 242 if ($numcourses < COURSE_MAX_COURSES_PER_DROPDOWN && !$showcourses) { 243 $showcourses = 1; 244 } 245 246 $sitecontext = context_system::instance(); 247 248 // Context for remote data is always SITE 249 // Groups for remote data are always OFF 250 if ($hostid == $CFG->mnet_localhost_id) { 251 $context = context_course::instance($course->id); 252 253 /// Setup for group handling. 254 if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { 255 $selectedgroup = -1; 256 $showgroups = false; 257 } else if ($course->groupmode) { 258 $showgroups = true; 259 } else { 260 $selectedgroup = 0; 261 $showgroups = false; 262 } 263 264 if ($selectedgroup === -1) { 265 if (isset($SESSION->currentgroup[$course->id])) { 266 $selectedgroup = $SESSION->currentgroup[$course->id]; 267 } else { 268 $selectedgroup = groups_get_all_groups($course->id, $USER->id); 269 if (is_array($selectedgroup)) { 270 $selectedgroup = array_shift(array_keys($selectedgroup)); 271 $SESSION->currentgroup[$course->id] = $selectedgroup; 272 } else { 273 $selectedgroup = 0; 274 } 275 } 276 } 277 278 } else { 279 $context = $sitecontext; 280 } 281 282 // Get all the possible users 283 $users = array(); 284 285 // Define limitfrom and limitnum for queries below 286 // If $showusers is enabled... don't apply limitfrom and limitnum 287 $limitfrom = empty($showusers) ? 0 : ''; 288 $limitnum = empty($showusers) ? COURSE_MAX_USERS_PER_DROPDOWN + 1 : ''; 289 290 // If looking at a different host, we're interested in all our site users 291 if ($hostid == $CFG->mnet_localhost_id && $course->id != SITEID) { 292 $userfieldsapi = \core_user\fields::for_name(); 293 $courseusers = get_enrolled_users($context, '', $selectedgroup, 'u.id, ' . 294 $userfieldsapi->get_sql('u', false, '', '', false)->selects, 295 null, $limitfrom, $limitnum); 296 } else { 297 // this may be a lot of users :-( 298 $userfieldsapi = \core_user\fields::for_name(); 299 $courseusers = $DB->get_records('user', array('deleted' => 0), 'lastaccess DESC', 'id, ' . 300 $userfieldsapi->get_sql('', false, '', '', false)->selects, 301 $limitfrom, $limitnum); 302 } 303 304 if (count($courseusers) < COURSE_MAX_USERS_PER_DROPDOWN && !$showusers) { 305 $showusers = 1; 306 } 307 308 if ($showusers) { 309 if ($courseusers) { 310 foreach ($courseusers as $courseuser) { 311 $users[$courseuser->id] = fullname($courseuser, has_capability('moodle/site:viewfullnames', $context)); 312 } 313 } 314 $users[$CFG->siteguest] = get_string('guestuser'); 315 } 316 317 // Get all the hosts that have log records 318 $sql = "select distinct 319 h.id, 320 h.name 321 from 322 {mnet_host} h, 323 {mnet_log} l 324 where 325 h.id = l.hostid 326 order by 327 h.name"; 328 329 if ($hosts = $DB->get_records_sql($sql)) { 330 foreach($hosts as $host) { 331 $hostarray[$host->id] = $host->name; 332 } 333 } 334 335 $hostarray[$CFG->mnet_localhost_id] = $SITE->fullname; 336 asort($hostarray); 337 338 $dropdown = array(); 339 340 foreach($hostarray as $hostid => $name) { 341 $courses = array(); 342 $sites = array(); 343 if ($CFG->mnet_localhost_id == $hostid) { 344 if (has_capability('report/log:view', $sitecontext) && $showcourses) { 345 if ($ccc = $DB->get_records("course", null, "fullname","id,shortname,fullname,category")) { 346 foreach ($ccc as $cc) { 347 if ($cc->id == SITEID) { 348 $sites["$hostid/$cc->id"] = format_string($cc->fullname).' ('.get_string('site').')'; 349 } else { 350 $courses["$hostid/$cc->id"] = format_string(get_course_display_name_for_list($cc)); 351 } 352 } 353 } 354 } 355 } else { 356 if (has_capability('report/log:view', $sitecontext) && $showcourses) { 357 $sql = "SELECT DISTINCT course, coursename FROM {mnet_log} where hostid = ?"; 358 if ($ccc = $DB->get_records_sql($sql, array($hostid))) { 359 foreach ($ccc as $cc) { 360 if (1 == $cc->course) { // TODO: this might be wrong - site course may have another id 361 $sites["$hostid/$cc->course"] = $cc->coursename.' ('.get_string('site').')'; 362 } else { 363 $courses["$hostid/$cc->course"] = $cc->coursename; 364 } 365 } 366 } 367 } 368 } 369 370 asort($courses); 371 $dropdown[] = array($name=>($sites + $courses)); 372 } 373 374 375 $activities = array(); 376 $selectedactivity = ""; 377 378 $modinfo = get_fast_modinfo($course); 379 if (!empty($modinfo->cms)) { 380 $section = 0; 381 $thissection = array(); 382 foreach ($modinfo->cms as $cm) { 383 // Exclude activities that aren't visible or have no view link (e.g. label). Account for folder being displayed inline. 384 if (!$cm->uservisible || (!$cm->has_view() && strcmp($cm->modname, 'folder') !== 0)) { 385 continue; 386 } 387 if ($cm->sectionnum > 0 and $section <> $cm->sectionnum) { 388 $activities[] = $thissection; 389 $thissection = array(); 390 } 391 $section = $cm->sectionnum; 392 $modname = strip_tags($cm->get_formatted_name()); 393 if (core_text::strlen($modname) > 55) { 394 $modname = core_text::substr($modname, 0, 50)."..."; 395 } 396 if (!$cm->visible) { 397 $modname = "(".$modname.")"; 398 } 399 $key = get_section_name($course, $cm->sectionnum); 400 if (!isset($thissection[$key])) { 401 $thissection[$key] = array(); 402 } 403 $thissection[$key][$cm->id] = $modname; 404 405 if ($cm->id == $modid) { 406 $selectedactivity = "$cm->id"; 407 } 408 } 409 if (!empty($thissection)) { 410 $activities[] = $thissection; 411 } 412 } 413 414 if (has_capability('report/log:view', $sitecontext) && !$course->category) { 415 $activities["site_errors"] = get_string("siteerrors"); 416 if ($modid === "site_errors") { 417 $selectedactivity = "site_errors"; 418 } 419 } 420 421 $strftimedate = get_string("strftimedate"); 422 $strftimedaydate = get_string("strftimedaydate"); 423 424 asort($users); 425 426 // Prepare the list of action options. 427 $actions = array( 428 'view' => get_string('view'), 429 'add' => get_string('add'), 430 'update' => get_string('update'), 431 'delete' => get_string('delete'), 432 '-view' => get_string('allchanges') 433 ); 434 435 // Get all the possible dates 436 // Note that we are keeping track of real (GMT) time and user time 437 // User time is only used in displays - all calcs and passing is GMT 438 439 $timenow = time(); // GMT 440 441 // What day is it now for the user, and when is midnight that day (in GMT). 442 $timemidnight = $today = usergetmidnight($timenow); 443 444 // Put today up the top of the list 445 $dates = array( 446 "0" => get_string('alldays'), 447 "$timemidnight" => get_string("today").", ".userdate($timenow, $strftimedate) 448 ); 449 450 if (!$course->startdate or ($course->startdate > $timenow)) { 451 $course->startdate = $course->timecreated; 452 } 453 454 $numdates = 1; 455 while ($timemidnight > $course->startdate and $numdates < 365) { 456 $timemidnight = $timemidnight - 86400; 457 $timenow = $timenow - 86400; 458 $dates["$timemidnight"] = userdate($timenow, $strftimedaydate); 459 $numdates++; 460 } 461 462 if ($selecteddate === "today") { 463 $selecteddate = $today; 464 } 465 466 echo "<form class=\"logselectform\" action=\"$CFG->wwwroot/report/log/index.php\" method=\"get\">\n"; 467 echo "<div>\n";//invisible fieldset here breaks wrapping 468 echo "<input type=\"hidden\" name=\"chooselog\" value=\"1\" />\n"; 469 echo "<input type=\"hidden\" name=\"showusers\" value=\"$showusers\" />\n"; 470 echo "<input type=\"hidden\" name=\"showcourses\" value=\"$showcourses\" />\n"; 471 if (has_capability('report/log:view', $sitecontext) && $showcourses) { 472 $cid = empty($course->id)? '1' : $course->id; 473 echo html_writer::label(get_string('selectacoursesite'), 'menuhost_course', false, array('class' => 'accesshide')); 474 echo html_writer::select($dropdown, "host_course", $hostid.'/'.$cid); 475 } else { 476 $courses = array(); 477 $courses[$course->id] = get_course_display_name_for_list($course) . ((empty($course->category)) ? ' ('.get_string('site').') ' : ''); 478 echo html_writer::label(get_string('selectacourse'), 'menuid', false, array('class' => 'accesshide')); 479 echo html_writer::select($courses,"id",$course->id, false); 480 if (has_capability('report/log:view', $sitecontext)) { 481 $a = new stdClass(); 482 $a->url = "$CFG->wwwroot/report/log/index.php?chooselog=0&group=$selectedgroup&user=$selecteduser" 483 ."&id=$course->id&date=$selecteddate&modid=$selectedactivity&showcourses=1&showusers=$showusers"; 484 print_string('logtoomanycourses','moodle',$a); 485 } 486 } 487 488 if ($showgroups) { 489 if ($cgroups = groups_get_all_groups($course->id)) { 490 foreach ($cgroups as $cgroup) { 491 $groups[$cgroup->id] = $cgroup->name; 492 } 493 } 494 else { 495 $groups = array(); 496 } 497 echo html_writer::label(get_string('selectagroup'), 'menugroup', false, array('class' => 'accesshide')); 498 echo html_writer::select($groups, "group", $selectedgroup, get_string("allgroups")); 499 } 500 501 if ($showusers) { 502 echo html_writer::label(get_string('participantslist'), 'menuuser', false, array('class' => 'accesshide')); 503 echo html_writer::select($users, "user", $selecteduser, get_string("allparticipants")); 504 } 505 else { 506 $users = array(); 507 if (!empty($selecteduser)) { 508 $user = $DB->get_record('user', array('id'=>$selecteduser)); 509 $users[$selecteduser] = fullname($user); 510 } 511 else { 512 $users[0] = get_string('allparticipants'); 513 } 514 echo html_writer::label(get_string('participantslist'), 'menuuser', false, array('class' => 'accesshide')); 515 echo html_writer::select($users, "user", $selecteduser, false); 516 $a = new stdClass(); 517 $a->url = "$CFG->wwwroot/report/log/index.php?chooselog=0&group=$selectedgroup&user=$selecteduser" 518 ."&id=$course->id&date=$selecteddate&modid=$selectedactivity&showusers=1&showcourses=$showcourses"; 519 print_string('logtoomanyusers','moodle',$a); 520 } 521 522 echo html_writer::label(get_string('date'), 'menudate', false, array('class' => 'accesshide')); 523 echo html_writer::select($dates, "date", $selecteddate, false); 524 echo html_writer::label(get_string('showreports'), 'menumodid', false, array('class' => 'accesshide')); 525 echo html_writer::select($activities, "modid", $selectedactivity, get_string("allactivities")); 526 echo html_writer::label(get_string('actions'), 'menumodaction', false, array('class' => 'accesshide')); 527 echo html_writer::select($actions, 'modaction', $modaction, get_string("allactions")); 528 529 $logformats = array('showashtml' => get_string('displayonpage'), 530 'downloadascsv' => get_string('downloadtext'), 531 'downloadasods' => get_string('downloadods'), 532 'downloadasexcel' => get_string('downloadexcel')); 533 echo html_writer::label(get_string('logsformat', 'report_log'), 'menulogformat', false, array('class' => 'accesshide')); 534 echo html_writer::select($logformats, 'logformat', $logformat, false); 535 echo '<input type="submit" value="'.get_string('gettheselogs').'" />'; 536 echo '</div>'; 537 echo '</form>'; 538 } 539 540 /** 541 * Fetch logs since the start of the courses and structure in series and labels to be sent to Chart API. 542 * 543 * @param stdClass $course the course object 544 * @param stdClass $user user object 545 * @param string $logreader the log reader where the logs are. 546 * @return array structured array to be sent to chart API, split in two indexes (series and labels). 547 */ 548 function report_log_userall_data($course, $user, $logreader) { 549 global $CFG; 550 $site = get_site(); 551 $timenow = time(); 552 $logs = []; 553 if ($course->id == $site->id) { 554 $courseselect = 0; 555 } else { 556 $courseselect = $course->id; 557 } 558 559 $maxseconds = REPORT_LOG_MAX_DISPLAY * 3600 * 24; // Seconds. 560 if ($timenow - $course->startdate > $maxseconds) { 561 $course->startdate = $timenow - $maxseconds; 562 } 563 564 if (!empty($CFG->loglifetime)) { 565 $maxseconds = $CFG->loglifetime * 3600 * 24; // Seconds. 566 if ($timenow - $course->startdate > $maxseconds) { 567 $course->startdate = $timenow - $maxseconds; 568 } 569 } 570 571 $timestart = $coursestart = usergetmidnight($course->startdate); 572 573 $i = 0; 574 $logs['series'][$i] = 0; 575 $logs['labels'][$i] = 0; 576 while ($timestart < $timenow) { 577 $timefinish = $timestart + 86400; 578 $logs['labels'][$i] = userdate($timestart, "%a %d %b"); 579 $logs['series'][$i] = 0; 580 $i++; 581 $timestart = $timefinish; 582 } 583 $rawlogs = report_log_usercourse($user->id, $courseselect, $coursestart, $logreader); 584 585 foreach ($rawlogs as $rawlog) { 586 if (isset($logs['labels'][$rawlog->day])) { 587 $logs['series'][$rawlog->day] = $rawlog->num; 588 } 589 } 590 591 return $logs; 592 } 593 594 /** 595 * Fetch logs of the current day and structure in series and labels to be sent to Chart API. 596 * 597 * @param stdClass $course the course object 598 * @param stdClass $user user object 599 * @param int $date A time of a day (in GMT). 600 * @param string $logreader the log reader where the logs are. 601 * @return array $logs structured array to be sent to chart API, split in two indexes (series and labels). 602 */ 603 function report_log_usertoday_data($course, $user, $date, $logreader) { 604 $site = get_site(); 605 $logs = []; 606 607 if ($course->id == $site->id) { 608 $courseselect = 0; 609 } else { 610 $courseselect = $course->id; 611 } 612 613 if ($date) { 614 $daystart = usergetmidnight($date); 615 } else { 616 $daystart = usergetmidnight(time()); 617 } 618 619 for ($i = 0; $i <= 23; $i++) { 620 $hour = $daystart + $i * 3600; 621 $logs['series'][$i] = 0; 622 $logs['labels'][$i] = userdate($hour, "%H:00"); 623 } 624 625 $rawlogs = report_log_userday($user->id, $courseselect, $daystart, $logreader); 626 627 foreach ($rawlogs as $rawlog) { 628 if (isset($logs['labels'][$rawlog->hour])) { 629 $logs['series'][$rawlog->hour] = $rawlog->num; 630 } 631 } 632 633 return $logs; 634 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body