ÿØÿà 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-Ї Ç PK}\Ǵ}n styles.cssnu[#page-report-outline-user table tr td { padding: 10px 3px; min-width: 20px; } #page-report-outline-user .subsection .icon { padding-left: 10px; } PK}\idb/install.phpnu[. /** * Post installation and migration code. * * @package report * @subpackage outline * @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; function xmldb_report_outline_install() { global $DB; } PK}\_(ի db/access.phpnu[. /** * Capabilities * * @package report_outline * @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/outline:view' => array( 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'coursereport/outline:view', ), 'report/outline:viewuserreport' => array( 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'report/outline:view', ) ); PK}\LHΨlang/en/report_outline.phpnu[. /** * Strings * * @package report * @subpackage outline * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['eventactivityreportviewed'] = 'Activity report viewed'; $string['eventoutlinereportviewed'] = 'Outline report viewed'; $string['neverseen'] = 'Never seen'; $string['nocapability'] = 'Can not access user outline report'; $string['nologreaderenabled'] = 'No log reader enabled'; $string['numviews'] = '{$a->numviews} views by {$a->distinctusers} users'; $string['outline:view'] = 'View activity report'; $string['outline:viewuserreport'] = 'View user activity report'; $string['page-report-outline-x'] = 'Any outline report'; $string['page-report-outline-index'] = 'Course outline report'; $string['page-report-outline-user'] = 'User course outline report'; $string['pluginname'] = 'Activity report'; $string['privacy:metadata'] = 'The Activity report plugin does not store any personal data.'; PK}\+VZlib.phpnu[. /** * This file contains public API of outline report * * @package report * @subpackage outline * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; /** * This function extends the course 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_outline_extend_navigation_course($navigation, $course, $context) { if (has_capability('report/outline:view', $context)) { $url = new moodle_url('/report/outline/index.php', array('id'=>$course->id)); $navigation->add(get_string('pluginname', 'report_outline'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', '')); } } /** * 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_outline_extend_navigation_user($navigation, $user, $course) { if (report_outline_can_access_user_report($user, $course)) { $url = new moodle_url('/report/outline/user.php', array('id'=>$user->id, 'course'=>$course->id, 'mode'=>'outline')); $navigation->add(get_string('outlinereport'), $url); $url = new moodle_url('/report/outline/user.php', array('id'=>$user->id, 'course'=>$course->id, 'mode'=>'complete')); $navigation->add(get_string('completereport'), $url); } } /** * Is current user allowed to access this report * * @private defined in lib.php for performance reasons * * @param stdClass $user * @param stdClass $course * @return bool */ function report_outline_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 true; } } else if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext)) { if ($course->showreports and (is_viewing($coursecontext, $user) or is_enrolled($coursecontext, $user))) { return 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 false; } if (has_capability('report/outline:viewuserreport', $coursecontext)) { return true; } return false; } /** * 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 */ function report_outline_page_type_list($pagetype, $parentcontext, $currentcontext) { $array = array( '*' => get_string('page-x', 'pagetype'), 'report-*' => get_string('page-report-x', 'pagetype'), 'report-outline-*' => get_string('page-report-outline-x', 'report_outline'), 'report-outline-index' => get_string('page-report-outline-index', 'report_outline'), 'report-outline-user' => get_string('page-report-outline-user', 'report_outline') ); return $array; } /** * 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_outline_supports_logstore($instance) { if ($instance instanceof \core\log\sql_internal_table_reader) { return true; } return false; } /** * 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_outline_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(); } if (report_outline_can_access_user_report($user, $course)) { $url = new moodle_url('/report/outline/user.php', array('id' => $user->id, 'course' => $course->id, 'mode' => 'outline')); $node = new core_user\output\myprofile\node('reports', 'outline', get_string('outlinereport'), null, $url); $tree->add_node($node); $url = new moodle_url('/report/outline/user.php', array('id' => $user->id, 'course' => $course->id, 'mode' => 'complete')); $node = new core_user\output\myprofile\node('reports', 'complete', get_string('completereport'), null, $url); $tree->add_node($node); } } PK}\䝲 version.phpnu[. /** * Version info * * @package report * @subpackage outline * @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_outline'; // Full name of the plugin (used for diagnostics) PK}\. /** * Form to filter the outline report * * @package report_outline * @copyright 2017 Davo Smith, Synergy Learning * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_outline; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->libdir.'/formslib.php'); /** * Class filter_form form to filter the results by date * @package report_outline */ class filter_form extends \moodleform { /** * Form definition * @throws \HTML_QuickForm_Error * @throws \coding_exception */ protected function definition() { $mform = $this->_form; $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); $mform->addElement('header', 'filterheader', get_string('filter')); $opts = ['optional' => true]; $mform->addElement('date_selector', 'filterstartdate', get_string('fromdate'), $opts); $mform->addElement('date_selector', 'filterenddate', get_string('todate'), $opts); $mform->setExpanded('filterheader', false); // Add the filter/cancel buttons (without 'closeHeaderBefore', so they collapse with the filter). $buttonarray = [ $mform->createElement('submit', 'submitbutton', get_string('filter')), $mform->createElement('cancel'), ]; $mform->addGroup($buttonarray, 'buttonar', '', [' '], false); } /** * Expand the form contents if the filter is in use. * @throws \HTML_QuickForm_Error */ public function definition_after_data() { $mform = $this->_form; $filterstartdate = $mform->getElement('filterstartdate')->getValue(); $filterenddate = $mform->getElement('filterenddate')->getValue(); if (!empty($filterstartdate['enabled']) || !empty($filterenddate['enabled'])) { $mform->setExpanded('filterheader', true); } } } PK}\|?classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for report_outline. * * @package report_outline * @copyright 2018 Zig Tan * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_outline\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for report_outline 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'; } }PK}\D !classes/output/activitieslist.phpnu[. namespace report_outline\output; use core_report\output\coursestructure; use course_modinfo; /** * Activities list page. * * @package report_outline * @copyright 2024 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class activitieslist extends coursestructure { /** * Constructor * * @param course_modinfo $modinfo * @param array $views Views information for activity and users. * @param bool $showlastaccess Whether the last access should be shown or not. * @param int $minlog The minimum log time is computed. * @param bool $showblogs Whether related blog entries should be shown or not. */ public function __construct( course_modinfo $modinfo, /** @var array $views Views information for activity and users. */ protected array $views, /** @var bool $showlastaccess Whether the last access should be shown or not. */ protected bool $showlastaccess = true, /** @var int $minlog The minimum log time is computed. */ protected int $minlog = 0, /** @var bool $showblogs Whether related blog entries should be shown or not. */ protected bool $showblogs = true, ) { $this->modinfo = $modinfo; } /** * Exports the data. * * @param \renderer_base $output * @return array|\stdClass */ public function export_for_template(\renderer_base $output) { $table = parent::export_for_template($output); $table['id'] = 'outlinereport'; $data = [ 'minlog' => userdate($this->minlog), 'table' => $table, ]; return $data; } /** * Exports the headers for report table. * * @param \renderer_base $output * @return array */ protected function export_headers(\renderer_base $output): array { $headers = parent::export_headers($output); $headers[] = get_string('views'); if ($this->showblogs) { $headers[] = get_string('relatedblogentries', 'blog'); } if ($this->showlastaccess) { $headers[] = get_string('lastaccess'); } return $headers; } /** * Exports the data for a single activity. * * @param \renderer_base $output * @param \cm_info $cm * @param bool $indelegated Whether the activity is part of a delegated section or not. * @return array */ public function export_activity_data(\renderer_base $output, \cm_info $cm, bool $indelegated = false): array { global $CFG; $data = parent::export_activity_data($output, $cm, $indelegated); if (empty($data) || !in_array('cells', $data)) { return []; } if (!empty($this->views[$cm->id]->numviews)) { $numviewscell = get_string('numviews', 'report_outline', $this->views[$cm->id]); } else { $numviewscell = '-'; } $data['cells'][] = [ 'activityclass' => 'numviews', 'text' => $numviewscell, ]; if ($this->showblogs) { $cell = ['activityclass' => 'blog']; require_once($CFG->dirroot.'/blog/lib.php'); if ($blogcount = blog_get_associated_count($cm->get_course()->id, $cm->id)) { $blogurl = new \moodle_url('/blog/index.php', ['modid' => $cm->id]); $cell['link'] = new \action_link($blogurl, $blogcount); } else { $cell['text'] = '-'; } $data['cells'][] = $cell; } if ($this->showlastaccess) { if (isset($this->views[$cm->id]->lasttime)) { $timeago = format_time(time() - $this->views[$cm->id]->lasttime); $lastaccesscell = userdate($this->views[$cm->id]->lasttime)." ($timeago)"; } else { $lastaccesscell = ''; } $data['cells'][] = [ 'activityclass' => 'lastaccess', 'text' => $lastaccesscell, ]; } return $data; } } PK}\n  )classes/output/hierarchicalactivities.phpnu[. namespace report_outline\output; use core_report\output\coursestructure; use course_modinfo; /** * Activities list page in a hierarchical format. * * @package report_outline * @copyright 2024 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class hierarchicalactivities extends coursestructure { /** * Exports the data for a single activity. * * @param \renderer_base $output * @param \cm_info $cm * @param bool $indelegated Whether the activity is part of a delegated section or not. * @return array */ public function export_activity_data(\renderer_base $output, \cm_info $cm, bool $indelegated = false): array { if (!$cm->has_view()) { return []; } if (!$cm->uservisible) { return []; } return [ 'isactivity' => true, 'isdelegated' => false, 'indelegated' => $indelegated, 'visible' => $cm->visible, 'id' => $cm->id, ]; } /** * Print activity data. * * @param \renderer_base $output * @param string $mode * @param \cm_info $mod * @param \stdClass $user * @param \stdClass $course */ public function print_activity( \renderer_base $output, string $mode, \cm_info $mod, \stdClass $user, \stdClass $course, ): void { global $CFG, $DB; $instance = $DB->get_record($mod->modname, ['id' => $mod->instance]); $libfile = "$CFG->dirroot/mod/$mod->modname/lib.php"; if (!file_exists($libfile)) { return; } require_once($libfile); switch ($mode) { case "outline": $useroutline = $mod->modname . "_user_outline"; if (function_exists($useroutline)) { $toprint = $useroutline($course, $user, $mod, $instance); } else { $toprint = report_outline_user_outline($user->id, $mod->id, $mod->modname, $mod->instance); } if (!$toprint) { $toprint = (object) ['info' => '-']; } report_outline_print_row($mod, $instance, $toprint); break; case "complete": $usercomplete = $mod->modname . "_user_complete"; $image = $output->pix_icon('monologo', $mod->modfullname, 'mod_' . $mod->modname, ['class' => 'icon']); echo "

$image $mod->modfullname: " . "wwwroot/mod/$mod->modname/view.php?id=$mod->id\">" . format_string($instance->name, true) . "

"; ob_start(); echo "
    "; if (function_exists($usercomplete)) { $usercomplete($course, $user, $mod, $instance); } else { echo report_outline_user_complete($user->id, $mod->id, $mod->modname, $mod->instance); } echo "
"; $toprint = ob_get_contents(); ob_end_clean(); if (str_replace(' ', '', $toprint) != '
    ') { echo $toprint; } break; } } } PK}\q[ll(classes/event/activity_report_viewed.phpnu[. /** * The report_outline activity report viewed event. * * @package report_outline * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_outline\event; defined('MOODLE_INTERNAL') || die(); /** * The report_outline activity report viewed event class. * * @package report_outline * @since Moodle 2.7 * @copyright 2014 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class activity_report_viewed extends \core\event\base { /** * Init method. * * @return void */ protected function init() { $this->data['crud'] = 'r'; $this->data['edulevel'] = self::LEVEL_TEACHING; } /** * Return localised event name. * * @return string */ public static function get_name() { return get_string('eventactivityreportviewed', 'report_outline'); } /** * Returns description of what happened. * * @return string */ public function get_description() { return "The user with id '$this->userid' viewed the outline activity report for the course with id '$this->courseid'."; } /** * Returns relevant URL. * * @return \moodle_url */ public function get_url() { return new \moodle_url('/report/outline/index.php', ['id' => $this->courseid]); } } PK}\)pl l classes/event/report_viewed.phpnu[. /** * The report_outline outline report viewed event. * * @package report_outline * @copyright 2013 Ankit Agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_outline\event; defined('MOODLE_INTERNAL') || die(); /** * The report_outline outline report viewed event class. * * @property-read array $other { * Extra information about the event. * * - string mode: display mode. * } * * @package report_outline * @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_TEACHING; } /** * Return localised event name. * * @return string */ public static function get_name() { return get_string('eventoutlinereportviewed', 'report_outline'); } /** * Returns description of what happened. * * @return string */ public function get_description() { return "The user with id '$this->userid' viewed the outline report for the user with id '$this->relateduserid' " . "for the course with id '$this->courseid'."; } /** * Returns relevant URL. * * @return \moodle_url */ public function get_url() { return new \moodle_url('/report/outline/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; } } PK}\ #templates/delegatedsection.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template report_outline/delegatedsection Template for Activity report sections. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * none Example context (json): { "visible": true, "class": "section", "text": "Section 1" } }}

    {{{ text }}}

    {{^visible}}
    {{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}
    {{/visible}} PK}\dtemplates/section.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template report_outline/section Template for Activity report sections. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * none Example context (json): { "visible": true, "class": "section", "text": "Section 1" } }}

    {{{ text }}}

    {{^visible}}
    {{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}
    {{/visible}} PK}\[WWtemplates/activity.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template report_outline/activity Template for Activity report activities. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * none Example context (json): { "indelegated": true, "visible": true, "class": "activity", "text": "Announcements", "cells": [{ "activityclass": "lastaccess", "text": "Friday, 14 June 2024, 5:17 PM" }] } }} {{#activitycolumn}}
    {{{ activityicon }}} {{#link}}{{/link}}{{{ text }}}{{#link}}{{/link}}
    {{^visible}}
    {{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}
    {{/visible}} {{/activitycolumn}} {{#cells}} {{#text}}{{{ text }}}{{/text}} {{#link}} {{> core/action_link}} {{/link}} {{/cells}} PK}\6fh templates/report.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template report_outline/report Template for Activity report. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * none Example context (json): { "minlog": "Monday, 29 April 2024, 4:18 PM", "table": { "class": "generaltable boxaligncenter", "id": "outlinereport", "headers": ["Activity", "Views", "Last access"], "headerscount": 3, "actitivities": [{ "isactivity": false, "indelegated": true, "visible": true, "class": "activity", "text": "Announcements", "cells": [{ "activityclass": "lastaccess", "text": "Friday, 14 June 2024, 5:17 PM" }] }, { "issection": true, "isdelegated": true, "visible": true, "class": "section", "text": "Section 1" }] } } }}
    {{#str}} computedfromlogs, admin, {{{ minlog }}} {{/str}}
    {{#table}} {{#headers}} {{/headers}} {{#activities}} {{#issection}} {{>report_outline/section }} {{/issection}} {{#isdelegated}} {{>report_outline/delegatedsection }} {{/isdelegated}} {{#isactivity}} {{>report_outline/activity }} {{/isactivity}} {{/activities}}
    {{{ . }}}
    {{/table}}
    PK}\eHE)E) locallib.phpnu[. /** * This file contains functions used by the outline reports * * @package report * @subpackage outline * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core\output\local\properties\iconsize; use core_course\output\activity_icon; defined('MOODLE_INTERNAL') || die; require_once(__DIR__.'/lib.php'); require_once($CFG->dirroot.'/course/lib.php'); function report_outline_print_row($mod, $instance, $result) { global $OUTPUT, $CFG; $activityicon = activity_icon::from_cm_info($mod) ->set_icon_size(iconsize::SIZE4); $image = $OUTPUT->render($activityicon); echo ""; echo "$image"; echo ""; echo " modfullname\""; echo " href=\"$CFG->wwwroot/mod/$mod->modname/view.php?id=$mod->id\">".format_string($instance->name,true).""; echo "   "; echo ""; if (isset($result->info)) { echo "$result->info"; } else { echo "

    -

    "; } echo ""; echo "   "; if (!empty($result->time)) { $timeago = format_time(time() - $result->time); echo "".userdate($result->time)." ($timeago)"; } echo ""; } /** * Returns an array of the commonly used log variables by the outline report. * * @return array the array of variables used */ function report_outline_get_common_log_variables() { global $DB; static $uselegacyreader; static $useinternalreader; static $minloginternalreader; static $logtable = null; if (isset($uselegacyreader) && isset($useinternalreader) && isset($minloginternalreader)) { return array($uselegacyreader, $useinternalreader, $minloginternalreader, $logtable); } $uselegacyreader = false; // Flag to determine if we should use the legacy reader. $useinternalreader = false; // Flag to determine if we should use the internal reader. $minloginternalreader = 0; // Set this to 0 for now. // Get list of readers. $logmanager = get_log_manager(); $readers = $logmanager->get_readers(); // Get preferred reader. if (!empty($readers)) { foreach ($readers as $readerpluginname => $reader) { // If sql_internal_table_reader is preferred reader. if ($reader instanceof \core\log\sql_internal_table_reader) { $useinternalreader = true; $logtable = $reader->get_internal_log_table_name(); $minloginternalreader = $DB->get_field_sql('SELECT min(timecreated) FROM {' . $logtable . '}'); } } } return array($uselegacyreader, $useinternalreader, $minloginternalreader, $logtable); } /** * Return the most commonly used user outline information. * * @param int $userid the id of the user * @param int $cmid the course module id * @param string $module the name of the module (eg. 'book') * @param int $instanceid (eg. the 'id' in the 'book' table) * @return stdClass|null if any information is found then a stdClass containing * this info is returned, else null is. */ function report_outline_user_outline($userid, $cmid, $module, $instanceid) { global $DB; list($uselegacyreader, $useinternalreader, $minloginternalreader, $logtable) = report_outline_get_common_log_variables(); // If using legacy log then get users from old table. if ($uselegacyreader) { // Create the params for the query. $params = array('userid' => $userid, 'module' => $module, 'action' => 'view', 'info' => $instanceid); // If we are going to use the internal (not legacy) log table, we should only get records // from the legacy table that exist before we started adding logs to the new table. $limittime = ''; if (!empty($minloginternalreader)) { $limittime = ' AND time < :timeto '; $params['timeto'] = $minloginternalreader; } $select = "SELECT COUNT(id) "; $from = "FROM {log} "; $where = "WHERE userid = :userid AND module = :module AND action = :action AND info = :info "; if ($legacylogcount = $DB->count_records_sql($select . $from . $where . $limittime, $params)) { $numviews = $legacylogcount; // Get the time for the last log. $select = "SELECT MAX(time) "; $lastlogtime = $DB->get_field_sql($select . $from . $where, $params); $result = new stdClass(); $result->info = get_string('numviews', '', $numviews); $result->time = $lastlogtime; } } // Get record from sql_internal_table_reader and combine with the number of views from the legacy log table (if needed). if ($useinternalreader) { $params = array('userid' => $userid, 'contextlevel' => CONTEXT_MODULE, 'contextinstanceid' => $cmid, 'crud' => 'r', 'edulevel1' => core\event\base::LEVEL_PARTICIPATING, 'edulevel2' => core\event\base::LEVEL_TEACHING, 'edulevel3' => core\event\base::LEVEL_OTHER, 'anonymous' => 0); $select = "SELECT COUNT(*) as count "; $from = "FROM {" . $logtable . "} "; $where = "WHERE userid = :userid AND contextlevel = :contextlevel AND contextinstanceid = :contextinstanceid AND crud = :crud AND edulevel IN (:edulevel1, :edulevel2, :edulevel3) AND anonymous = :anonymous"; if ($internalreadercount = $DB->count_records_sql($select . $from . $where, $params)) { if (!empty($numviews)) { $numviews = $numviews + $internalreadercount; } else { $numviews = $internalreadercount; } // Get the time for the last log. $select = "SELECT MAX(timecreated) "; $lastlogtime = $DB->get_field_sql($select . $from . $where, $params); $result = new stdClass(); $result->info = get_string('numviews', '', $numviews); $result->time = $lastlogtime; } } if (!empty($result)) { return $result; } return null; } /** * Display the most commonly used user complete information. * * @param int $userid the id of the user * @param int $cmid the course module id * @param string $module the name of the module (eg. 'book') * @param int $instanceid (eg. the 'id' in the 'book' table) * @return string */ function report_outline_user_complete($userid, $cmid, $module, $instanceid) { global $DB; list($uselegacyreader, $useinternalreader, $minloginternalreader, $logtable) = report_outline_get_common_log_variables(); // If using legacy log then get users from old table. if ($uselegacyreader) { // Create the params for the query. $params = array('userid' => $userid, 'module' => $module, 'action' => 'view', 'info' => $instanceid); // If we are going to use the internal (not legacy) log table, we should only get records // from the legacy table that exist before we started adding logs to the new table. $limittime = ''; if (!empty($minloginternalreader)) { $limittime = ' AND time < :timeto '; $params['timeto'] = $minloginternalreader; } $select = "SELECT COUNT(id) "; $from = "FROM {log} "; $where = "WHERE userid = :userid AND module = :module AND action = :action AND info = :info "; if ($legacylogcount = $DB->count_records_sql($select . $from . $where . $limittime, $params)) { $numviews = $legacylogcount; // Get the time for the last log. $select = "SELECT MAX(time) "; $lastlogtime = $DB->get_field_sql($select . $from . $where, $params); $strnumviews = get_string('numviews', '', $numviews); } } // Get record from sql_internal_table_reader and combine with the number of views from the legacy log table (if needed). if ($useinternalreader) { $params = array('userid' => $userid, 'contextlevel' => CONTEXT_MODULE, 'contextinstanceid' => $cmid, 'crud' => 'r', 'edulevel1' => core\event\base::LEVEL_PARTICIPATING, 'edulevel2' => core\event\base::LEVEL_TEACHING, 'edulevel3' => core\event\base::LEVEL_OTHER, 'anonymous' => 0); $select = "SELECT COUNT(*) as count "; $from = "FROM {" . $logtable . "} "; $where = "WHERE userid = :userid AND contextlevel = :contextlevel AND contextinstanceid = :contextinstanceid AND crud = :crud AND edulevel IN (:edulevel1, :edulevel2, :edulevel3) AND anonymous = :anonymous"; if ($internalreadercount = $DB->count_records_sql($select . $from . $where, $params)) { if (!empty($numviews)) { $numviews = $numviews + $internalreadercount; } else { $numviews = $internalreadercount; } // Get the time for the last log. $select = "SELECT MAX(timecreated) "; $lastlogtime = $DB->get_field_sql($select . $from . $where, $params); $strnumviews = get_string('numviews', '', $numviews); } } if (!empty($strnumviews) && (!empty($lastlogtime))) { return $strnumviews . ' - ' . get_string('mostrecently') . ' ' . userdate($lastlogtime); } else { return get_string('neverseen', 'report_outline'); } } PK}\@Q   index.phpnu[. /** * Display user activity reports for a course (totals) * * @package report * @subpackage outline * @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.'/report/outline/locallib.php'); $id = required_param('id',PARAM_INT); // course id $startdate = optional_param('startdate', null, PARAM_INT); $enddate = optional_param('enddate', null, PARAM_INT); $course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST); $pageparams = array('id' => $id); if ($startdate) { $pageparams['startdate'] = $startdate; } if ($enddate) { $pageparams['enddate'] = $enddate; } $PAGE->set_url('/report/outline/index.php', $pageparams); $PAGE->set_pagelayout('report'); require_login($course); $context = context_course::instance($course->id); require_capability('report/outline:view', $context); // Handle form to filter access logs by date. $filterform = new \report_outline\filter_form(); $filterform->set_data(['id' => $course->id, 'filterstartdate' => $startdate, 'filterenddate' => $enddate]); if ($filterform->is_cancelled()) { $redir = $PAGE->url; $redir->remove_params(['startdate', 'enddate']); redirect($redir); } if ($filter = $filterform->get_data()) { $redir = $PAGE->url; if ($filter->filterstartdate) { $redir->param('startdate', $filter->filterstartdate); } if ($filter->filterenddate) { $redir->param('enddate', $filter->filterenddate); } redirect($redir); } // Trigger an activity report viewed event. $event = \report_outline\event\activity_report_viewed::create(array('context' => $context)); $event->trigger(); $showlastaccess = true; $showblogs = !empty($CFG->enableblogs) && $CFG->useblogassociations; $hiddenfields = explode(',', $CFG->hiddenuserfields); if (array_search('lastaccess', $hiddenfields) !== false and !has_capability('moodle/user:viewhiddendetails', $context)) { $showlastaccess = false; } $stractivityreport = get_string('pluginname', 'report_outline'); $PAGE->set_title($course->shortname .': '. $stractivityreport); $PAGE->set_heading($course->fullname); echo $OUTPUT->header(); // Print selector drop down. $pluginname = get_string('pluginname', 'report_outline'); report_helper::print_report_selector($pluginname); list($uselegacyreader, $useinternalreader, $minloginternalreader, $logtable) = report_outline_get_common_log_variables(); // If no legacy and no internal log then don't proceed. if (!$uselegacyreader && !$useinternalreader) { echo $OUTPUT->box_start('generalbox', 'notice'); echo $OUTPUT->notification(get_string('nologreaderenabled', 'report_outline')); echo $OUTPUT->box_end(); echo $OUTPUT->footer(); die(); } // We want to display the time we are beginning to get logs from in the heading. // If we are using the legacy reader check the minimum time in that log table. if ($uselegacyreader) { $minlog = $DB->get_field_sql('SELECT min(time) FROM {log}'); } // If we are using the internal reader check the minimum time in that table. if ($useinternalreader) { // If new log table has older data then don't use the minimum time obtained from the legacy table. if (empty($minlog) || ($minloginternalreader <= $minlog)) { $minlog = $minloginternalreader; } } $filterform->display(); $modinfo = get_fast_modinfo($course); // If using legacy log then get users from old table. if ($uselegacyreader) { // If we are going to use the internal (not legacy) log table, we should only get records // from the legacy table that exist before we started adding logs to the new table. $params = array('courseid' => $course->id, 'action' => 'view%', 'visible' => 1); $limittime = ''; if (!empty($minloginternalreader)) { $limittime = ' AND time < :timeto '; $params['timeto'] = $minloginternalreader; } if ($startdate) { $limittime .= ' AND time >= :startdate '; $params['startdate'] = $startdate; } if ($enddate) { $limittime .= ' AND time < :enddate '; $params['enddate'] = $enddate; } // Check if we need to show the last access. $sqllasttime = ''; if ($showlastaccess) { $sqllasttime = ", MAX(time) AS lasttime"; } $logactionlike = $DB->sql_like('l.action', ':action'); $sql = "SELECT cm.id, COUNT('x') AS numviews, COUNT(DISTINCT userid) AS distinctusers $sqllasttime FROM {course_modules} cm JOIN {modules} m ON m.id = cm.module JOIN {log} l ON l.cmid = cm.id WHERE cm.course = :courseid AND $logactionlike AND m.visible = :visible $limittime GROUP BY cm.id"; $views = $DB->get_records_sql($sql, $params); } // Get record from sql_internal_table_reader and merge with records obtained from legacy log (if needed). if ($useinternalreader) { // Check if we need to show the last access. $sqllasttime = ''; if ($showlastaccess) { $sqllasttime = ", MAX(timecreated) AS lasttime"; } $params = array('courseid' => $course->id, 'contextmodule' => CONTEXT_MODULE); $limittime = ''; if ($startdate) { $limittime .= ' AND timecreated >= :startdate '; $params['startdate'] = $startdate; } if ($enddate) { $limittime .= ' AND timecreated < :enddate '; $params['enddate'] = $enddate; } $sql = "SELECT contextinstanceid as cmid, COUNT('x') AS numviews, COUNT(DISTINCT userid) AS distinctusers $sqllasttime FROM {" . $logtable . "} l WHERE courseid = :courseid AND anonymous = 0 AND crud = 'r' AND contextlevel = :contextmodule $limittime GROUP BY contextinstanceid"; $v = $DB->get_records_sql($sql, $params); if (empty($views)) { $views = $v; } else { // Merge two view arrays. foreach ($v as $key => $value) { if (isset($views[$key]) && !empty($views[$key]->numviews)) { $views[$key]->numviews += $value->numviews; if ($value->lasttime > $views[$key]->lasttime) { $views[$key]->lasttime = $value->lasttime; } } else { $views[$key] = $value; } } } } $activitieslist = new report_outline\output\activitieslist($modinfo, $views, $showlastaccess, $minlog, $showblogs); echo $OUTPUT->render_from_template('report_outline/report', $activitieslist->export_for_template($OUTPUT)); echo $OUTPUT->footer(); PK}\Ѭ}UUtests/lib_test.phpnu[. /** * Tests for report library functions. * * @package report_outline * @copyright 2014 onwards Ankit agarwal * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. */ namespace report_outline; defined('MOODLE_INTERNAL') || die(); global $CFG; /** * Class report_outline_lib_testcase * * @package report_outline * @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 context_course Course context. */ private $coursecontext; /** * @var \core_user\output\myprofile\tree The navigation tree. */ private $tree; /** * @var int Dummy role for testing. */ private $roleid; 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->coursecontext = \context_course::instance($this->course->id); $this->roleid = create_role('Dummy role', 'dummyrole', 'dummy role description'); $this->resetAfterTest(); } /** * Test report_log_supports_logstore. */ public function test_report_participation_supports_logstore(): void { $logmanager = get_log_manager(); $allstores = \core_component::get_plugin_list_with_class('logstore', 'log\store'); $supportedstores = array( '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_outline'); $stores = array_keys($stores); foreach ($expectedstores as $expectedstore) { $this->assertContains($expectedstore, $stores); } } /** * Tests the report_outline_myprofile_navigation() function as an admin user. */ public function test_report_outline_myprofile_navigation(): void { $this->setAdminUser(); $iscurrentuser = false; report_outline_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course); $reflector = new \ReflectionObject($this->tree); $nodes = $reflector->getProperty('nodes'); $this->assertArrayHasKey('outline', $nodes->getValue($this->tree)); $this->assertArrayHasKey('complete', $nodes->getValue($this->tree)); } /** * Tests the report_outline_myprofile_navigation() function as a user without permission. */ public function test_report_outline_myprofile_navigation_without_permission(): void { $this->setUser($this->user); $iscurrentuser = true; report_outline_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course); $reflector = new \ReflectionObject($this->tree); $nodes = $reflector->getProperty('nodes'); $this->assertArrayNotHasKey('outline', $nodes->getValue($this->tree)); $this->assertArrayNotHasKey('complete', $nodes->getValue($this->tree)); } /** * Test that the current user can not access user report without report/outline:viewuserreport permission. */ public function test_report_outline_can_not_access_user_report_without_viewuserreport_permission(): void { $this->getDataGenerator()->role_assign($this->roleid, $this->user->id, $this->coursecontext->id); $this->setUser($this->user); $this->assertFalse(report_outline_can_access_user_report($this->user, $this->course)); } /** * Test that the current user can access user report with report/outline:viewuserreport permission. */ public function test_report_outline_can_access_user_report_with_viewuserreport_permission(): void { assign_capability('report/outline:viewuserreport', CAP_ALLOW, $this->roleid, $this->coursecontext->id, true); $this->getDataGenerator()->role_assign($this->roleid, $this->user->id, $this->coursecontext->id); $this->setUser($this->user); $this->assertTrue(report_outline_can_access_user_report($this->user, $this->course)); } } PK}\i~ ~ tests/behat/user.featurenu[@report @report_outline Feature: View the user page for the outline report In order to ensure the user page for the outline report works as expected As a student I need to log in as a student and view the user page for the outline report Background: Given the following "courses" exist: | fullname | shortname | format | showreports | | Course 1 | C1 | topics | 1 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "activities" exist: | activity | name | description | course | idnumber | | folder | Folder name | Folder description | C1 | folder1 | And the following "activities" exist: | activity | name | description | course | idnumber | externalurl | | url | URL name | URL description | C1 | folder1 | http://www.google.com | When I log in as "admin" Scenario: View the user page when only the standard log reader is enabled Given I navigate to "Plugins > Logging > Manage log stores" in site administration And "Disable" "link" should exist in the "Standard log" "table_row" And I log out And I log in as "student1" And I am on "Course 1" course homepage # We want to view this multiple times, to make sure the count is working. And I follow "Folder name" And I reload the page And I reload the page And I reload the page And I am on "Course 1" course homepage # We want to view this multiple times, to make sure the count is working. And I follow "URL name" And I reload the page And I reload the page And I follow "Profile" in the user menu And I click on "Course 1" "link" in the "region-main" "region" When I follow "Outline report" Then I should see "4 views" in the "Folder name" "table_row" And I should see "3 views" in the "URL name" "table_row" And I follow "Profile" in the user menu And I click on "Course 1" "link" in the "region-main" "region" When I follow "Complete report" And I should see "4 views" And I should see "3 views" Scenario: View the user complete report page when there is a no-grade forum Given the following "activities" exist: | activity | name | course | idnumber | | forum | forum1 | C1 | forum1 | And I am on "Course 1" course homepage When I follow "Participants" And I follow "Student 1" And I follow "Outline report" Then I should see "Activity report" When I follow "Participants" And I follow "Student 1" And I follow "Complete report" Then I should see "Activity report" PK}\ C $tests/behat/behat_report_outline.phpnu[. /** * Custom behat functions * * @package report_outline * @copyright 2017 Davo Smith, Synergy Learning * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); /** * Class behat_report_outline custom Behat steps for report_outline. */ class behat_report_outline extends behat_base { /** * This is a horrible, horrible hack, but it is not clear how else a range of log entries can be produced to test the * filtering of the log entries. * * @Given /^the log timestamp for "(?P(?:[^"]|\\")*)" and "(?P(?:[^"]|\\")*)" is set to "(?P(?:[^"]|\\")*)"$/ * @param string $username * @param string $activityidnumber * @param string $date */ public function the_log_timestamp_for_and_is_set_to($username, $activityidnumber, $date) { global $DB; // Get the name of the log table. $lm = get_log_manager(); $readers = $lm->get_readers('\\core\\log\\sql_internal_table_reader'); $reader = reset($readers); $table = $reader->get_internal_log_table_name(); // Find the log entry. $cmrec = $DB->get_record('course_modules', ['idnumber' => $activityidnumber], '*', MUST_EXIST); $modname = $DB->get_field('modules', 'name', ['id' => $cmrec->module], MUST_EXIST); $userid = $DB->get_field('user', 'id', ['username' => $username], MUST_EXIST); $cond = [ 'userid' => $userid, 'component' => 'mod_'.$modname, 'target' => 'course_module', 'action' => 'viewed', 'contextinstanceid' => $cmrec->id, ]; $logentries = $DB->get_records($table, $cond, 'timecreated DESC', 'id', 0, 1); $logentry = reset($logentries); // Update the timecreated for the entry. $timestamp = strtotime($date); $DB->set_field($table, 'timecreated', $timestamp, ['id' => $logentry->id]); } }PK}\%[@@&tests/behat/subsection_reports.featurenu[@mod @mod_subsection @report Feature: Subsections are shown in reports In order to use reports As an teacher I need to see sections and subsections structure in reports Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | category | numsections | initsections | | Course 1 | C1 | 0 | 1 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | course | idnumber | section | | page | First page | C1 | page1 | 1 | | subsection | Subsection 1 | C1 | subsection1 | 1 | | page | Last page | C1 | last | 1 | | page | Page in Subsection 1 | C1 | subpage | 2 | And I log in as "teacher1" @report_outline Scenario: Course Activity report show subsections' information Given I am on "Course 1" course homepage When I navigate to "Reports > Activity report" in current page administration Then I should see "First page" in the "generaltable" "table" And "Subsection" "table_row" should appear before "Last page" "table_row" And "Page in Subsection 1" "table_row" should appear before "Last page" "table_row" PK}\X0//tests/behat/outline.featurenu[@report @report_outline Feature: View an outline report In order to ensure the outline report works as expected As a teacher I need to log in as a teacher and view the outline report Background: Given the following "courses" exist: | fullname | shortname | format | numsections | | Course 1 | C1 | topics | 1 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "activities" exist: | activity | name | course | idnumber | | forum | Forum name | C1 | forum1 | | book | Book name | C1 | book1 | When I am on the "Course 1" course page logged in as admin Scenario: View the outline report when only the standard log reader is enabled Given I navigate to "Plugins > Logging > Manage log stores" in site administration And "Disable" "link" should exist in the "Standard log" "table_row" And I am on the "Course 1" course page logged in as student1 And I follow "Forum name" And I am on "Course 1" course homepage And I follow "Book name" And I am on the "Course 1" course page logged in as student2 And I follow "Book name" And I am on the "Course 1" course page logged in as admin When I navigate to "Reports" in current page administration And I click on "Activity report" "link" Then I should see "2 views by 2 users" in the "Book name" "table_row" And I should see "1 views by 1 users" in the "Forum name" "table_row" Scenario: View the outline report when no log reader is enabled Given 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 am on "Course 1" course homepage When I navigate to "Reports" in current page administration And I click on "Activity report" "link" Then I should see "No log reader enabled" Scenario: Multiple views from a single user are identified as not distinct Given I am on the "Course 1" course page logged in as student1 And I follow "Forum name" And I am on "Course 1" course homepage And I follow "Forum name" And I am on "Course 1" course homepage And I follow "Forum name" And I am on site homepage When I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration And I click on "Activity report" "link" Then I should see "3 views by 1 users" in the "Forum name" "table_row" And I should see "-" in the "Book name" "table_row" Scenario: Multiple views from multiple users are identified as not distinct Given I am on the "Course 1" course page logged in as student1 And I follow "Forum name" And I am on "Course 1" course homepage And I follow "Forum name" And I am on "Course 1" course homepage And I follow "Forum name" And I am on site homepage Given I am on the "Course 1" course page logged in as student2 And I follow "Forum name" And I am on "Course 1" course homepage And I follow "Forum name" And I am on "Course 1" course homepage And I follow "Forum name" And I am on site homepage When I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration And I click on "Activity report" "link" Then I should see "6 views by 2 users" in the "Forum name" "table_row" And I should see "-" in the "Book name" "table_row" Scenario: No views from any users When I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration And I click on "Activity report" "link" Then I should see "-" in the "Forum name" "table_row" And I should see "-" in the "Book name" "table_row" Scenario: The outline report can represent courses with subsections Given the following "activities" exist: | activity | name | course | section | visible | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | | assign | Activity1 | C1 | 1 | 1 | 1 | 0 | And the following "activities" exist: | activity | name | course | section | visible | | subsection | Subsection1 | C1 | 1 | 1 | And the following "activities" exist: | activity | name | course | section | visible | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | | assign | Subactivity1.1 | C1 | 2 | 1 | 1 | 0 | | assign | Subactivity1.2 | C1 | 2 | 0 | 1 | 0 | | assign | Activity2 | C1 | 1 | 1 | 1 | 0 | When I am on the "Course 1" course page logged in as teacher1 And I navigate to "Reports" in current page administration And I click on "Activity report" "link" Then "Subactivity1.1" "table_row" should appear after "Activity1" "table_row" And "Subactivity1.2" "table_row" should appear after "Subactivity1.1" "table_row" And "Activity2" "table_row" should appear after "Subactivity1.2" "table_row" And I navigate to "Participants" in current page administration And I click on "Student 1" "link" And I click on "Outline report" "link" And "Subactivity1.1" "table_row" should appear after "Activity1" "table_row" And I should not see "Subactivity1.2" in the "page-content" "region" And "Activity2" "table_row" should appear after "Subactivity1.1" "table_row" PK}\tests/behat/filter.featurenu[@report @report_outline Feature: Filter an outline report In order to ensure the outline report works as expected As a teacher I need to log in as a teacher and view the outline report with various filters in place Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | And the following "activities" exist: | activity | name | course | idnumber | section | | forum | Forum name | C1 | FORUM01 | 1 | | book | Book name | C1 | BOOK01 | 1 | When I am on the "Course 1" course page logged in as admin Scenario: Filter the outline report by start date Given I navigate to "Plugins > Logging > Manage log stores" in site administration And "Disable" "link" should exist in the "Standard log" "table_row" And I am on the "Forum name" "forum activity" page logged in as student1 And the log timestamp for "student1" and "FORUM01" is set to "12 June 2017 12:49:00" And I am on "Course 1" course homepage And I follow "Book name" And the log timestamp for "student1" and "BOOK01" is set to "10 June 2017 14:01:00" And I am on the "Book name" "book activity" page logged in as student2 And the log timestamp for "student2" and "BOOK01" is set to "14 June 2017 11:02:00" And I am on the "Course 1" course page logged in as admin And I navigate to "Reports" in current page administration And I click on "Activity report" "link" And I should see "2 views by 2 users" in the "Book name" "table_row" And I should see "1 views by 1 users" in the "Forum name" "table_row" When I set the following fields to these values: | filterstartdate[enabled] | 1 | | filterstartdate[day] | 12 | | filterstartdate[month] | June | | filterstartdate[year] | 2017 | And I click on "Filter" "button" in the "#fgroup_id_buttonar" "css_element" Then I should see "1 views by 1 users" in the "Book name" "table_row" And I should see "1 views by 1 users" in the "Forum name" "table_row" Scenario: Filter the outline report by end date Given I navigate to "Plugins > Logging > Manage log stores" in site administration And "Disable" "link" should exist in the "Standard log" "table_row" And I am on the "Forum name" "forum activity" page logged in as student1 And the log timestamp for "student1" and "FORUM01" is set to "12 June 2017 12:49:00" And I am on "Course 1" course homepage And I follow "Book name" And the log timestamp for "student1" and "BOOK01" is set to "10 June 2017 14:01:00" And I am on the "Book name" "book activity" page logged in as student2 And the log timestamp for "student2" and "BOOK01" is set to "14 June 2017 11:02:00" And I am on the "Course 1" course page logged in as admin And I navigate to "Reports" in current page administration And I click on "Activity report" "link" And I should see "2 views by 2 users" in the "Book name" "table_row" And I should see "1 views by 1 users" in the "Forum name" "table_row" When I set the following fields to these values: | filterenddate[enabled] | 1 | | filterenddate[day] | 11 | | filterenddate[month] | June | | filterenddate[year] | 2017 | And I click on "Filter" "button" in the "#fgroup_id_buttonar" "css_element" Then I should see "1 views by 1 users" in the "Book name" "table_row" And I should not see "views by" in the "Forum name" "table_row" PK}\)}MM)tests/behat/course_report_outline.featurenu[@report @report_outline Feature: In a course administration page, navigate through report page, test for activity report page In order to navigate through report page As an admin Go to course administration -> Reports -> Activity 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 | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | | student1 | C1 | student | @javascript Scenario: Selector should be available in course activiy report 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 "Activity report" "link" Then "Report" "field" should exist in the "tertiary-navigation" "region" And I should see "Activity report" in the "tertiary-navigation" "region" PK}\Kuser.phpnu[. /** * Display user activity reports for a course (totals) * * @package report * @subpackage outline * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use report_outline\output\hierarchicalactivities; require('../../config.php'); require_once($CFG->dirroot.'/report/outline/locallib.php'); require_once($CFG->dirroot.'/report/outline/lib.php'); $userid = required_param('id', PARAM_INT); $courseid = required_param('course', PARAM_INT); $mode = optional_param('mode', 'outline', PARAM_ALPHA); if ($mode !== 'complete' and $mode !== 'outline') { $mode = 'outline'; } $user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0), '*', MUST_EXIST); $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST); $coursecontext = context_course::instance($course->id); $personalcontext = context_user::instance($user->id); if ($courseid == 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); } $PAGE->set_url('/report/outline/user.php', array('id'=>$userid, 'course'=>$courseid, 'mode'=>$mode)); if (!report_outline_can_access_user_report($user, $course)) { throw new \moodle_exception('nocapability', 'report_outline'); } $stractivityreport = get_string('activityreport'); $PAGE->set_pagelayout('report'); $PAGE->set_url('/report/outline/user.php', array('id'=>$user->id, 'course'=>$course->id, '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/outline/user.php', array('id' => $user->id, 'course' => $course->id, 'mode' => $mode)) ); if ($mode === 'complete') { $navigationnode['name'] = get_string('completereport'); } else { $navigationnode['name'] = get_string('outlinereport'); } $PAGE->add_report_nodes($user->id, $navigationnode); if ($courseid == SITEID) { $PAGE->set_heading(fullname($user)); } else { $PAGE->set_heading($course->fullname); } // Trigger a report viewed event. $event = \report_outline\event\report_viewed::create(array('context' => context_course::instance($course->id), 'relateduserid' => $userid, 'other' => array('mode' => $mode))); $event->trigger(); echo $OUTPUT->header(); if ($courseid != SITEID) { $backurl = new moodle_url('/user/view.php', ['id' => $userid, 'course' => $courseid]); echo $OUTPUT->single_button($backurl, get_string('back'), 'get', ['class' => 'mb-3']); echo $OUTPUT->context_header( array( 'heading' => fullname($user), 'user' => $user, 'usercontext' => $personalcontext ), 2); if ($mode === 'outline') { echo $OUTPUT->heading(get_string('outlinereport', 'moodle'), 2, 'main mt-4 mb-4'); } else { echo $OUTPUT->heading(get_string('completereport', 'moodle'), 2, 'main mt-4 mb-4'); } } $modinfo = get_fast_modinfo($course, $user->id); $itemsprinted = false; $coursestructure = new hierarchicalactivities($modinfo); $sections = $coursestructure->export_hierarchy($OUTPUT); foreach ($sections as $i => $section) { if (!$section['visible']) { continue; } $section = (object) $section; $itemsprinted = true; echo '
    '; echo '

    '; echo $section->text; echo "

    "; echo '
    '; if ($mode == "outline") { echo ""; } foreach ($section->activities as $cm) { if (!$cm['visible']) { continue; } $cm = (object) $cm; if ($cm->isdelegated) { // Print subsection box. if ($mode == "outline") { echo "
    "; } echo '
    '; echo '

    '; echo $cm->text; echo "

    "; echo '
    '; if ($mode == "outline") { echo ""; } foreach ($cm->activities as $subcm) { if (!$subcm['visible']) { continue; } $subcm = (object) $subcm; $mod = $modinfo->cms[$subcm->id]; $coursestructure->print_activity($OUTPUT, $mode, $mod, $user, $course); } if ($mode == "outline") { echo "
    "; } echo "
    "; // Content. echo "
    "; // Subsection. if ($mode == "outline") { echo ""; } continue; } $mod = $modinfo->cms[$cm->id]; $coursestructure->print_activity($OUTPUT, $mode, $mod, $user, $course); } if ($mode == "outline") { echo "
    "; } echo '
    '; // Content. echo '
    '; // Section. } if (!$itemsprinted) { echo $OUTPUT->notification(get_string('nothingtodisplay'), 'info', false); } echo $OUTPUT->footer(); PK}\Ǵ}n styles.cssnu[PK}\idb/install.phpnu[PK}\_(ի db/access.phpnu[PK}\LHΨ lang/en/report_outline.phpnu[PK}\+VZlib.phpnu[PK}\䝲 *version.phpnu[PK}\