ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç styles.css000064400000000300152266352430006602 0ustar00#page-report-log-index .info { margin: 10px; } #page-report-log-index .logselectform { margin: 10px auto; } #page-report-log-user .info { margin: 10px; text-align: center; } db/install.php000064400000002241152266352430007317 0ustar00. /** * Post installation and migration code. * * Contains code that are run during the installation of report/logs * * @package report_log * @copyright 2011 Petr Skoda {@link http://skodak.org} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; /** * Contains codes to be run during installation of report/logs * * @global moodle_database $DB * @return void */ function xmldb_report_log_install() { global $DB; } db/access.php000064400000003307152266352430007116 0ustar00. /** * Capabilities * * Defines capablities related to logs * * @package report_log * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $capabilities = array( 'report/log:view' => array( 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'coursereport/log:view', ), 'report/log:viewtoday' => array( 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'coursereport/log:viewtoday', ) ); lang/en/report_log.php000064400000004321152266352430010764 0ustar00. /** * Lang strings. * * Language strings to be used by report/logs * * @package report_log * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['allsources'] = 'All sources'; $string['cli'] = 'CLI'; $string['eventcomponent'] = 'Component'; $string['eventcontext'] = 'Event context'; $string['eventloggedas'] = '{$a->realusername} as {$a->asusername}'; $string['eventorigin'] = 'Origin'; $string['eventrelatedfullnameuser'] = 'Affected user'; $string['eventreportviewed'] = 'Log report viewed'; $string['eventuserreportviewed'] = 'User log report viewed'; $string['log:view'] = 'View course logs'; $string['log:viewtoday'] = 'View today\'s logs'; $string['page'] = 'Page {$a}'; $string['logsformat'] = 'Logs format'; $string['missingcontext'] = 'Deleted {$a->name} (id \'{$a->instanceid}\')'; $string['missingcourse'] = 'Missing course (id \'{$a->instanceid}\')'; $string['nocapability'] = 'Can not access user log report'; $string['nologreaderenabled'] = 'No log reader enabled'; $string['origin'] = 'Source'; $string['other'] = 'Other'; $string['page-report-log-x'] = 'Any log report'; $string['page-report-log-index'] = 'Course log report'; $string['page-report-log-user'] = 'User course log report'; $string['pluginname'] = 'Logs'; $string['restore'] = 'Restore'; $string['selectlogreader'] = 'Select log reader'; $string['web'] = 'Web'; $string['ws'] = 'Web service'; $string['privacy:metadata'] = 'The Logs plugin does not store any personal data.'; lib.php000064400000015550152266352430006041 0ustar00. /** * Public API of the log report. * * Defines the APIs used by log reports * * @package report_log * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core\report_helper; defined('MOODLE_INTERNAL') || die; /** * This function extends the navigation with the report items * * @param navigation_node $navigation The navigation node to extend * @param stdClass $course The course to object for the report * @param stdClass $context The context of the course */ function report_log_extend_navigation_course($navigation, $course, $context) { if ( has_capability('report/log:view', $context) && report_helper::has_valid_group($context) ) { $url = new moodle_url('/report/log/index.php', array('id'=>$course->id)); $navigation->add(get_string('pluginname', 'report_log'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', '')); } } /** * Callback to verify if the given instance of store is supported by this report or not. * * @param string $instance store instance. * * @return bool returns true if the store is supported by the report, false otherwise. */ function report_log_supports_logstore($instance) { if ($instance instanceof \core\log\sql_reader) { return true; } return false; } /** * This function extends the course navigation with the report items * * @param navigation_node $navigation The navigation node to extend * @param stdClass $user * @param stdClass $course The course to object for the report */ function report_log_extend_navigation_user($navigation, $user, $course) { list($all, $today) = report_log_can_access_user_report($user, $course); if ($today) { $url = new moodle_url('/report/log/user.php', array('id'=>$user->id, 'course'=>$course->id, 'mode'=>'today')); $navigation->add(get_string('todaylogs'), $url); } if ($all) { $url = new moodle_url('/report/log/user.php', array('id'=>$user->id, 'course'=>$course->id, 'mode'=>'all')); $navigation->add(get_string('alllogs'), $url); } } /** * Is current user allowed to access this report * * @access private defined in lib.php for performance reasons * @global stdClass $USER * @param stdClass $user * @param stdClass $course * @return array with two elements $all, $today */ function report_log_can_access_user_report($user, $course) { global $USER; $coursecontext = context_course::instance($course->id); $personalcontext = context_user::instance($user->id); if ($user->id == $USER->id) { if ($course->showreports and (is_viewing($coursecontext, $USER) or is_enrolled($coursecontext, $USER))) { return array(true, true); } } else if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext)) { if ($course->showreports and (is_viewing($coursecontext, $user) or is_enrolled($coursecontext, $user))) { return array(true, true); } } // Check if $USER shares group with $user (in case separated groups are enabled and 'moodle/site:accessallgroups' is disabled). if (!groups_user_groups_visible($course, $user->id)) { return array(false, false); } $today = false; $all = false; if (has_capability('report/log:viewtoday', $coursecontext)) { $today = true; } if (has_capability('report/log:view', $coursecontext)) { $all = true; } return array($all, $today); } /** * This function extends the module navigation with the report items * * @param navigation_node $navigation The navigation node to extend * @param stdClass $cm */ function report_log_extend_navigation_module($navigation, $cm) { if ( has_capability('report/log:view', context_course::instance($cm->course)) && report_helper::has_valid_group(context_module::instance($cm->id)) ) { $url = new moodle_url('/report/log/index.php', [ 'chooselog' => '1', 'id' => $cm->course, 'modid' => $cm->id, 'isactivitypage' => '1', ]); $navigation->add(get_string('logs'), $url, navigation_node::TYPE_SETTING, null, 'logreport', new pix_icon('i/report', '')); } } /** * Return a list of page types * * @param string $pagetype current page type * @param stdClass $parentcontext Block's parent context * @param stdClass $currentcontext Current context of block * @return array a list of page types */ function report_log_page_type_list($pagetype, $parentcontext, $currentcontext) { $array = array( '*' => get_string('page-x', 'pagetype'), 'report-*' => get_string('page-report-x', 'pagetype'), 'report-log-*' => get_string('page-report-log-x', 'report_log'), 'report-log-index' => get_string('page-report-log-index', 'report_log'), 'report-log-user' => get_string('page-report-log-user', 'report_log') ); return $array; } /** * Add nodes to myprofile page. * * @param \core_user\output\myprofile\tree $tree Tree object * @param stdClass $user user object * @param bool $iscurrentuser * @param stdClass $course Course object * * @return bool */ function report_log_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $iscurrentuser, $course) { if (empty($course)) { // We want to display these reports under the site context. $course = get_fast_modinfo(SITEID)->get_course(); } list($all, $today) = report_log_can_access_user_report($user, $course); if ($today) { // Today's log. $url = new moodle_url('/report/log/user.php', array('id' => $user->id, 'course' => $course->id, 'mode' => 'today')); $node = new core_user\output\myprofile\node('reports', 'todayslogs', get_string('todaylogs'), null, $url); $tree->add_node($node); } if ($all) { // All logs. $url = new moodle_url('/report/log/user.php', array('id' => $user->id, 'course' => $course->id, 'mode' => 'all')); $node = new core_user\output\myprofile\node('reports', 'alllogs', get_string('alllogs'), null, $url); $tree->add_node($node); } return true; } version.php000064400000002327152266352430006756 0ustar00. /** * Version info * * This File contains information about the current version of report/logs * * @package report_log * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'report_log'; // Full name of the plugin (used for diagnostics) classes/renderer.php000064400000027102152266352430010532 0ustar00. /** * Log report renderer. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; /** * Report log renderer's for printing reports. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class report_log_renderer extends plugin_renderer_base { /** * This method should never be manually called, it should only be called by process. * * @deprecated since 2.8, to be removed in 2.9 * @param report_log_renderable $reportlog * @return string */ public function render_report_log_renderable(report_log_renderable $reportlog) { debugging('Do not call this method. Please call $renderer->render($reportlog) instead.', DEBUG_DEVELOPER); return $this->render($reportlog); } /** * Render log report page. * * @param report_log_renderable $reportlog object of report_log. */ protected function render_report_log(report_log_renderable $reportlog) { if (empty($reportlog->selectedlogreader)) { echo $this->output->notification(get_string('nologreaderenabled', 'report_log'), 'notifyproblem'); return; } if ($reportlog->showselectorform) { $this->report_selector_form($reportlog); } if ($reportlog->showreport) { $reportlog->tablelog->out($reportlog->perpage, true); } } /** * Prints/return reader selector * * @param report_log_renderable $reportlog log report. */ public function reader_selector(report_log_renderable $reportlog) { $readers = $reportlog->get_readers(true); if (empty($readers)) { $readers = array(get_string('nologreaderenabled', 'report_log')); } $url = fullclone ($reportlog->url); $url->remove_params(array('logreader')); $select = new single_select($url, 'logreader', $readers, $reportlog->selectedlogreader, null); $select->set_label(get_string('selectlogreader', 'report_log')); echo $this->output->render($select); } /** * This function is used to generate and display selector form * * @param report_log_renderable $reportlog log report. */ public function report_selector_form(report_log_renderable $reportlog) { echo html_writer::start_tag('form', array('class' => 'logselecform', 'action' => $reportlog->url, 'method' => 'get')); echo html_writer::start_div('d-flex flex-wrap align-items-center gap-2'); echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'chooselog', 'value' => '1')); echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'showusers', 'value' => $reportlog->showusers)); echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'showcourses', 'value' => $reportlog->showcourses)); $selectedcourseid = empty($reportlog->sitecoursefilter) ? (empty($reportlog->course) ? 0 : $reportlog->course->id) : $reportlog->sitecoursefilter; echo $this->get_course_selector_field($reportlog, $selectedcourseid); // Add group selector. $groups = $reportlog->get_group_list(); if (!empty($groups)) { echo html_writer::label(get_string('selectagroup'), 'menugroup', false, array('class' => 'accesshide')); echo html_writer::select($groups, "group", $reportlog->groupid, get_string("allgroups")); } // Add user selector. $users = $reportlog->get_user_list(); if ($reportlog->showusers) { echo html_writer::label(get_string('selctauser'), 'menuuser', false, array('class' => 'accesshide')); echo html_writer::select($users, "user", $reportlog->userid, get_string("allparticipants")); } else { $users = array(); if (!empty($reportlog->userid)) { $users[$reportlog->userid] = $reportlog->get_selected_user_fullname(); } else { $users[0] = get_string('allparticipants'); } echo html_writer::label(get_string('selctauser'), 'menuuser', false, array('class' => 'accesshide')); echo html_writer::select($users, "user", $reportlog->userid, false); $a = new stdClass(); $a->url = new moodle_url('/report/log/index.php', array('chooselog' => 0, 'group' => $reportlog->get_selected_group(), 'user' => $reportlog->userid, 'id' => $selectedcourseid, 'date' => $reportlog->date, 'modid' => $reportlog->modid, 'showusers' => 1, 'showcourses' => $reportlog->showcourses)); $a->url = $a->url->out(false); echo html_writer::start_span('mx-1'); print_string('logtoomanyusers', 'moodle', $a); echo html_writer::end_span(); } // Add date selector. $dates = $reportlog->get_date_options(); echo html_writer::label(get_string('date'), 'menudate', false, array('class' => 'accesshide')); echo html_writer::select($dates, "date", $reportlog->date, get_string("alldays")); // Add activity selector. echo $this->get_activity_selector_field($reportlog); // Add actions selector. echo html_writer::label(get_string('actions'), 'menumodaction', false, array('class' => 'accesshide')); echo html_writer::select($reportlog->get_actions(), 'modaction', $reportlog->action, get_string("allactions")); // Add origin. $origin = $reportlog->get_origin_options(); echo html_writer::label(get_string('origin', 'report_log'), 'menuorigin', false, array('class' => 'accesshide')); echo html_writer::select($origin, 'origin', $reportlog->origin, false); // Add edulevel. $edulevel = $reportlog->get_edulevel_options(); echo html_writer::label(get_string('edulevel'), 'menuedulevel', false, array('class' => 'accesshide')); echo html_writer::select($edulevel, 'edulevel', $reportlog->edulevel, false) .$this->help_icon('edulevel'); // Add reader option. // If there is some reader available then only show submit button. $readers = $reportlog->get_readers(true); if (!empty($readers)) { if (count($readers) == 1) { $attributes = array('type' => 'hidden', 'name' => 'logreader', 'value' => key($readers)); echo html_writer::empty_tag('input', $attributes); } else { echo html_writer::label(get_string('selectlogreader', 'report_log'), 'menureader', false, array('class' => 'accesshide')); echo html_writer::select($readers, 'logreader', $reportlog->selectedlogreader, false); } echo html_writer::end_div(); echo html_writer::start_div('mt-2'); echo html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('gettheselogs'), 'class' => 'btn btn-primary')); echo html_writer::end_div(); } echo html_writer::end_tag('form'); } /** * Generates the course selector field for the log report. * * @param report_log_renderable $reportlog The output instance. * @param int $selectedcourseid * @return string */ private function get_course_selector_field(report_log_renderable $reportlog, int $selectedcourseid): string { // When the log report is accessed vie an activity, we do not need a course selector. if ($reportlog->isactivitypage) { return html_writer::empty_tag( 'input', ['type' => 'hidden', 'name' => 'id', 'value' => $selectedcourseid] ); } $result = ''; $sitecontext = context_system::instance(); $courses = $reportlog->get_course_list(); if (!empty($courses) && $reportlog->showcourses) { $result .= html_writer::label(get_string('selectacourse'), 'menuid', false, ['class' => 'accesshide']); $result .= html_writer::select($courses, "id", $selectedcourseid, null); return $result; } $courses = []; $courseinfo = ($selectedcourseid == SITEID) ? ' (' . get_string('site') . ') ' : ''; $courses[$selectedcourseid] = get_course_display_name_for_list($reportlog->course) . $courseinfo; $result .= html_writer::label(get_string('selectacourse'), 'menuid', false, ['class' => 'accesshide']); $result .= html_writer::select($courses, "id", $selectedcourseid, false); // Check if user is admin and this came because of limitation on number of courses to show in dropdown. if (has_capability('report/log:view', $sitecontext)) { $a = new stdClass(); $a->url = new moodle_url( '/report/log/index.php', [ 'chooselog' => 0, 'group' => $reportlog->get_selected_group(), 'user' => $reportlog->userid, 'id' => $selectedcourseid, 'date' => $reportlog->date, 'modid' => $reportlog->modid, 'showcourses' => 1, 'showusers' => $reportlog->showusers, ] ); $a->url = $a->url->out(false); $result .= get_string('logtoomanycourses', 'moodle', $a); } return $result; } /** * Generates the activity selector field for the log report. * * @param report_log_renderable $reportlog The output instance. * @return string */ private function get_activity_selector_field(report_log_renderable $reportlog): string { $result = ''; // When the log report is accessed vie an activity, we do not need an activity selector. if ($reportlog->isactivitypage) { $result .= html_writer::empty_tag( 'input', ['type' => 'hidden', 'name' => 'isactivitypage', 'value' => $reportlog->isactivitypage] ); $result .= html_writer::empty_tag( 'input', ['type' => 'hidden', 'name' => 'modid', 'value' => $reportlog->modid] ); return $result; } [$activities, $disabled] = $reportlog->get_activities_list(); $result .= html_writer::label( text: get_string('activities'), for: 'menumodid', colonize: false, attributes: ['class' => 'accesshide'], ); $result .= html_writer::select( options: $activities, name: "modid", selected: $reportlog->modid, nothing: get_string("allactivities"), attributes: [], disabled: $disabled, ); return $result; } } classes/renderable.php000064400000054165152266352430011040 0ustar00. /** * Log report renderer. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; use core\log\manager; /** * Report log renderable class. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class report_log_renderable implements renderable { /** @var manager log manager */ protected $logmanager; /** @var string selected log reader pluginname */ public $selectedlogreader = null; /** @var int page number */ public $page; /** @var int perpage records to show */ public $perpage; /** @var stdClass course record */ public $course; /** @var moodle_url url of report page */ public $url; /** @var int selected date from which records should be displayed */ public $date; /** @var int selected user id for which logs are displayed */ public $userid; /** @var int selected moduleid */ public $modid; /** @var string selected action filter */ public $action; /** @var int educational level */ public $edulevel; /** @var bool show courses */ public $showcourses; /** @var bool show users */ public $showusers; /** @var bool show report */ public $showreport; /** @var bool show selector form */ public $showselectorform; /** @var string selected log format */ public $logformat; /** @var string order to sort */ public $order; /** @var string origin to filter event origin */ public $origin; /** @var int group id */ public $groupid; /** @var int forces the use of a course filter in site context */ public $sitecoursefilter; /** @var table_log table log which will be used for rendering logs */ public $tablelog; /** @var array Index of delegated sections (indexed by component and itemid) */ protected $delegatedbycm; /** @var bool if the page is activity page */ public $isactivitypage; /** * Constructor. * * @param string $logreader (optional)reader pluginname from which logs will be fetched. * @param stdClass|int $course (optional) course record or id * @param int $userid (optional) id of user to filter records for. * @param int|string $modid (optional) module id or site_errors for filtering errors. * @param string $action (optional) action name to filter. * @param int $groupid (optional) groupid of user. * @param int $edulevel (optional) educational level. * @param bool $showcourses (optional) show courses. * @param bool $showusers (optional) show users. * @param bool $showreport (optional) show report. * @param bool $showselectorform (optional) show selector form. * @param moodle_url|string $url (optional) page url. * @param int $date date (optional) timestamp of start of the day for which logs will be displayed. * @param string $logformat log format. * @param int $page (optional) page number. * @param int $perpage (optional) number of records to show per page. * @param string $order (optional) sortorder of fetched records * @param string $origin (optional) origin of the event. * @param bool $isactivitypage (optional) if the page is activity page. * @param int $sitecoursefilter (optional) use a course filter in site context. */ public function __construct( $logreader = "", $course = 0, $userid = 0, $modid = 0, $action = "", $groupid = 0, $edulevel = -1, $showcourses = false, $showusers = false, $showreport = true, $showselectorform = true, $url = "", $date = 0, $logformat='showashtml', $page = 0, $perpage = 100, $order = "timecreated ASC", $origin ='', bool $isactivitypage = false, $sitecoursefilter = 0, ) { global $PAGE, $SITE; // Use first reader as selected reader, if not passed. if (empty($logreader)) { $readers = $this->get_readers(); if (!empty($readers)) { reset($readers); $logreader = key($readers); } else { $logreader = null; } } // Use page url if empty. if (empty($url)) { $url = new moodle_url($PAGE->url); } else { $url = new moodle_url($url); } $this->selectedlogreader = $logreader; $url->param('logreader', $logreader); // Use site course id, if course is empty. if (!empty($course) && is_int($course)) { $courseid = $course; try { $course = get_course($courseid); } catch (dml_missing_record_exception) { // Missing courses may have be deleted, so display them in site context. $course = $SITE; $sitecoursefilter = $courseid; } } $this->course = $course; $this->userid = $userid; $this->date = $date; $this->page = $page; $this->perpage = $perpage; $this->url = $url; $this->order = $order; $this->modid = $modid; $this->action = $action; $this->groupid = $groupid; $this->edulevel = $edulevel; $this->showcourses = $showcourses; $this->showusers = $showusers; $this->showreport = $showreport; $this->showselectorform = $showselectorform; $this->logformat = $logformat; $this->origin = $origin; $this->isactivitypage = $isactivitypage; $this->sitecoursefilter = $sitecoursefilter; } /** * Get a list of enabled sql_reader objects/name * * @param bool $nameonly if true only reader names will be returned. * @return array core\log\sql_reader object or name. */ public function get_readers($nameonly = false) { if (!isset($this->logmanager)) { $this->logmanager = get_log_manager(); } $readers = $this->logmanager->get_readers('core\log\sql_reader'); if ($nameonly) { foreach ($readers as $pluginname => $reader) { $readers[$pluginname] = $reader->get_name(); } } return $readers; } /** * Helper function to return list of activities to show in selection filter. * * @return array list of activities. */ public function get_activities_list() { $activities = []; $disabled = []; // For site just return site errors option. $sitecontext = context_system::instance(); if ($this->course->id == SITEID && has_capability('report/log:view', $sitecontext)) { $activities["site_errors"] = get_string("siteerrors"); return [$activities, $disabled]; } $modinfo = get_fast_modinfo($this->course); if (!$this->delegatedbycm) { $this->delegatedbycm = $modinfo->get_sections_delegated_by_cm(); } if (!empty($modinfo->cms)) { $section = 0; $thissection = array(); foreach ($modinfo->cms as $cm) { if (!$modname = $this->get_activity_name($cm)) { continue; } if ($cm->sectionnum > 0 and $section <> $cm->sectionnum) { $sectioninfo = $modinfo->get_section_info($cm->sectionnum); // Don't show subsections here. We are showing them in the corresponding module. if ($sectioninfo->get_component_instance()) { continue; } $activities[] = $thissection; $thissection = array(); } $section = $cm->sectionnum; $key = get_section_name($this->course, $cm->sectionnum); if (!isset($thissection[$key])) { $thissection[$key] = []; } $thissection[$key][$cm->id] = $modname; // Check if the module is delegating a section. if (array_key_exists($cm->id, $this->delegatedbycm)) { $delegated = $this->delegatedbycm[$cm->id]; $modules = (empty($delegated->sequence)) ? [] : explode(',', $delegated->sequence); $thissection[$key] = $thissection[$key] + $this->get_delegated_section_activities($modinfo, $modules); $disabled[] = $cm->id; } } if (!empty($thissection)) { $activities[] = $thissection; } } return [$activities, $disabled]; } /** * Helper function to return list of activities in a delegated section. * * @param course_modinfo $modinfo * @param array $cms List of cm ids in the section. * @return array list of activities. */ protected function get_delegated_section_activities(course_modinfo $modinfo, array $cmids): array { $activities = []; $indenter = '    '; foreach ($cmids as $cmid) { $cm = $modinfo->cms[$cmid]; if ($modname = $this->get_activity_name($cm)) { $activities[$cmid] = $indenter.$modname; } } return $activities; } /** * Helper function to return the name to show in the dropdown. * * @param cm_info $cm * @return string The name. */ private function get_activity_name(cm_info $cm): string { // Exclude activities that aren't visible or have no view link (e.g. label). Account for folders displayed inline. // Activities delegating sections might not have a URL, but should be return a name to be shown. $tobeshown = (strcmp($cm->modname, 'folder') == 0) || array_key_exists($cm->id, $this->delegatedbycm); if (!$cm->uservisible || (!$cm->has_view() && !$tobeshown)) { return ''; } $modname = strip_tags($cm->get_formatted_name()); if (core_text::strlen($modname) > 55) { $modname = core_text::substr($modname, 0, 50)."..."; } if (!$cm->visible) { $modname = "(".$modname.")"; } return $modname; } /** * Helper function to get selected group. * * @return int selected group. */ public function get_selected_group() { global $SESSION, $USER; // No groups for system. if (empty($this->course)) { return 0; } $context = context_course::instance($this->course->id); $selectedgroup = 0; // Setup for group handling. $groupmode = groups_get_course_groupmode($this->course); if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { if (isset($SESSION->currentgroup[$this->course->id])) { $selectedgroup = $SESSION->currentgroup[$this->course->id]; } else if ($this->groupid > 0) { $SESSION->currentgroup[$this->course->id] = $this->groupid; $selectedgroup = $this->groupid; } } else if ($groupmode) { $selectedgroup = $this->groupid; } return $selectedgroup; } /** * Return list of actions for log reader. * * @todo MDL-44528 Get list from log_store. * @return array list of action options. */ public function get_actions() { $actions = array( 'c' => get_string('create'), 'r' => get_string('view'), 'u' => get_string('update'), 'd' => get_string('delete'), 'cud' => get_string('allchanges') ); return $actions; } /** * Return selected user fullname. * * @return string user fullname. */ public function get_selected_user_fullname() { $user = core_user::get_user($this->userid); if (empty($this->course)) { // We are in system context. $context = context_system::instance(); } else { // We are in course context. $context = context_course::instance($this->course->id); } return fullname($user, has_capability('moodle/site:viewfullnames', $context)); } /** * Return list of courses to show in selector. * * @return array list of courses. */ public function get_course_list() { global $DB, $SITE; $courses = array(); $sitecontext = context_system::instance(); // First check to see if we can override showcourses and showusers. $numcourses = $DB->count_records("course"); if ($numcourses < COURSE_MAX_COURSES_PER_DROPDOWN && !$this->showcourses) { $this->showcourses = 1; } // Check if course filter should be shown. if (has_capability('report/log:view', $sitecontext) && $this->showcourses) { if ($courserecords = $DB->get_records("course", null, "fullname", "id,shortname,fullname,category")) { foreach ($courserecords as $course) { if ($course->id == SITEID) { $courses[$course->id] = format_string($course->fullname) . ' (' . get_string('site') . ')'; } else { $courses[$course->id] = format_string(get_course_display_name_for_list($course)); } } } // If filtering by a missing course, add a placeholder. if (!empty($this->sitecoursefilter)) { $courses[$this->sitecoursefilter] = get_string('missingcourse', 'report_log', [ 'instanceid' => $this->sitecoursefilter, ]); } core_collator::asort($courses); } return $courses; } /** * Return list of groups that are used in this course. This is done when groups are used in the course * and the user is allowed to see all groups or groups are visible anyway. If groups are used but the * mode is separate groups and the user is not allowed to see all groups, the list contains the groups * only, where the user is member. * If the course uses no groups, the list is empty. * * @return array list of groups. */ public function get_group_list() { global $USER; // No groups for system. if (empty($this->course)) { return []; } $context = context_course::instance($this->course->id); $groupmode = groups_get_course_groupmode($this->course); $grouplist = []; $userid = $groupmode == SEPARATEGROUPS ? $USER->id : 0; if (has_capability('moodle/site:accessallgroups', $context)) { $userid = 0; } $cgroups = groups_get_all_groups($this->course->id, $userid); if (!empty($cgroups)) { $grouplist = array_column($cgroups, 'name', 'id'); } return $grouplist; } /** * Return list of users. * * @return array list of users. */ public function get_user_list() { global $CFG, $SITE; $courseid = $SITE->id; if (!empty($this->course)) { $courseid = $this->course->id; } $context = context_course::instance($courseid); $limitfrom = empty($this->showusers) ? 0 : ''; $limitnum = empty($this->showusers) ? COURSE_MAX_USERS_PER_DROPDOWN + 1 : ''; $userfieldsapi = \core_user\fields::for_name(); // Get the groups of that course that the user can see. $groups = $this->get_group_list(); $groupids = array_keys($groups); // Now doublecheck the value of groupids and deal with special case like USERWITHOUTGROUP. $groupmode = groups_get_course_groupmode($this->course); if ( has_capability('moodle/site:accessallgroups', $context) || $groupmode != SEPARATEGROUPS || empty($groupids) ) { $groupids[] = USERSWITHOUTGROUP; } // First case, the user has selected a group and user is in this group. if ($this->groupid > 0) { if (!isset($groups[$this->groupid])) { // The user is not in this group, so we will ignore the group selection. $groupids = 0; } else { $groupids = [$this->groupid]; } } $courseusers = get_enrolled_users($context, '', $groupids, 'u.id, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects, null, $limitfrom, $limitnum); if (count($courseusers) < COURSE_MAX_USERS_PER_DROPDOWN && !$this->showusers) { $this->showusers = 1; } $users = array(); if ($this->showusers) { if ($courseusers) { foreach ($courseusers as $courseuser) { $users[$courseuser->id] = fullname($courseuser, has_capability('moodle/site:viewfullnames', $context)); } } $users[$CFG->siteguest] = get_string('guestuser'); } return $users; } /** * Return list of date options. * * @return array date options. */ public function get_date_options() { global $SITE; $strftimedate = get_string("strftimedate"); $strftimedaydate = get_string("strftimedaydate"); // Get all the possible dates. // Note that we are keeping track of real (GMT) time and user time. // User time is only used in displays - all calcs and passing is GMT. $timenow = time(); // GMT. // What day is it now for the user, and when is midnight that day (in GMT). $timemidnight = usergetmidnight($timenow); // Put today up the top of the list. $dates = array("$timemidnight" => get_string("today").", ".userdate($timenow, $strftimedate) ); // If course is empty, get it from frontpage. $course = $SITE; if (!empty($this->course)) { $course = $this->course; } if (!$course->startdate or ($course->startdate > $timenow)) { $course->startdate = $course->timecreated; } $numdates = 1; while ($timemidnight > $course->startdate and $numdates < 365) { $timemidnight = $timemidnight - 86400; $timenow = $timenow - 86400; $dates["$timemidnight"] = userdate($timenow, $strftimedaydate); $numdates++; } return $dates; } /** * Return list of components to show in selector. * * @return array list of origins. */ public function get_origin_options() { $ret = array(); $ret[''] = get_string('allsources', 'report_log'); $ret['cli'] = get_string('cli', 'report_log'); $ret['restore'] = get_string('restore', 'report_log'); $ret['web'] = get_string('web', 'report_log'); $ret['ws'] = get_string('ws', 'report_log'); $ret['---'] = get_string('other', 'report_log'); return $ret; } /** * Return list of edulevel. * * @todo MDL-44528 Get list from log_store. * @return array list of edulevels. */ public function get_edulevel_options() { $edulevels = array( -1 => get_string("edulevel"), 1 => get_string('edulevelteacher'), 2 => get_string('edulevelparticipating'), 0 => get_string('edulevelother') ); return $edulevels; } /** * Setup table log. */ public function setup_table() { $readers = $this->get_readers(); $filter = new \stdClass(); if (!empty($this->course)) { $filter->courseid = $this->course->id; } else { $filter->courseid = 0; } $filter->userid = $this->userid; $filter->modid = $this->modid; $filter->groupid = $this->get_selected_group(); $filter->logreader = $readers[$this->selectedlogreader]; $filter->edulevel = $this->edulevel; $filter->action = $this->action; $filter->date = $this->date; $filter->orderby = $this->order; $filter->origin = $this->origin; $filter->sitecoursefilter = $this->sitecoursefilter; // If showing site_errors. if ('site_errors' === $this->modid) { $filter->siteerrors = true; $filter->modid = 0; } $this->tablelog = new report_log_table_log('report_log', $filter); $this->tablelog->define_baseurl($this->url); $this->tablelog->is_downloadable(true); $this->tablelog->show_download_buttons_at(array(TABLE_P_BOTTOM)); } /** * Download logs in specified format. */ public function download() { $filename = 'logs_' . userdate(time(), get_string('backupnameformat', 'langconfig'), 99, false); if ($this->course->id !== SITEID) { $courseshortname = format_string($this->course->shortname, true, array('context' => context_course::instance($this->course->id))); $filename = clean_filename('logs_' . $courseshortname . '_' . userdate(time(), get_string('backupnameformat', 'langconfig'), 99, false)); } $this->tablelog->is_downloading($this->logformat, $filename); $this->tablelog->out($this->perpage, false); } } classes/privacy/provider.php000064400000002727152266352430012241 0ustar00. /** * Privacy Subsystem implementation for report_log. * * @package report_log * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_log\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for report_log implementing null_provider. * * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } }classes/helper.php000064400000004753152266352430010212 0ustar00. namespace report_log; /** * Helper class for displaying logs. * * @package report_log * @copyright 2024 Benjamin Walker * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class helper { /** * Returns a string that attempts to infer event context display when context no longer exists. * This can be used to show a minimum amount of info in log tables, but ideally this would * be a last resort as log display should not change. * * @param \core\event\base|\stdClass $event * @return string inferred context display, or an empty string. */ public static function get_context_fallback(\core\event\base|\stdClass $event): string { if (empty($event) || empty($event->contextlevel) || empty($event->component)) { return ''; } $name = \context_helper::get_level_name($event->contextlevel); $instanceid = $event->contextinstanceid ?? ''; // Use the plugin name for modules and blocks. if ($event->contextlevel == CONTEXT_MODULE || $event->contextlevel == CONTEXT_BLOCK) { // Some events list the component as core and store the modulename in other. $component = !empty($event->other['modulename']) ? 'mod_' . $event->other['modulename'] : $event->component; // Use module name to keep names consistent. Making component human readable is a close approximation. $modulename = preg_replace('/^(mod_|block_)/', '', $component); $name = str_replace('_', ' ', $modulename); } // Can't access context get_url methods, so don't bother showing a url. return get_string('missingcontext', 'report_log', [ 'name' => strtolower($name), 'instanceid' => $instanceid, ]); } } classes/event/report_viewed.php000064400000007220152266352430012722 0ustar00. /** * The report_log report viewed event. * * @package report_log * @copyright 2013 Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_log\event; defined('MOODLE_INTERNAL') || die(); /** * The report_log report viewed event class. * * @property-read array $other { * Extra information about the event. * * - int groupid: Group to display. * - int date: Date to display logs from. * - int modid: Module id for which logs were displayed. * - string modaction: Module action. * - string logformat: Log format in which logs were displayed. * } * * @package report_log * @since Moodle 2.7 * @copyright 2013 Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class report_viewed extends \core\event\base { /** * Init method. * * @return void */ protected function init() { $this->data['crud'] = 'r'; $this->data['edulevel'] = self::LEVEL_OTHER; } /** * Return localised event name. * * @return string */ public static function get_name() { return get_string('eventreportviewed', 'report_log'); } /** * Returns description of what happened. * * @return string */ public function get_description() { return "The user with id '$this->userid' viewed the log report for the course with id '$this->courseid'."; } /** * Returns relevant URL. * * @return \moodle_url */ public function get_url() { return new \moodle_url('/report/log/index.php', array('id' => $this->courseid)); } /** * Custom validation. * * @throws \coding_exception * @return void */ protected function validate_data() { parent::validate_data(); if (!isset($this->other['groupid'])) { throw new \coding_exception('The \'groupid\' value must be set in other.'); } if (!isset($this->other['date'])) { throw new \coding_exception('The \'date\' value must be set in other.'); } if (!isset($this->other['modid'])) { throw new \coding_exception('The \'modid\' value must be set in other.'); } if (!isset($this->other['modaction'])) { throw new \coding_exception('The \'modaction\' value must be set in other.'); } if (!isset($this->other['logformat'])) { throw new \coding_exception('The \'logformat\' value must be set in other.'); } if (!isset($this->relateduserid)) { throw new \coding_exception('The \'relateduserid\' must be set.'); } } public static function get_other_mapping() { $othermapped = array(); $othermapped['modid'] = array('db' => 'course_modules', 'restore' => 'course_module'); $othermapped['groupid'] = array('db' => 'groups', 'restore' => 'group'); return $othermapped; } } classes/event/user_report_viewed.php000064400000005420152266352430013760 0ustar00. /** * The report_log user report viewed event. * * @package report_log * @copyright 2013 Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_log\event; defined('MOODLE_INTERNAL') || die(); /** * The report_log user report viewed event class. * * @property-read array $other { * Extra information about the event. * * - string mode: display mode. * } * * @package report_log * @since Moodle 2.7 * @copyright 2013 Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_report_viewed extends \core\event\base { /** * Init method. * * @return void */ protected function init() { $this->data['crud'] = 'r'; $this->data['edulevel'] = self::LEVEL_OTHER; } /** * Return localised event name. * * @return string */ public static function get_name() { return get_string('eventuserreportviewed', 'report_log'); } /** * Returns description of what happened. * * @return string */ public function get_description() { return "The user with id '$this->userid' viewed the user log report for the user with id '$this->relateduserid'."; } /** * Returns relevant URL. * * @return \moodle_url */ public function get_url() { return new \moodle_url('/report/log/user.php', array('course' => $this->courseid, 'id' => $this->relateduserid, 'mode' => $this->other['mode'])); } /** * Custom validation. * * @throws \coding_exception * @return void */ protected function validate_data() { parent::validate_data(); if (empty($this->other['mode'])) { throw new \coding_exception('The \'mode\' value must be set in other.'); } if (empty($this->relateduserid)) { throw new \coding_exception('The \'relateduserid\' must be set.'); } } public static function get_other_mapping() { // Nothing to map. return false; } } classes/table_log.php000064400000046300152266352430010655 0ustar00. /** * Table log for displaying logs. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core\dataformat; use core\report_helper; defined('MOODLE_INTERNAL') || die; global $CFG; require_once($CFG->libdir . '/tablelib.php'); /** * Table log class for displaying logs. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class report_log_table_log extends table_sql { /** @var array list of user fullnames shown in report */ private $userfullnames = array(); /** @var array list of context name shown in report */ private $contextname = array(); /** @var stdClass filters parameters */ private $filterparams; /** @var int[] A list of users to filter by */ private ?array $lateuseridfilter = null; /** * Sets up the table_log parameters. * * @param string $uniqueid unique id of form. * @param stdClass $filterparams (optional) filter params. * - int courseid: id of course * - int userid: user id * - int|string modid: Module id or "site_errors" to view site errors * - int groupid: Group id * - \core\log\sql_reader logreader: reader from which data will be fetched. * - int edulevel: educational level. * - string action: view action * - int date: Date from which logs to be viewed. */ public function __construct($uniqueid, $filterparams = null) { parent::__construct($uniqueid); $this->set_attribute('class', 'reportlog generaltable generalbox table-sm'); $this->filterparams = $filterparams; // Add course column if logs are displayed for site. $cols = array(); $headers = array(); if (empty($filterparams->courseid)) { $cols = array('course'); $headers = array(get_string('course')); } $this->define_columns(array_merge($cols, array('time', 'fullnameuser', 'relatedfullnameuser', 'context', 'component', 'eventname', 'description', 'origin', 'ip'))); $this->define_headers(array_merge($headers, array( get_string('time'), get_string('fullnameuser'), get_string('eventrelatedfullnameuser', 'report_log'), get_string('eventcontext', 'report_log'), get_string('eventcomponent', 'report_log'), get_string('eventname'), get_string('description'), get_string('eventorigin', 'report_log'), get_string('ip_address') ) )); $this->collapsible(false); $this->sortable(false); $this->pageable(true); } /** * Gets the user full name. * * This function is useful because, in the unlikely case that the user is * not already loaded in $this->userfullnames it will fetch it from db. * * @since Moodle 2.9 * @param int $userid * @return string|false */ protected function get_user_fullname($userid) { if (empty($userid)) { return false; } // Check if we already have this users' fullname. $userfullname = $this->userfullnames[$userid] ?? null; if (!empty($userfullname)) { return $userfullname; } // We already looked for the user and it does not exist. if ($userfullname === false) { return false; } // If we reach that point new users logs have been generated since the last users db query. $userfieldsapi = \core_user\fields::for_name(); $fields = $userfieldsapi->get_sql('', false, '', '', false)->selects; if ($user = \core_user::get_user($userid, $fields)) { $this->userfullnames[$userid] = fullname($user, has_capability('moodle/site:viewfullnames', $this->get_context())); } else { $this->userfullnames[$userid] = false; } return $this->userfullnames[$userid]; } /** * Generate the time column. * * @param stdClass $event event data. * @return string HTML for the time column */ public function col_time($event) { if (empty($this->download)) { $dateformat = get_string('strftimedatetimeaccurate', 'core_langconfig'); } else { $dateformat = get_string('strftimedatetimeshortaccurate', 'core_langconfig'); } return userdate($event->timecreated, $dateformat); } /** * Generate the username column. * * @param \core\event\base $event event data. * @return string HTML for the username column */ public function col_fullnameuser($event) { // Get extra event data for origin and realuserid. $logextra = $event->get_logextra(); $eventusername = $event->userid ? $this->get_user_fullname($event->userid) : false; // Add username who did the action. if (!empty($logextra['realuserid'])) { $a = new stdClass(); if (!$a->realusername = $this->get_user_fullname($logextra['realuserid'])) { $a->realusername = '-'; } if (!$a->asusername = $eventusername) { $a->asusername = '-'; } if (empty($this->download)) { $params = array('id' => $logextra['realuserid']); if ($event->courseid) { $params['course'] = $event->courseid; } $a->realusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->realusername); $params['id'] = $event->userid; $a->asusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->asusername); } $username = get_string('eventloggedas', 'report_log', $a); } else if ($eventusername) { if (empty($this->download)) { $params = ['id' => $event->userid]; if ($event->courseid) { $params['course'] = $event->courseid; } $username = html_writer::link(new moodle_url('/user/view.php', $params), $eventusername); } else { $username = $eventusername; } } else { $username = '-'; } return $username; } /** * Generate the related username column. * * @param stdClass $event event data. * @return string HTML for the related username column */ public function col_relatedfullnameuser($event) { // Add affected user. if (!empty($event->relateduserid) && $username = $this->get_user_fullname($event->relateduserid)) { if (empty($this->download)) { $params = array('id' => $event->relateduserid); if ($event->courseid) { $params['course'] = $event->courseid; } $username = html_writer::link(new moodle_url('/user/view.php', $params), $username); } } else { $username = '-'; } return $username; } /** * Generate the context column. * * @param stdClass $event event data. * @return string HTML for the context column */ public function col_context($event) { // Add context name. if ($event->contextid) { // If context name was fetched before then return, else get one. if (isset($this->contextname[$event->contextid])) { return $this->contextname[$event->contextid]; } else { $context = context::instance_by_id($event->contextid, IGNORE_MISSING); if ($context) { $contextname = $context->get_context_name(true); if (empty($this->download) && $url = $context->get_url()) { $contextname = html_writer::link($url, $contextname); } } else if (!$contextname = \report_log\helper::get_context_fallback($event)) { $contextname = get_string('other'); } } } else { $contextname = get_string('other'); } $this->contextname[$event->contextid] = $contextname; return $contextname; } /** * Generate the component column. * * @param stdClass $event event data. * @return string HTML for the component column */ public function col_component($event) { // Component. $componentname = $event->component; if (($event->component === 'core') || ($event->component === 'legacy')) { return get_string('coresystem'); } else if (get_string_manager()->string_exists('pluginname', $event->component)) { return get_string('pluginname', $event->component); } else { return $componentname; } } /** * Generate the event name column. * * @param stdClass $event event data. * @return string HTML for the event name column */ public function col_eventname($event) { // Event name. $eventname = $event->get_name(); // Only encode as an action link if we're not downloading. if (($url = $event->get_url()) && empty($this->download)) { $eventname = $this->action_link($url, $eventname, 'action'); } return $eventname; } /** * Generate the description column. * * @param stdClass $event event data. * @return string HTML for the description column */ public function col_description($event) { if (empty($this->download) || dataformat::get_format_instance($this->download)->supports_html()) { return format_text($event->get_description(), FORMAT_PLAIN); } else { return $event->get_description(); } } /** * Generate the origin column. * * @param stdClass $event event data. * @return string HTML for the origin column */ public function col_origin($event) { // Get extra event data for origin and realuserid. $logextra = $event->get_logextra(); // Add event origin, normally IP/cron. return $logextra['origin']; } /** * Generate the ip column. * * @param stdClass $event event data. * @return string HTML for the ip column */ public function col_ip($event) { // Get extra event data for origin and realuserid. $logextra = $event->get_logextra(); $ip = $logextra['ip']; if (empty($this->download)) { $url = new moodle_url("/iplookup/index.php?popup=1&ip={$ip}&user={$event->userid}"); $ip = $this->action_link($url, $ip, 'ip'); } return $ip; } /** * Method to create a link with popup action. * * @param moodle_url $url The url to open. * @param string $text Anchor text for the link. * @param string $name Name of the popup window. * * @return string html to use. */ protected function action_link(moodle_url $url, $text, $name = 'popup') { global $OUTPUT; $link = new action_link($url, $text, new popup_action('click', $url, $name, array('height' => 440, 'width' => 700))); return $OUTPUT->render($link); } /** * Helper function to get legacy crud action. * * @param string $crud crud action * @return string legacy action. */ public function get_legacy_crud_action($crud) { $legacyactionmap = array('c' => 'add', 'r' => 'view', 'u' => 'update', 'd' => 'delete'); if (array_key_exists($crud, $legacyactionmap)) { return $legacyactionmap[$crud]; } else { // From old legacy log. return '-view'; } } /** * Helper function which is used by build logs to get action sql and param. * * @return array sql and param for action. */ public function get_action_sql() { global $DB; // In new logs we have a field to pick, and in legacy try get this from action. if (!empty($this->filterparams->action)) { list($sql, $params) = $DB->get_in_or_equal(str_split($this->filterparams->action), SQL_PARAMS_NAMED, 'crud'); $sql = "crud " . $sql; } else { // Add condition for all possible values of crud (to use db index). list($sql, $params) = $DB->get_in_or_equal(array('c', 'r', 'u', 'd'), SQL_PARAMS_NAMED, 'crud'); $sql = "crud ".$sql; } return array($sql, $params); } /** * Helper function which is used by build logs to get course module sql and param. * * @return array sql and param for action. */ public function get_cm_sql() { $joins = array(); $params = array(); $joins[] = "contextinstanceid = :contextinstanceid"; $joins[] = "contextlevel = :contextmodule"; $params['contextinstanceid'] = $this->filterparams->modid; $params['contextmodule'] = CONTEXT_MODULE; $sql = implode(' AND ', $joins); return array($sql, $params); } /** * Query the reader. Store results in the object for use by build_table. * * @param int $pagesize size of page for paginated displayed table. * @param bool $useinitialsbar do you want to use the initials bar. */ public function query_db($pagesize, $useinitialsbar = true) { global $DB, $USER; $joins = array(); $params = array(); // If we filter by userid and module id we also need to filter by crud and edulevel to ensure DB index is engaged. $useextendeddbindex = !empty($this->filterparams->userid) && !empty($this->filterparams->modid); if (!empty($this->filterparams->courseid) && $this->filterparams->courseid != SITEID) { $joins[] = "courseid = :courseid"; $params['courseid'] = $this->filterparams->courseid; } else if (!empty($this->filterparams->sitecoursefilter)) { // Add filters for missing/deleted courses in site context. $joins[] = "courseid = :courseid"; $params['courseid'] = $this->filterparams->sitecoursefilter; } if (!empty($this->filterparams->siteerrors)) { $joins[] = "( action='error' OR action='infected' OR action='failed' )"; } if (!empty($this->filterparams->modid)) { list($actionsql, $actionparams) = $this->get_cm_sql(); $joins[] = $actionsql; $params = array_merge($params, $actionparams); } if (!empty($this->filterparams->action) || $useextendeddbindex) { list($actionsql, $actionparams) = $this->get_action_sql(); $joins[] = $actionsql; $params = array_merge($params, $actionparams); } // Getting all members of a group. [ 'joins' => $groupjoins, 'params' => $groupparams, 'useridfilter' => $this->lateuseridfilter, ] = report_helper::get_group_filter($this->filterparams); $joins = array_merge($joins, $groupjoins); $params = array_merge($params, $groupparams); if (!empty($this->filterparams->date)) { $joins[] = "timecreated > :date AND timecreated < :enddate"; $params['date'] = $this->filterparams->date; $params['enddate'] = $this->filterparams->date + DAYSECS; // Show logs only for the selected date. } if (isset($this->filterparams->edulevel) && ($this->filterparams->edulevel >= 0)) { $joins[] = "edulevel = :edulevel"; $params['edulevel'] = $this->filterparams->edulevel; } else if ($useextendeddbindex) { list($edulevelsql, $edulevelparams) = $DB->get_in_or_equal(array(\core\event\base::LEVEL_OTHER, \core\event\base::LEVEL_PARTICIPATING, \core\event\base::LEVEL_TEACHING), SQL_PARAMS_NAMED, 'edulevel'); $joins[] = "edulevel ".$edulevelsql; $params = array_merge($params, $edulevelparams); } // Origin. if (isset($this->filterparams->origin) && ($this->filterparams->origin != '')) { if ($this->filterparams->origin !== '---') { // Filter by a single origin. $joins[] = "origin = :origin"; $params['origin'] = $this->filterparams->origin; } else { // Filter by everything else. list($originsql, $originparams) = $DB->get_in_or_equal(array('cli', 'restore', 'ws', 'web'), SQL_PARAMS_NAMED, 'origin', false); $joins[] = "origin " . $originsql; $params = array_merge($params, $originparams); } } // Filter out anonymous actions, this is N/A for legacy log because it never stores them. if ($this->filterparams->modid) { $context = context_module::instance($this->filterparams->modid); } else { $context = context_course::instance($this->filterparams->courseid); } if (!has_capability('moodle/site:viewanonymousevents', $context)) { $joins[] = "anonymous = 0"; } $selector = implode(' AND ', $joins); if (!$this->is_downloading()) { $total = $this->filterparams->logreader->get_events_select_count($selector, $params); $this->pagesize($pagesize, $total); } else { $this->pageable(false); } // Get the events. Same query than before; even if it is not likely, logs from new users // may be added since last query so we will need to work around later to prevent problems. // In almost most of the cases this will be better than having two opened recordsets. $this->rawdata = new \CallbackFilterIterator( $this->filterparams->logreader->get_events_select_iterator( $selector, $params, $this->filterparams->orderby, $this->get_page_start(), $this->get_page_size(), ), function ($event) { if ($this->lateuseridfilter === null) { return true; } return isset($this->lateuseridfilter[$event->userid]); }, ); // Set initial bars. if ($useinitialsbar && !$this->is_downloading()) { $this->initialbars($total > $pagesize); } } } locallib.php000064400000057156152266352430007064 0ustar00. /** * This file contains functions used by the log reports * * This files lists the functions that are used during the log report generation. * * @package report_log * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; if (!defined('REPORT_LOG_MAX_DISPLAY')) { define('REPORT_LOG_MAX_DISPLAY', 150); // days } require_once(__DIR__.'/lib.php'); /** * This function is used to generate and display the log activity graph * * @global stdClass $CFG * @param stdClass $course course instance * @param int|stdClass $user id/object of the user whose logs are needed * @param string $typeormode type of logs graph needed (usercourse.png/userday.png) or the mode (today, all). * @param int $date timestamp in GMT (seconds since epoch) * @param string $logreader Log reader. * @param int $sitecoursefilter use a course filter in site context. * @return void */ function report_log_print_graph($course, $user, $typeormode, $date=0, $logreader='', $sitecoursefilter = 0) { global $CFG, $OUTPUT; if (!is_object($user)) { $user = core_user::get_user($user); } $logmanager = get_log_manager(); $readers = $logmanager->get_readers(); if (empty($logreader)) { $reader = reset($readers); } else { $reader = $readers[$logreader]; } // If reader is not a sql_internal_table_reader and not legacy store then don't show graph. if (!($reader instanceof \core\log\sql_internal_table_reader)) { return array(); } $coursecontext = context_course::instance($course->id); $a = new stdClass(); $a->coursename = format_string($course->shortname, true, array('context' => $coursecontext)); $a->username = fullname($user, true); if ($typeormode == 'today' || $typeormode == 'userday.png') { $logs = report_log_usertoday_data($course, $user, $date, $logreader, $sitecoursefilter); $title = get_string("hitsoncoursetoday", "", $a); } else if ($typeormode == 'all' || $typeormode == 'usercourse.png') { $logs = report_log_userall_data($course, $user, $logreader, $sitecoursefilter); $title = get_string("hitsoncourse", "", $a); } if (!empty($CFG->preferlinegraphs)) { $chart = new \core\chart_line(); } else { $chart = new \core\chart_bar(); } $series = new \core\chart_series(get_string("hits"), $logs['series']); $chart->add_series($series); $chart->set_title($title); $chart->set_labels($logs['labels']); $yaxis = $chart->get_yaxis(0, true); $yaxis->set_label(get_string("hits")); $yaxis->set_stepsize(max(1, round(max($logs['series']) / 10))); echo $OUTPUT->render($chart); } /** * Select all log records for a given course and user * * @param int $userid The id of the user as found in the 'user' table. * @param int $courseid The id of the course as found in the 'course' table. * @param string $coursestart unix timestamp representing course start date and time. * @param string $logreader log reader to use. * @return array */ function report_log_usercourse($userid, $courseid, $coursestart, $logreader = '') { global $DB; $logmanager = get_log_manager(); $readers = $logmanager->get_readers(); if (empty($logreader)) { $reader = reset($readers); } else { $reader = $readers[$logreader]; } // If reader is not a sql_internal_table_reader and not legacy store then return. if (!($reader instanceof \core\log\sql_internal_table_reader)) { return array(); } $coursestart = (int)$coursestart; // Note: unfortunately pg complains if you use name parameter or column alias in GROUP BY. $logtable = $reader->get_internal_log_table_name(); $timefield = 'timecreated'; $coursefield = 'courseid'; $nonanonymous = 'AND anonymous = 0'; $params = array(); $courseselect = ''; if ($courseid) { $courseselect = "AND $coursefield = :courseid"; $params['courseid'] = $courseid; } $params['userid'] = $userid; return $DB->get_records_sql("SELECT FLOOR(($timefield - $coursestart)/" . DAYSECS . ") AS day, COUNT(*) AS num FROM {" . $logtable . "} WHERE userid = :userid AND $timefield > $coursestart $courseselect $nonanonymous GROUP BY FLOOR(($timefield - $coursestart)/" . DAYSECS .")", $params); } /** * Select all log records for a given course, user, and day * * @param int $userid The id of the user as found in the 'user' table. * @param int $courseid The id of the course as found in the 'course' table. * @param string $daystart unix timestamp of the start of the day for which the logs needs to be retrived * @param string $logreader log reader to use. * @return array */ function report_log_userday($userid, $courseid, $daystart, $logreader = '') { global $DB; $logmanager = get_log_manager(); $readers = $logmanager->get_readers(); if (empty($logreader)) { $reader = reset($readers); } else { $reader = $readers[$logreader]; } // If reader is not a sql_internal_table_reader and not legacy store then return. if (!($reader instanceof \core\log\sql_internal_table_reader)) { return array(); } $daystart = (int)$daystart; // Note: unfortunately pg complains if you use name parameter or column alias in GROUP BY. $logtable = $reader->get_internal_log_table_name(); $timefield = 'timecreated'; $coursefield = 'courseid'; $nonanonymous = 'AND anonymous = 0'; $params = array('userid' => $userid); $courseselect = ''; if ($courseid) { $courseselect = "AND $coursefield = :courseid"; $params['courseid'] = $courseid; } return $DB->get_records_sql("SELECT FLOOR(($timefield - $daystart)/" . HOURSECS . ") AS hour, COUNT(*) AS num FROM {" . $logtable . "} WHERE userid = :userid AND $timefield > $daystart $courseselect $nonanonymous GROUP BY FLOOR(($timefield - $daystart)/" . HOURSECS . ") ", $params); } /** * This function is used to generate and display Mnet selector form * * @global stdClass $USER * @global stdClass $CFG * @global stdClass $SITE * @global moodle_database $DB * @global core_renderer $OUTPUT * @global stdClass $SESSION * @uses CONTEXT_SYSTEM * @uses COURSE_MAX_COURSES_PER_DROPDOWN * @uses CONTEXT_COURSE * @uses SEPARATEGROUPS * @param int $hostid host id * @param stdClass $course course instance * @param int $selecteduser id of the selected user * @param string $selecteddate Date selected * @param string $modname course_module->id * @param string $modid number or 'site_errors' * @param string $modaction an action as recorded in the logs * @param int $selectedgroup Group to display * @param int $showcourses whether to show courses if we're over our limit. * @param int $showusers whether to show users if we're over our limit. * @param string $logformat Format of the logs (downloadascsv, showashtml, downloadasods, downloadasexcel) * @return void */ function report_log_print_mnet_selector_form($hostid, $course, $selecteduser=0, $selecteddate='today', $modname="", $modid=0, $modaction='', $selectedgroup=-1, $showcourses=0, $showusers=0, $logformat='showashtml') { global $USER, $CFG, $SITE, $DB, $OUTPUT, $SESSION; require_once $CFG->dirroot.'/mnet/peer.php'; $mnet_peer = new mnet_peer(); $mnet_peer->set_id($hostid); $sql = "SELECT DISTINCT course, hostid, coursename FROM {mnet_log}"; $courses = $DB->get_records_sql($sql); $remotecoursecount = count($courses); // first check to see if we can override showcourses and showusers $numcourses = $remotecoursecount + $DB->count_records('course'); if ($numcourses < COURSE_MAX_COURSES_PER_DROPDOWN && !$showcourses) { $showcourses = 1; } $sitecontext = context_system::instance(); // Context for remote data is always SITE // Groups for remote data are always OFF if ($hostid == $CFG->mnet_localhost_id) { $context = context_course::instance($course->id); /// Setup for group handling. if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { $selectedgroup = -1; $showgroups = false; } else if ($course->groupmode) { $showgroups = true; } else { $selectedgroup = 0; $showgroups = false; } if ($selectedgroup === -1) { if (isset($SESSION->currentgroup[$course->id])) { $selectedgroup = $SESSION->currentgroup[$course->id]; } else { $selectedgroup = groups_get_all_groups($course->id, $USER->id); if (is_array($selectedgroup)) { $selectedgroup = array_shift(array_keys($selectedgroup)); $SESSION->currentgroup[$course->id] = $selectedgroup; } else { $selectedgroup = 0; } } } } else { $context = $sitecontext; } // Get all the possible users $users = array(); // Define limitfrom and limitnum for queries below // If $showusers is enabled... don't apply limitfrom and limitnum $limitfrom = empty($showusers) ? 0 : ''; $limitnum = empty($showusers) ? COURSE_MAX_USERS_PER_DROPDOWN + 1 : ''; // If looking at a different host, we're interested in all our site users if ($hostid == $CFG->mnet_localhost_id && $course->id != SITEID) { $userfieldsapi = \core_user\fields::for_name(); $courseusers = get_enrolled_users($context, '', $selectedgroup, 'u.id, ' . $userfieldsapi->get_sql('u', false, '', '', false)->selects, null, $limitfrom, $limitnum); } else { // this may be a lot of users :-( $userfieldsapi = \core_user\fields::for_name(); $courseusers = $DB->get_records('user', array('deleted' => 0), 'lastaccess DESC', 'id, ' . $userfieldsapi->get_sql('', false, '', '', false)->selects, $limitfrom, $limitnum); } if (count($courseusers) < COURSE_MAX_USERS_PER_DROPDOWN && !$showusers) { $showusers = 1; } if ($showusers) { if ($courseusers) { foreach ($courseusers as $courseuser) { $users[$courseuser->id] = fullname($courseuser, has_capability('moodle/site:viewfullnames', $context)); } } $users[$CFG->siteguest] = get_string('guestuser'); } // Get all the hosts that have log records $sql = "select distinct h.id, h.name from {mnet_host} h, {mnet_log} l where h.id = l.hostid order by h.name"; if ($hosts = $DB->get_records_sql($sql)) { foreach($hosts as $host) { $hostarray[$host->id] = $host->name; } } $hostarray[$CFG->mnet_localhost_id] = $SITE->fullname; asort($hostarray); $dropdown = array(); foreach($hostarray as $hostid => $name) { $courses = array(); $sites = array(); if ($CFG->mnet_localhost_id == $hostid) { if (has_capability('report/log:view', $sitecontext) && $showcourses) { if ($ccc = $DB->get_records("course", null, "fullname","id,shortname,fullname,category")) { foreach ($ccc as $cc) { if ($cc->id == SITEID) { $sites["$hostid/$cc->id"] = format_string($cc->fullname).' ('.get_string('site').')'; } else { $courses["$hostid/$cc->id"] = format_string(get_course_display_name_for_list($cc)); } } } } } else { if (has_capability('report/log:view', $sitecontext) && $showcourses) { $sql = "SELECT DISTINCT course, coursename FROM {mnet_log} where hostid = ?"; if ($ccc = $DB->get_records_sql($sql, array($hostid))) { foreach ($ccc as $cc) { if (1 == $cc->course) { // TODO: this might be wrong - site course may have another id $sites["$hostid/$cc->course"] = $cc->coursename.' ('.get_string('site').')'; } else { $courses["$hostid/$cc->course"] = $cc->coursename; } } } } } asort($courses); $dropdown[] = array($name=>($sites + $courses)); } $activities = array(); $selectedactivity = ""; $modinfo = get_fast_modinfo($course); if (!empty($modinfo->cms)) { $section = 0; $thissection = array(); foreach ($modinfo->cms as $cm) { // Exclude activities that aren't visible or have no view link (e.g. label). Account for folder being displayed inline. if (!$cm->uservisible || (!$cm->has_view() && strcmp($cm->modname, 'folder') !== 0)) { continue; } if ($cm->sectionnum > 0 and $section <> $cm->sectionnum) { $activities[] = $thissection; $thissection = array(); } $section = $cm->sectionnum; $modname = strip_tags($cm->get_formatted_name()); if (core_text::strlen($modname) > 55) { $modname = core_text::substr($modname, 0, 50)."..."; } if (!$cm->visible) { $modname = "(".$modname.")"; } $key = get_section_name($course, $cm->sectionnum); if (!isset($thissection[$key])) { $thissection[$key] = array(); } $thissection[$key][$cm->id] = $modname; if ($cm->id == $modid) { $selectedactivity = "$cm->id"; } } if (!empty($thissection)) { $activities[] = $thissection; } } if (has_capability('report/log:view', $sitecontext) && !$course->category) { $activities["site_errors"] = get_string("siteerrors"); if ($modid === "site_errors") { $selectedactivity = "site_errors"; } } $strftimedate = get_string("strftimedate"); $strftimedaydate = get_string("strftimedaydate"); asort($users); // Prepare the list of action options. $actions = array( 'view' => get_string('view'), 'add' => get_string('add'), 'update' => get_string('update'), 'delete' => get_string('delete'), '-view' => get_string('allchanges') ); // Get all the possible dates // Note that we are keeping track of real (GMT) time and user time // User time is only used in displays - all calcs and passing is GMT $timenow = time(); // GMT // What day is it now for the user, and when is midnight that day (in GMT). $timemidnight = $today = usergetmidnight($timenow); // Put today up the top of the list $dates = array( "0" => get_string('alldays'), "$timemidnight" => get_string("today").", ".userdate($timenow, $strftimedate) ); if (!$course->startdate or ($course->startdate > $timenow)) { $course->startdate = $course->timecreated; } $numdates = 1; while ($timemidnight > $course->startdate and $numdates < 365) { $timemidnight = $timemidnight - 86400; $timenow = $timenow - 86400; $dates["$timemidnight"] = userdate($timenow, $strftimedaydate); $numdates++; } if ($selecteddate === "today") { $selecteddate = $today; } echo "
wwwroot/report/log/index.php\" method=\"get\">\n"; echo "
\n";//invisible fieldset here breaks wrapping echo "\n"; echo "\n"; echo "\n"; if (has_capability('report/log:view', $sitecontext) && $showcourses) { $cid = empty($course->id)? '1' : $course->id; echo html_writer::label(get_string('selectacoursesite'), 'menuhost_course', false, array('class' => 'accesshide')); echo html_writer::select($dropdown, "host_course", $hostid.'/'.$cid); } else { $courses = array(); $courses[$course->id] = get_course_display_name_for_list($course) . ((empty($course->category)) ? ' ('.get_string('site').') ' : ''); echo html_writer::label(get_string('selectacourse'), 'menuid', false, array('class' => 'accesshide')); echo html_writer::select($courses,"id",$course->id, false); if (has_capability('report/log:view', $sitecontext)) { $a = new stdClass(); $a->url = "$CFG->wwwroot/report/log/index.php?chooselog=0&group=$selectedgroup&user=$selecteduser" ."&id=$course->id&date=$selecteddate&modid=$selectedactivity&showcourses=1&showusers=$showusers"; print_string('logtoomanycourses','moodle',$a); } } if ($showgroups) { if ($cgroups = groups_get_all_groups($course->id)) { foreach ($cgroups as $cgroup) { $groups[$cgroup->id] = $cgroup->name; } } else { $groups = array(); } echo html_writer::label(get_string('selectagroup'), 'menugroup', false, array('class' => 'accesshide')); echo html_writer::select($groups, "group", $selectedgroup, get_string("allgroups")); } if ($showusers) { echo html_writer::label(get_string('participantslist'), 'menuuser', false, array('class' => 'accesshide')); echo html_writer::select($users, "user", $selecteduser, get_string("allparticipants")); } else { $users = array(); if (!empty($selecteduser)) { $user = $DB->get_record('user', array('id'=>$selecteduser)); $users[$selecteduser] = fullname($user); } else { $users[0] = get_string('allparticipants'); } echo html_writer::label(get_string('participantslist'), 'menuuser', false, array('class' => 'accesshide')); echo html_writer::select($users, "user", $selecteduser, false); $a = new stdClass(); $a->url = "$CFG->wwwroot/report/log/index.php?chooselog=0&group=$selectedgroup&user=$selecteduser" ."&id=$course->id&date=$selecteddate&modid=$selectedactivity&showusers=1&showcourses=$showcourses"; print_string('logtoomanyusers','moodle',$a); } echo html_writer::label(get_string('date'), 'menudate', false, array('class' => 'accesshide')); echo html_writer::select($dates, "date", $selecteddate, false); echo html_writer::label(get_string('showreports'), 'menumodid', false, array('class' => 'accesshide')); echo html_writer::select($activities, "modid", $selectedactivity, get_string("allactivities")); echo html_writer::label(get_string('actions'), 'menumodaction', false, array('class' => 'accesshide')); echo html_writer::select($actions, 'modaction', $modaction, get_string("allactions")); $logformats = array('showashtml' => get_string('displayonpage'), 'downloadascsv' => get_string('downloadtext'), 'downloadasods' => get_string('downloadods'), 'downloadasexcel' => get_string('downloadexcel')); echo html_writer::label(get_string('logsformat', 'report_log'), 'menulogformat', false, array('class' => 'accesshide')); echo html_writer::select($logformats, 'logformat', $logformat, false); echo ''; echo '
'; echo '
'; } /** * Fetch logs since the start of the courses and structure in series and labels to be sent to Chart API. * * @param stdClass $course the course object * @param stdClass $user user object * @param string $logreader the log reader where the logs are. * @param int $sitecoursefilter use a course filter in site context. * @return array structured array to be sent to chart API, split in two indexes (series and labels). */ function report_log_userall_data($course, $user, $logreader, $sitecoursefilter = 0) { global $CFG; $site = get_site(); $timenow = time(); $logs = []; if ($course->id == $site->id) { $courseselect = $sitecoursefilter; } else { $courseselect = $course->id; } $maxseconds = REPORT_LOG_MAX_DISPLAY * 3600 * 24; // Seconds. if ($timenow - $course->startdate > $maxseconds) { $course->startdate = $timenow - $maxseconds; } if (!empty($CFG->loglifetime)) { $maxseconds = $CFG->loglifetime * 3600 * 24; // Seconds. if ($timenow - $course->startdate > $maxseconds) { $course->startdate = $timenow - $maxseconds; } } $timestart = $coursestart = usergetmidnight($course->startdate); $i = 0; $logs['series'][$i] = 0; $logs['labels'][$i] = 0; while ($timestart < $timenow) { $timefinish = $timestart + 86400; $logs['labels'][$i] = userdate($timestart, "%a %d %b"); $logs['series'][$i] = 0; $i++; $timestart = $timefinish; } $rawlogs = report_log_usercourse($user->id, $courseselect, $coursestart, $logreader); foreach ($rawlogs as $rawlog) { if (isset($logs['labels'][$rawlog->day])) { $logs['series'][$rawlog->day] = $rawlog->num; } } return $logs; } /** * Fetch logs of the current day and structure in series and labels to be sent to Chart API. * * @param stdClass $course the course object * @param stdClass $user user object * @param int $date A time of a day (in GMT). * @param string $logreader the log reader where the logs are. * @param int $sitecoursefilter use a course filter in site context. * @return array $logs structured array to be sent to chart API, split in two indexes (series and labels). */ function report_log_usertoday_data($course, $user, $date, $logreader, $sitecoursefilter = 0) { $site = get_site(); $logs = []; if ($course->id == $site->id) { $courseselect = $sitecoursefilter; } else { $courseselect = $course->id; } if ($date) { $daystart = usergetmidnight($date); } else { $daystart = usergetmidnight(time()); } for ($i = 0; $i <= 23; $i++) { $hour = $daystart + $i * 3600; $logs['series'][$i] = 0; $logs['labels'][$i] = userdate($hour, "%H:00"); } $rawlogs = report_log_userday($user->id, $courseselect, $daystart, $logreader); foreach ($rawlogs as $rawlog) { if (isset($logs['labels'][$rawlog->hour])) { $logs['series'][$rawlog->hour] = $rawlog->num; } } return $logs; } index.php000064400000020361152266352430006376 0ustar00. /** * Displays different views of the logs. * * @package report_log * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core\report_helper; require('../../config.php'); require_once($CFG->dirroot.'/course/lib.php'); require_once($CFG->dirroot.'/report/log/locallib.php'); require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->dirroot.'/lib/tablelib.php'); $id = optional_param('id', 0, PARAM_INT);// Course ID. $group = optional_param('group', 0, PARAM_INT); // Group to display. $user = optional_param('user', 0, PARAM_INT); // User to display. $date = optional_param('date', 0, PARAM_INT); // Date to display. $modid = optional_param('modid', 0, PARAM_ALPHANUMEXT); // Module id or 'site_errors'. $isactivitypage = optional_param('isactivitypage', false, PARAM_BOOL); // Is this a course module page? $modaction = optional_param('modaction', '', PARAM_ALPHAEXT); // An action as recorded in the logs. $page = optional_param('page', '0', PARAM_INT); // Which page to show. $perpage = optional_param('perpage', '100', PARAM_INT); // How many per page. $showcourses = optional_param('showcourses', false, PARAM_BOOL); // Whether to show courses if we're over our limit. $showusers = optional_param('showusers', false, PARAM_BOOL); // Whether to show users if we're over our limit. $chooselog = optional_param('chooselog', false, PARAM_BOOL); $logformat = optional_param('download', '', PARAM_ALPHA); $logreader = optional_param('logreader', '', PARAM_COMPONENT); // Reader which will be used for displaying logs. $edulevel = optional_param('edulevel', -1, PARAM_INT); // Educational level. $origin = optional_param('origin', '', PARAM_TEXT); // Event origin. $params = array(); if (!empty($id)) { $params['id'] = $id; } else { $id = $SITE->id; } if ($group !== 0) { $params['group'] = $group; } if ($user !== 0) { $params['user'] = $user; } if ($date !== 0) { $params['date'] = $date; } if ($modid !== 0) { $params['modid'] = $modid; } if ($modaction !== '') { $params['modaction'] = $modaction; } if ($page !== '0') { $params['page'] = $page; } if ($perpage !== '100') { $params['perpage'] = $perpage; } if ($showcourses) { $params['showcourses'] = $showcourses; } if ($showusers) { $params['showusers'] = $showusers; } if ($chooselog) { $params['chooselog'] = $chooselog; } if ($logformat !== '') { $params['download'] = $logformat; } if ($logreader !== '') { $params['logreader'] = $logreader; } if (($edulevel != -1)) { $params['edulevel'] = $edulevel; } if ($origin !== '') { $params['origin'] = $origin; } $url = new moodle_url("/report/log/index.php", $params); $PAGE->set_url('/report/log/index.php', array('id' => $id)); $PAGE->set_pagelayout('report'); $cminfo = null; if (!is_number($modid)) { $isactivitypage = false; } if ($isactivitypage) { $modinfo = get_fast_modinfo($id); $cminfo = $modinfo->cms[intval($modid)] ?? null; if ($cminfo === null) { throw new moodle_exception('invalidmoduleid', '', '', $modid); } } // Get course details. $sitecoursefilter = 0; if ($id != $SITE->id) { $course = $DB->get_record('course', ['id' => $id], '*'); if ($course) { require_login($course); $context = context_course::instance($course->id); if ($cminfo !== null) { $context = $cminfo->context; $PAGE->set_cm($cminfo); } } else { // Missing courses may have be deleted, so display them in site context. $sitecoursefilter = $id; } } if (empty($course)) { $course = $SITE; require_login(); $context = context_system::instance(); $PAGE->set_context($context); } require_capability('report/log:view', $context); // When user choose to view logs then only trigger event. if ($chooselog) { // Trigger a report viewed event. $event = \report_log\event\report_viewed::create(array('context' => $context, 'relateduserid' => $user, 'other' => array('groupid' => $group, 'date' => $date, 'modid' => $modid, 'modaction' => $modaction, 'logformat' => $logformat))); $event->trigger(); } if (!empty($page)) { $strlogs = get_string('logs'). ": ". get_string('page', 'report_log', $page + 1); } else { $strlogs = get_string('logs'); } $stradministration = get_string('administration'); $strreports = get_string('reports'); // Before we close session, make sure we have editing information in session. $adminediting = optional_param('adminedit', -1, PARAM_BOOL); if ($PAGE->user_allowed_editing() && $adminediting != -1) { $USER->editing = $adminediting; } if ($course->id == $SITE->id) { admin_externalpage_setup('reportlog', '', null, '', array('pagelayout' => 'report')); $PAGE->set_title($strlogs); $PAGE->set_primary_active_tab('siteadminnode'); } else { $contexttitle = $course->shortname . ': '; if ($cminfo !== null) { $contexttitle .= $cminfo->name . ' - '; } $PAGE->set_title($contexttitle . $strlogs); $PAGE->set_heading($course->fullname); } $output = $PAGE->get_renderer('report_log'); if (!report_helper::has_valid_group($context)) { echo $output->header(); echo $output->notification(get_string('notingroup')); echo $output->footer(); exit(); } $reportlog = new report_log_renderable( logreader: $logreader, course: $course, userid: $user, modid: $modid, action: $modaction, groupid: $group, edulevel: $edulevel, showcourses: $showcourses, showusers: $showusers, showreport: $chooselog, showselectorform: true, url: $url, date: $date, logformat: $logformat, page: $page, perpage: $perpage, order: 'timecreated DESC', origin: $origin, isactivitypage: $isactivitypage, sitecoursefilter: $sitecoursefilter, ); $readers = $reportlog->get_readers(); if (empty($readers)) { echo $output->header(); echo $output->heading(get_string('nologreaderenabled', 'report_log')); } else { if (!empty($chooselog)) { // Delay creation of table, till called by user with filter. $reportlog->setup_table(); if (empty($logformat)) { echo $output->header(); // Print selector dropdown. $pluginname = get_string('pluginname', 'report_log'); if (!$isactivitypage) { report_helper::print_report_selector($pluginname); } $userinfo = get_string('allparticipants'); $dateinfo = get_string('alldays'); if ($user) { $u = $DB->get_record('user', array('id' => $user, 'deleted' => 0), '*', MUST_EXIST); $userinfo = fullname($u, has_capability('moodle/site:viewfullnames', $context)); } if ($date) { $dateinfo = userdate($date, get_string('strftimedaydate')); } if (!empty($course) && ($course->id != SITEID)) { $PAGE->navbar->add("$userinfo, $dateinfo"); } echo $output->render($reportlog); } else { \core\session\manager::write_close(); $reportlog->download(); exit(); } } else { echo $output->header(); // Print selector dropdown. $pluginname = get_string('pluginname', 'report_log'); if (!$isactivitypage) { report_helper::print_report_selector($pluginname); } echo $output->heading(get_string('chooselogs') .':', 3); echo $output->render($reportlog); } } echo $output->footer(); UPGRADING.md000064400000000756152266352430006426 0ustar00# report_log Upgrade notes ## 5.0 ### Removed - Support for the $grouplist public member in the report_log_renderable class has been removed. For more information see [MDL-81155](https://tracker.moodle.org/browse/MDL-81155) ## 4.5 ### Added - The `\report_log_renderable::get_activities_list()` method return values now includes an array of disabled elements, in addition to the array of activities. For more information see [MDL-82146](https://tracker.moodle.org/browse/MDL-82146) graph.php000064400000002325152266352430006370 0ustar00. /** * Produces a graph of log accesses for a user * * Generates an image representing the log data in a graphical manner for a user. * * @package report_log * @deprecated since 3.2 * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require("../../config.php"); require_once($CFG->libdir . '/filelib.php'); debugging('This way of generating the chart is deprecated, refer to report_log_print_graph().', DEBUG_DEVELOPER); send_file_not_found(); tests/lib_test.php000064400000007632152266352430010244 0ustar00. /** * Tests for report library functions. * * @package report_log * @copyright 2014 onwards Ankit agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. */ namespace report_log; defined('MOODLE_INTERNAL') || die(); global $CFG; /** * Class report_log_events_testcase. * * @package report_log * @copyright 2014 onwards Ankit agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. */ final class lib_test extends \advanced_testcase { /** * @var stdClass The user. */ private $user; /** * @var stdClass The course. */ private $course; /** * @var \core_user\output\myprofile\tree The navigation tree. */ private $tree; public function setUp(): void { parent::setUp(); $this->user = $this->getDataGenerator()->create_user(); $this->course = $this->getDataGenerator()->create_course(); $this->tree = new \core_user\output\myprofile\tree(); $this->resetAfterTest(); } /** * Test report_log_supports_logstore. */ public function test_report_log_supports_logstore(): void { $logmanager = get_log_manager(); $allstores = \core_component::get_plugin_list_with_class('logstore', 'log\store'); $supportedstores = array( 'logstore_database' => '\logstore_database\log\store', 'logstore_standard' => '\logstore_standard\log\store' ); // Make sure all supported stores are installed. $expectedstores = array_keys(array_intersect($allstores, $supportedstores)); $stores = $logmanager->get_supported_logstores('report_log'); $stores = array_keys($stores); foreach ($expectedstores as $expectedstore) { $this->assertContains($expectedstore, $stores); } } /** * Tests the report_log_myprofile_navigation() function as an admin viewing the logs for a user. */ public function test_report_log_myprofile_navigation(): void { // Set as the admin. $this->setAdminUser(); $iscurrentuser = false; // Check the node tree is correct. report_log_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course); $reflector = new \ReflectionObject($this->tree); $nodes = $reflector->getProperty('nodes'); $this->assertArrayHasKey('alllogs', $nodes->getValue($this->tree)); $this->assertArrayHasKey('todayslogs', $nodes->getValue($this->tree)); } /** * Tests the report_log_myprofile_navigation() function as a user without permission. */ public function test_report_log_myprofile_navigation_without_permission(): void { // Set to the other user. $this->setUser($this->user); $iscurrentuser = true; // Check the node tree is correct. report_log_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course); $reflector = new \ReflectionObject($this->tree); $nodes = $reflector->getProperty('nodes'); $this->assertArrayNotHasKey('alllogs', $nodes->getValue($this->tree)); $this->assertArrayNotHasKey('todayslogs', $nodes->getValue($this->tree)); } } tests/event/events_test.php000064400000007355152266352430012125 0ustar00. /** * Tests for report log events. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. */ namespace report_log\event; /** * Class report_log_events_testcase * * Class for tests related to log events. * * @package report_log * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. */ final class events_test extends \advanced_testcase { /** * Setup testcase. */ public function setUp(): void { parent::setUp(); $this->setAdminUser(); $this->resetAfterTest(); } /** * Test the report viewed event. * * It's not possible to use the moodle API to simulate the viewing of log report, so here we * simply create the event and trigger it. */ public function test_report_viewed(): void { $course = $this->getDataGenerator()->create_course(); $context = \context_course::instance($course->id); // Trigger event for log report viewed. $event = \report_log\event\report_viewed::create(array('context' => $context, 'relateduserid' => 0, 'other' => array('groupid' => 0, 'date' => 0, 'modid' => 0, 'modaction' => '', 'logformat' => 'showashtml'))); // Trigger and capture the event. $sink = $this->redirectEvents(); $event->trigger(); $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\report_log\event\report_viewed', $event); $this->assertEquals($context, $event->get_context()); $this->assertEventContextNotUsed($event); $url = new \moodle_url('/report/log/index.php', array('id' => $event->courseid)); $this->assertEquals($url, $event->get_url()); } /** * Test the user report viewed event. * * It's not possible to use the moodle API to simulate the viewing of user log report, so here we * simply create the event and trigger it. */ public function test_user_report_viewed(): void { $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $context = \context_course::instance($course->id); // Trigger event for user report viewed. $event = \report_log\event\user_report_viewed::create(array('context' => $context, 'relateduserid' => $user->id, 'other' => array('mode' => 'today'))); // Trigger and capture the event. $sink = $this->redirectEvents(); $event->trigger(); $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\report_log\event\user_report_viewed', $event); $this->assertEquals($context, $event->get_context()); $this->assertEventContextNotUsed($event); $url = new \moodle_url('/report/log/user.php', array('course' => $course->id, 'id' => $user->id, 'mode' => 'today')); $this->assertEquals($url, $event->get_url()); } } tests/behat/group_report_log.feature000064400000010505152266352430013747 0ustar00@report @report_log Feature: In a course with group mode, I can view the group report log page or not depending on the group I am in. Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course separate group | C1 | 0 | 1 | | Course visible group | C2 | 0 | 2 | | Course no group | C3 | 0 | 0 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | | teacher1 | Teacher | 1 | teacher1@example.com | | teacher2 | Teacher | 2 | teacher2@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | teacher | | teacher2 | C1 | teacher | | student1 | C1 | student | | student2 | C1 | student | | teacher1 | C2 | teacher | | teacher2 | C2 | teacher | | student1 | C2 | student | | student2 | C2 | student | | teacher1 | C3 | teacher | | teacher2 | C3 | teacher | | student1 | C3 | student | | student2 | C3 | student | And the following "groups" exist: | course | name | idnumber | | C1 | Group C1.1 | group11 | | C2 | Group C2.1 | group21 | | C3 | Group C3.1 | group31 | And the following "group members" exist: | group | user | | group11 | student1 | | group11 | teacher1 | | group21 | student1 | | group21 | teacher1 | | group31 | student1 | | group31 | teacher1 | And the following "activities" exist: | activity | name | intro | course | idnumber | | page | Page11 | Page11 | C1 | page1 | | page | Page21 | Page21 | C2 | page1 | | page | Page31 | Page31 | C3 | page1 | # Generate logs for the pages. And I am on the "Page11" "page activity" page logged in as student1 And I am on "Course separate group" course homepage And I log out And I am on the "Page11" "page activity" page logged in as student2 And I am on "Course separate group" course homepage And I log out And I am on the "Page21" "page activity" page logged in as student1 And I am on "Course visible group" course homepage And I log out And I am on the "Page21" "page activity" page logged in as student2 And I am on "Course visible group" course homepage And I log out And I am on the "Page31" "page activity" page logged in as student1 And I am on "Course no group" course homepage And I log out And I am on the "Page31" "page activity" page logged in as student2 And I am on "Course no group" course homepage And I log out Scenario Outline: As a user in a course, I can view a link to the report logs if I am in the right group. Given I log in as "" And I am on "" course homepage When I navigate to "Reports" in current page administration And "Logs" "link" And I log out Examples: | course | user | shouldexist | | Course separate group | teacher1 | should exist | | Course separate group | teacher2 | should not exist | | Course visible group | teacher1 | should exist | | Course visible group | teacher2 | should exist | | Course no group | teacher1 | should exist | | Course no group | teacher2 | should exist | Scenario Outline: As a non editing teacher not in a group, I can not view the report logs. Given I log in as "" When I am on the "" "report_log > Logs" page Then I "you need to be part of a group to see this page." Examples: | course | user | shouldsee | | Course separate group | teacher1 | should not see | | Course separate group | teacher2 | should see | | Course visible group | teacher1 | should not see | | Course visible group | teacher2 | should not see | | Course no group | teacher1 | should not see | | Course no group | teacher2 | should not see | tests/behat/course_report_log.feature000064400000002033152266352430014110 0ustar00@report @report_log Feature: In a course administration page, navigate through report page, test for report log page In order to navigate through report page As an admin Go to course administration -> reports Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | | student1 | C1 | student | @javascript Scenario: Report selector should be available in the report log page Given I log in as "admin" And I am on "Course 1" course homepage When I navigate to "Reports" in current page administration And I click on "Logs" "link" Then "Report" "field" should exist in the "tertiary-navigation" "region" And I should see "Logs" in the "tertiary-navigation" "region" tests/behat/subsection_reports.feature000064400000003212152266352430014310 0ustar00@report @report_log @mod_subsection Feature: Subsection behavior in Log report. Background: Given the following "courses" exist: | fullname | shortname | category | numsections | initsections | | Course 1 | C1 | 0 | 2 | 1 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | | student1 | C1 | student | And the following "activities" exist: | activity | name | course | idnumber | section | | subsection | Subsection 1 | C1 | subsection1 | 1 | | page | Page 1 | C1 | page1 | 3 | @javascript Scenario: Visible subsections should be available in the activities selector Given I log in as "admin" And I am on "Course 1" course homepage When I navigate to "Reports" in current page administration And I click on "Logs" "link" Then the "Activities" select box should contain "Subsection 1" And the "Activities" select box should contain "Page 1" @javascript Scenario: Hidden subsections should be available in the activities selector Given I log in as "admin" And I am on "Course 1" course homepage with editing mode on And I hide section "Subsection 1" When I navigate to "Reports" in current page administration And I click on "Logs" "link" Then the "Activities" select box should not contain "Subsection 1" And the "Activities" select box should not contain "Page 1" tests/behat/behat_report_log.php000064400000004066152266352430013037 0ustar00. require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); /** * Step definition for report_log behat tests. * * @package report_log * @category test * @copyright 2025 Laurent David * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_report_log extends behat_base { /** * Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'. * * Recognised page names are: * | pagetype | name meaning | description | * | logs | Course name | The course report logs page | * * @param string $page identifies which type of page this is, e.g. 'Logs'. * @param string $identifier identifies the particular page, e.g. 'C1'. * @return moodle_url the corresponding URL. * @throws Exception with a meaningful error message if the specified page cannot be found. */ protected function resolve_page_instance_url(string $page, string $identifier): moodle_url { switch (strtolower($page)) { case 'logs': $courseid = $this->get_course_id($identifier); return new moodle_url('/report/log/index.php', [ 'id' => $courseid, ]); default: throw new Exception("Unrecognised page type '{$page}'"); } } } tests/behat/filter_log.feature000064400000003277152266352430012515 0ustar00@report @report_log Feature: In a report, admin can filter log data In order to filter log data As an admin I need to log in with different user and go to log and apply filter Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic | | teacher1 | Teacher | One | teacher1@example.com | t1 | | fred | | | | student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | | student1 | C1 | student | And the following config values are set as admin: | fullnamedisplay | firstname | | alternativefullnameformat | middlename, alternatename, firstname, lastname | And I log in as "admin" Scenario: Filter log report for standard log reader Given I am on "Course 1" course homepage And I navigate to course participants And I follow "Ann, Jill, Grainne, Beauchamp" And I click on "Log in as" "link" And I press "Continue" And I log out And I log in as "admin" When I navigate to "Reports > Logs" in site administration And I set the field "id" to "Acceptance test site (Site)" And I set the field "user" to "All participants" And I press "Get these logs" Then I should see "User logged in as another user" tests/behat/filter_log_actions.feature000064400000007024152266352430014227 0ustar00@report @report_log Feature: In a report, admin can filter log data by action In order to filter log data by action As an admin I need to view the logs and apply a filter Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment 1 | | intro | Offline text | | assignsubmission_file_enabled | 0 | | section | 1 | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on # View Action. And I follow "Test assignment 1" # Update Action. And I navigate to "Settings" in current page administration And I press "Save and return to course" # Delete Action. And I delete "Test assignment 1" activity And I log out And I trigger cron Scenario: View only create actions. Given I log in as "admin" When I navigate to "Reports > Logs" in site administration And I set the field "menumodaction" to "Create" And I press "Get these logs" And I should not see "Course module updated" And I should not see "The status of the submission has been viewed." And I should not see "Course module deleted" Scenario: View only update actions. Given I log in as "admin" When I navigate to "Reports > Logs" in site administration And I set the field "menumodaction" to "Update" And I press "Get these logs" Then I should see "Course module updated" And I should not see "Course module created" And I should not see "The status of the submission has been viewed." And I should not see "Course module deleted" Scenario: View only view actions. Given I log in as "admin" When I navigate to "Reports > Logs" in site administration And I set the field "menumodaction" to "View" And I press "Get these logs" Then I should see "The status of the submission has been viewed." And I should not see "Course module created" And I should not see "Course module updated" And I should not see "Course module deleted" Scenario: View only delete actions. Given I log in as "admin" When I navigate to "Reports > Logs" in site administration And I set the field "menumodaction" to "Delete" And I press "Get these logs" Then I should see "Course module deleted" And I should not see "Course module created" And I should not see "Course module updated" And I should not see "The status of the submission has been viewed." Scenario: View only changes. Given I log in as "admin" When I navigate to "Reports > Logs" in site administration And I set the field "menumodaction" to "All changes" And I press "Get these logs" Then I should see "Course module deleted" And I should see "Course module updated" And I should not see "The status of the submission has been viewed." Scenario: View all actions. Given I log in as "admin" When I navigate to "Reports > Logs" in site administration And I set the field "menumodaction" to "All actions" And I press "Get these logs" Then I should see "Course module deleted" And I should see "Course module updated" And I should see "The status of the submission has been viewed." tests/behat/user_log.feature000064400000007066152266352430012206 0ustar00@report @report_log Feature: User can view activity log. In order to view user log As an teacher I need to view user today's and all report Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic | | teacher1 | Teacher | One | teacher1@example.com | t1 | | fred | | | | student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "activity" exists: | activity | assign | | course | C1 | | idnumber | 0001 | | name | Test assignment name | | intro | Submit your online text | | section | 1 | | assignsubmission_onlinetext_enabled | 1 | | assignsubmission_file_enabled | 0 | And the following config values are set as admin: | fullnamedisplay | firstname | | alternativefullnameformat | middlename, alternatename, firstname, lastname | And I log in as "student1" And I am on "Course 1" course homepage And I follow "Test assignment name" When I press "Add submission" And I set the following fields to these values: | Online text | I'm the student first submission | And I press "Save changes" And I log out Scenario: View Todays' and all log report for user Given I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to course participants And I follow "Ann, Jill, Grainne, Beauchamp" When I follow "Today's logs" And I should see "Assignment: Test assignment name" And I follow "Ann, Jill, Grainne, Beauchamp" And I follow "All logs" Then I should see "Assignment: Test assignment name" Scenario: No log reader enabled should be visible when no log store enabled. Given I log in as "admin" And I navigate to "Plugins > Logging > Manage log stores" in site administration And I click on "Disable" "link" in the "Standard log" "table_row" And I log out And I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to course participants And I follow "Ann, Jill, Grainne, Beauchamp" When I follow "Today's logs" And I should see "No log reader enabled" And I am on "Course 1" course homepage And I navigate to course participants And I follow "Ann, Jill, Grainne, Beauchamp" And I follow "All logs" Then I should see "No log reader enabled" Scenario: View Todays' log report for user through Course log report Given I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Reports" in current page administration And I click on "Logs" "link" And I set the field with xpath "//select[@name='user']" to "Ann, Jill, Grainne, Beauchamp" When I click on "Get these logs" "button" Then I should see "Ann, Jill, Grainne, Beauchamp" tests/behat/activity_report_log.feature000064400000005202152266352430014445 0ustar00@report @report_log Feature: In a activity page, navigate through the More / Logs menu, test for report log page In order to navigate through report page As an admin Go to the activity page, click on More / Logs menu, and check for the report log page Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "activities" exist: | activity | name | course | section | | page | Test page 1 | C1 | 1 | | page | Test page 2 | C1 | 1 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And I log in as "student1" And I am on "Course 1" course homepage And I follow "Test page 1" And I am on "Course 1" course homepage And I follow "Test page 2" And I log out And I log in as "student2" And I am on "Course 1" course homepage And I follow "Test page 1" And I log out Scenario: Report selectors should be targeted toward course module Given I am on the "Test page 1" Activity page logged in as "admin" And I navigate to "Logs" in current page administration And "menuid" "select" should not exist And "modid" "select" should not exist And I should see "All participants" in the "user" "select" And I should see "All days" in the "date" "select" And I should see "All sources" in the "origin" "select" And I should see "All events" in the "edulevel" "select" And I should see "Test page 1" in the "#page-header" "css_element" And I should see "Student 1" in the "user" "select" When I set the field "user" to "Student 1" And I click on "Get these logs" "button" Then I should see "Test page 1" in the "#page-header" "css_element" And I should not see "Student 2" in the "table.reportlog" "css_element" And I should see "Page: Test page 1" in the "table.reportlog" "css_element" Scenario: Report submission stays in the same course module page Given I am on the "Test page 1" Activity page logged in as "admin" When I navigate to "Logs" in current page administration And I click on "Get these logs" "button" Then I should see "Test page 1" in the "#page-header" "css_element" tests/renderable_test.php000064400000044720152266352430011600 0ustar00. namespace report_log; use context_course; use core_user; /** * Class report_log\renderable_test to cover functions in \report_log_renderable. * * @package report_log * @copyright 2023 Stephan Robotta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. */ final class renderable_test extends \advanced_testcase { /** * @var int The course with separate groups. */ const COURSE_SEPARATE_GROUP = 0; /** * @var int The course with separate groups. */ const COURSE_VISIBLE_GROUP = 1; /** * @var int The course with separate groups. */ const COURSE_NO_GROUP = 2; /** * @var array The setup of users. */ const SETUP_USER_DEFS = [ // Make student2 also member of group1. 'student' => [ 'student0' => ['group0'], 'student1' => ['group1'], 'student2' => ['group0', 'group1'], 'student3' => [], ], // Make teacher2 also member of group1. 'teacher' => [ 'teacher0' => ['group0'], 'teacher1' => ['group1'], 'teacher2' => ['group0', 'group1'], 'teacher3' => [], ], // Make editingteacher also member of group1. 'editingteacher' => [ 'editingteacher0' => ['group0'], 'editingteacher1' => ['group1'], 'editingteacher2' => ['group0', 'group1'], ], ]; /** * @var array|\stdClass all users indexed by username. */ private $users = []; /** * @var array The groups by courses (array of array). */ private $groupsbycourse = []; /** * @var array The courses. */ private $courses; /** * Get the data provider for test_get_user_list(). * * @return array */ public static function get_user_visibility_list_provider(): array { return [ 'separategroups: student 0' => [ self::COURSE_SEPARATE_GROUP, 'student0', // All users in group 0. [ 'student0', 'student2', 'teacher0', 'teacher2', 'editingteacher0', 'editingteacher2', ], ], 'separategroups: student 1' => [ self::COURSE_SEPARATE_GROUP, 'student1', // All users in group1. [ 'student1', 'student2', 'teacher1', 'teacher2', 'editingteacher1', 'editingteacher2', ], ], 'separategroups: student 2' => [ self::COURSE_SEPARATE_GROUP, 'student2', // All users in group0 and group1. [ 'student0', 'student1', 'student2', 'teacher0', 'teacher1', 'teacher2', 'editingteacher0', 'editingteacher1', 'editingteacher2', ], ], 'separategroups: student3' => [ self::COURSE_SEPARATE_GROUP, 'student3', // Student 3 is not in any group so should only see user without a group. [ 'student3', 'teacher3', ], ], 'separategroups: editing teacher 0' => [ self::COURSE_SEPARATE_GROUP, 'editingteacher0', // All users including student 3 as we can see all users (event the one not in a group). [ 'student0', 'student1', 'student2', 'student3', 'teacher0', 'teacher1', 'teacher2', 'teacher3', 'editingteacher0', 'editingteacher1', 'editingteacher2', ], ], 'separategroups: teacher 0' => [ self::COURSE_SEPARATE_GROUP, 'teacher0', // All users in group 0. [ 'student0', 'student2', 'teacher0', 'teacher2', 'editingteacher0', 'editingteacher2', ], ], 'separategroups: teacher 2' => [ self::COURSE_SEPARATE_GROUP, 'teacher2', // All users in group0 and group1. [ 'student0', 'student1', 'student2', 'teacher0', 'teacher1', 'teacher2', 'editingteacher0', 'editingteacher1', 'editingteacher2', ], ], 'separategroups: teacher 3' => [ self::COURSE_SEPARATE_GROUP, 'teacher3', // Teacher 3 is not in any group so should only see user without a group. [ 'student3', 'teacher3', ], ], 'separategroups: teacher 2 with group set to group 1' => [ self::COURSE_SEPARATE_GROUP, 'teacher2', // All users in group1. [ 'student1', 'student2', 'teacher1', 'teacher2', 'editingteacher1', 'editingteacher2', ], 'group1', ], 'visiblegroup: editing teacher' => [ self::COURSE_VISIBLE_GROUP, 'editingteacher0', // All users. [ 'student0', 'student1', 'student2', 'student3', 'teacher0', 'teacher1', 'teacher2', 'teacher3', 'editingteacher0', 'editingteacher1', 'editingteacher2', ], ], 'visiblegroup: student' => [ self::COURSE_VISIBLE_GROUP, 'student0', // All users. [ 'student0', 'student1', 'student2', 'student3', 'teacher0', 'teacher1', 'teacher2', 'teacher3', 'editingteacher0', 'editingteacher1', 'editingteacher2', ], ], 'visiblegroup: teacher 0' => [ self::COURSE_VISIBLE_GROUP, 'teacher2', // All users. [ 'student0', 'student1', 'student2', 'student3', 'teacher0', 'teacher1', 'teacher2', 'teacher3', 'editingteacher0', 'editingteacher1', 'editingteacher2', ], ], 'visiblegroup: editing teacher 0' => [ self::COURSE_VISIBLE_GROUP, 'editingteacher0', // All users. [ 'student0', 'student1', 'student2', 'student3', 'teacher0', 'teacher1', 'teacher2', 'teacher3', 'editingteacher0', 'editingteacher1', 'editingteacher2', ], ], 'visiblegroup: teacher 2 with group set to group 1' => [ self::COURSE_VISIBLE_GROUP, 'teacher2', // All users in group1. [ 'student1', 'student2', 'teacher1', 'teacher2', 'editingteacher1', 'editingteacher2', ], 'group1', ], ]; } /** * Data provider for test_get_group_list(). * * @return array */ public static function get_group_list_provider(): array { return [ // The student sees his own group only. 'separategroup: student in one group' => [self::COURSE_SEPARATE_GROUP, 'student0', 1], 'separategroup: student in two groups' => [self::COURSE_SEPARATE_GROUP, 'student2', 2], // While the teacher is not allowed to see all groups. 'separategroup: teacher in one group' => [self::COURSE_SEPARATE_GROUP, 'teacher0', 1], 'separategroup: teacher in two groups' => [self::COURSE_SEPARATE_GROUP, 'teacher2', 2], // But editing teacher should see all. 'separategroup: editingteacher' => [self::COURSE_SEPARATE_GROUP, 'editingteacher0', 2], // The student sees all groups. 'visiblegroup: student in one group' => [self::COURSE_VISIBLE_GROUP, 'student0', 2], // Same for teacher. 'visiblegroup: teacher in one group' => [self::COURSE_VISIBLE_GROUP, 'teacher0', 2], // And editing teacher. 'visiblegroup: editingteacher' => [self::COURSE_VISIBLE_GROUP, 'editingteacher0', 2], // No group. 'nogroups: student in one group' => [self::COURSE_NO_GROUP, 'student0', 0], // Same for teacher. 'nogroups: teacher in one group' => [self::COURSE_NO_GROUP, 'teacher0', 0], // And editing teacher. 'nogroups: editingteacher' => [self::COURSE_NO_GROUP, 'editingteacher0', 0], ]; } /** * Set up a course with two groups, three students being each in one of the groups, * two teachers each in either group while the second teacher is also member of the other group. * * @return void * @throws \coding_exception */ public function setUp(): void { parent::setUp(); $this->resetAfterTest(); $this->courses[self::COURSE_SEPARATE_GROUP] = $this->getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS]); $this->courses[self::COURSE_VISIBLE_GROUP] = $this->getDataGenerator()->create_course(['groupmode' => VISIBLEGROUPS]); $this->courses[self::COURSE_NO_GROUP] = $this->getDataGenerator()->create_course(); foreach ($this->courses as $coursetype => $course) { if ($coursetype == self::COURSE_NO_GROUP) { continue; } $this->groupsbycourse[$coursetype] = []; $this->groupsbycourse[$coursetype]['group0'] = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'group0']); $this->groupsbycourse[$coursetype]['group1'] = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'group1']); } foreach (self::SETUP_USER_DEFS as $role => $userdefs) { foreach ($userdefs as $username => $groups) { $user = $this->getDataGenerator()->create_user( [ 'username' => $username, 'firstname' => "FN{$role}{$username}", 'lastname' => "LN{$role}{$username}", ]); foreach ($this->courses as $coursetype => $course) { $this->getDataGenerator()->enrol_user($user->id, $course->id, $role); foreach ($groups as $groupname) { if ($coursetype == self::COURSE_NO_GROUP) { continue; } $this->getDataGenerator()->create_group_member([ 'groupid' => $this->groupsbycourse[$coursetype][$groupname]->id, 'userid' => $user->id, ]); } } $this->users[$username] = $user; } } } /** * Test report_log_renderable::get_user_list(). * * @param int $courseindex * @param string $username * @param array $expectedusers * @param string|null $groupname * @covers \report_log_renderable::get_user_list * @dataProvider get_user_visibility_list_provider * @return void */ public function test_get_user_list(int $courseindex, string $username, array $expectedusers, ?string $groupname = null): void { global $PAGE, $CFG; $currentcourse = $this->courses[$courseindex]; $PAGE->set_url('/report/log/index.php?id=' . $currentcourse->id); // Fetch all users of group 1 and the guest user. $currentuser = $this->users[$username]; $this->setUser($currentuser->id); $groupid = 0; if ($groupname) { $groupid = $this->groupsbycourse[$courseindex][$groupname]->id; } $renderable = new \report_log_renderable( "", (int) $currentcourse->id, $currentuser->id, 0, '', $groupid); $userlist = $renderable->get_user_list(); unset($userlist[$CFG->siteguest]); // We ignore guest. $usersid = array_keys($userlist); $users = array_map(function($userid) { return core_user::get_user($userid); }, $usersid); // Now check that the users are the expected ones. asort($expectedusers); $userlistbyname = array_column($users, 'username'); asort($userlistbyname); $this->assertEquals(array_values($expectedusers), array_values($userlistbyname)); // Check that users are in order lastname > firstname > id. $sortedusers = $users; // Sort user by lastname > firstname > id. usort($sortedusers, function($a, $b) { if ($a->lastname != $b->lastname) { return $a->lastname <=> $b->lastname; } if ($a->firstname != $b->firstname) { return $a->firstname <=> $b->firstname; } return $a->id <=> $b->id; }); $sortedusernames = array_column($sortedusers, 'username'); $userlistbyname = array_column($users, 'username'); $this->assertEquals($sortedusernames, $userlistbyname); } /** * Test report_log_renderable::get_group_list(). * * @covers \report_log_renderable::get_group_list * @dataProvider get_group_list_provider * @return void */ public function test_get_group_list($courseindex, $username, $expectedcount): void { global $PAGE; $PAGE->set_url('/report/log/index.php?id=' . $this->courses[$courseindex]->id); $this->setUser($this->users[$username]->id); $renderable = new \report_log_renderable("", (int) $this->courses[$courseindex]->id, $this->users[$username]->id); $groups = $renderable->get_group_list(); $this->assertCount($expectedcount, $groups); } /** * Test table_log * * @param int $courseindex * @param string $username * @param array $expectedusers * @param string|null $groupname * @covers \report_log_renderable::get_user_list * @dataProvider get_user_visibility_list_provider * @return void */ public function test_get_table_logs(int $courseindex, string $username, array $expectedusers, ?string $groupname = null): void { global $DB, $PAGE; $this->preventResetByRollback(); // This is to ensure that we can actually trigger event and record them in the log store. // Configure log store. set_config('enabled_stores', 'logstore_standard', 'tool_log'); $manager = get_log_manager(true); $DB->delete_records('logstore_standard_log'); foreach ($this->courses as $course) { foreach ($this->users as $user) { $eventdata = [ 'context' => context_course::instance($course->id), 'userid' => $user->id, ]; $event = \core\event\course_viewed::create($eventdata); $event->trigger(); } } $stores = $manager->get_readers(); $store = $stores['logstore_standard']; // Build the report. $currentuser = $this->users[$username]; $this->setUser($currentuser->id); $groupid = 0; if ($groupname) { $groupid = $this->groupsbycourse[$courseindex][$groupname]->id; } $PAGE->set_url('/report/log/index.php?id=' . $this->courses[$courseindex]->id); $renderable = new \report_log_renderable("", (int) $this->courses[$courseindex]->id, 0, 0, '', $groupid); $renderable->setup_table(); $table = $renderable->tablelog; $store->flush(); $table->query_db(100); $usernames = []; foreach ($table->rawdata as $event) { if (get_class($event) !== \core\event\course_viewed::class) { continue; } $user = core_user::get_user($event->userid, '*', MUST_EXIST); $usernames[] = $user->username; } sort($expectedusers); sort($usernames); $this->assertEquals($expectedusers, $usernames); } /** * Test getting logs for deleted courses. * * @covers \report_log_renderable::setup_table * @return void */ public function test_get_deleted_course_logs(): void { global $DB, $PAGE; $this->preventResetByRollback(); // Ensure events can be recorded in log store. // Configure log store and user. set_config('enabled_stores', 'logstore_standard', 'tool_log'); $manager = get_log_manager(true); $stores = $manager->get_readers(); $store = $stores['logstore_standard']; $this->setUser(get_admin()); // Set and delete course. $course = reset($this->courses); $deletedcourseid = $course->id; delete_course($course, false); $this->assertFalse($DB->record_exists('course', ['id' => $deletedcourseid])); // Test rendering. $PAGE->set_url('/report/log/index.php?id=' . $deletedcourseid); $renderable = new \report_log_renderable('', (int) $deletedcourseid); $renderable->setup_table(); $store->flush(); $table = $renderable->tablelog; $table->query_db(100); // Confirm we have logs for the course deletion and that the filtering is correct. $this->assertNotEmpty($table->totalrows); $expectedrows = count($DB->get_records('logstore_standard_log', ['courseid' => $deletedcourseid])); $this->assertEquals($expectedrows, $table->totalrows); } } user.php000064400000012402152266352430006242 0ustar00. /** * Display user activity reports for a course (totals) * * @package report_log * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require('../../config.php'); require_once($CFG->dirroot.'/report/log/locallib.php'); require_once($CFG->dirroot.'/lib/tablelib.php'); $userid = required_param('id', PARAM_INT); $courseid = required_param('course', PARAM_INT); $mode = optional_param('mode', 'today', PARAM_ALPHA); $page = optional_param('page', 0, PARAM_INT); $perpage = optional_param('perpage', 100, PARAM_INT); $logreader = optional_param('logreader', '', PARAM_COMPONENT); // Log reader which will be used for displaying logs. if ($mode !== 'today' and $mode !== 'all') { $mode = 'today'; } $user = $DB->get_record('user', array('id' => $userid, 'deleted' => 0), '*', MUST_EXIST); $course = $DB->get_record('course', ['id' => $courseid], '*'); $sitecoursefilter = 0; if (!$course) { // Missing courses may have be deleted, so display them in site context. $course = $SITE; $sitecoursefilter = $courseid; } $coursecontext = context_course::instance($course->id); $personalcontext = context_user::instance($user->id); if ($course->id == SITEID) { $PAGE->set_context($personalcontext); } if ($USER->id != $user->id and has_capability('moodle/user:viewuseractivitiesreport', $personalcontext) and !is_enrolled($coursecontext, $USER) and is_enrolled($coursecontext, $user)) { //TODO: do not require parents to be enrolled in courses - this is a hack! require_login(); $PAGE->set_course($course); } else { require_login($course); } list($all, $today) = report_log_can_access_user_report($user, $course); if (!$today && !$all) { throw new \moodle_exception('nocapability', 'report_log'); } if ($mode === 'today') { if (!$today) { require_capability('report/log:viewtoday', $coursecontext); } } else { if (!$all) { require_capability('report/log:view', $coursecontext); } } $stractivityreport = get_string('activityreport'); $PAGE->set_pagelayout('report'); $PAGE->set_url('/report/log/user.php', ['id' => $user->id, 'course' => $courseid, 'mode' => $mode]); $PAGE->navigation->extend_for_user($user); $PAGE->navigation->set_userid_for_parent_checks($user->id); // see MDL-25805 for reasons and for full commit reference for reversal when fixed. $PAGE->set_title("$course->shortname: $stractivityreport"); // Create the appropriate breadcrumb. $navigationnode = array( 'url' => new moodle_url('/report/log/user.php', ['id' => $user->id, 'course' => $courseid, 'mode' => $mode]), ); if ($mode === 'today') { $navigationnode['name'] = get_string('todaylogs'); } else { $navigationnode['name'] = get_string('alllogs'); } $PAGE->add_report_nodes($user->id, $navigationnode); if ($course->id == SITEID) { $PAGE->set_heading(fullname($user, has_capability('moodle/site:viewfullnames', $PAGE->context))); } else { $PAGE->set_heading($course->fullname); } // Trigger a user logs viewed event. $event = \report_log\event\user_report_viewed::create(array('context' => $coursecontext, 'relateduserid' => $userid, 'other' => array('mode' => $mode))); $event->trigger(); echo $OUTPUT->header(); if ($course->id != SITEID) { $userheading = array( 'heading' => fullname($user, has_capability('moodle/site:viewfullnames', $PAGE->context)), 'user' => $user, 'usercontext' => $personalcontext, ); echo $OUTPUT->context_header($userheading, 2); if ($mode === 'today') { echo $OUTPUT->heading(get_string('todaylogs', 'moodle'), 2, 'main mt-4 mb-4'); } else { echo $OUTPUT->heading(get_string('alllogs', 'moodle'), 2, 'main mt-4 mb-4'); } } // Time to filter records from. if ($mode === 'today') { $timefrom = usergetmidnight(time()); } else { $timefrom = 0; } $output = $PAGE->get_renderer('report_log'); $reportlog = new report_log_renderable($logreader, $course, $user->id, 0, '', -1, -1, false, false, true, false, $PAGE->url, $timefrom, '', $page, $perpage, 'timecreated DESC', '' , $sitecoursefilter); // Setup table if log reader is enabled. if (!empty($reportlog->selectedlogreader)) { $reportlog->setup_table(); $reportlog->tablelog->is_downloadable(false); } echo $output->reader_selector($reportlog); // Print the graphic chart accordingly to the mode (all, today). echo '
'; report_log_print_graph($course, $user, $mode, 0, $logreader, $sitecoursefilter); echo '
'; echo $output->render($reportlog); echo $OUTPUT->footer(); settings.php000064400000002270152266352430007126 0ustar00. /** * Links and settings * * Contains settings used by logs report. * * @package report_log * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; // Just a link to course report. $ADMIN->add('reports', new admin_externalpage('reportlog', get_string('log', 'admin'), $CFG->wwwroot . "/report/log/index.php?id=0", 'report/log:view')); // No report settings. $settings = null;