From 6912ddb3c6cbba0e2845b30e2be2cbc50cc3f4e8 Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Wed, 19 Jun 2024 13:57:07 +0200 Subject: [PATCH] MDL-81771 report_outline: Add subsections to course Activity report --- .upgradenotes/MDL-81771-2024062007585494.yml | 5 + report/classes/output/coursestructure.php | 179 ++++++++++++++++++ .../outline/classes/output/activitieslist.php | 145 ++++++++++++++ report/outline/index.php | 107 +---------- report/outline/styles.css | 13 +- report/outline/templates/activity.mustache | 63 ++++++ .../templates/delegatedsection.mustache | 48 +++++ report/outline/templates/report.mustache | 86 +++++++++ report/outline/templates/section.mustache | 48 +++++ theme/boost/scss/moodle/reports.scss | 22 +++ theme/boost/style/moodle.css | 20 ++ theme/classic/style/moodle.css | 20 ++ 12 files changed, 640 insertions(+), 116 deletions(-) create mode 100644 .upgradenotes/MDL-81771-2024062007585494.yml create mode 100644 report/classes/output/coursestructure.php create mode 100644 report/outline/classes/output/activitieslist.php create mode 100644 report/outline/templates/activity.mustache create mode 100644 report/outline/templates/delegatedsection.mustache create mode 100644 report/outline/templates/report.mustache create mode 100644 report/outline/templates/section.mustache diff --git a/.upgradenotes/MDL-81771-2024062007585494.yml b/.upgradenotes/MDL-81771-2024062007585494.yml new file mode 100644 index 00000000000..72aec8da6bd --- /dev/null +++ b/.upgradenotes/MDL-81771-2024062007585494.yml @@ -0,0 +1,5 @@ +issueNumber: MDL-81771 +notes: + core_report: + - message: New coursestructure output general class has been created + type: improved diff --git a/report/classes/output/coursestructure.php b/report/classes/output/coursestructure.php new file mode 100644 index 00000000000..e5be29b0c82 --- /dev/null +++ b/report/classes/output/coursestructure.php @@ -0,0 +1,179 @@ +. + +namespace core_report\output; + +/** + * Course sections, subsections and activities structure for reports. + * + * @package core_report + * @copyright 2024 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class coursestructure implements \renderable, \templatable { + + /** + * Constructor + * + * @param \course_modinfo $modinfo + */ + public function __construct( + /** @var \course_modinfo $modinfo */ + protected \course_modinfo $modinfo + ) { + } + + /** + * Exports the data. + * + * @param \renderer_base $output + * @return array|\stdClass + */ + public function export_for_template(\renderer_base $output) { + + $headers = $this->export_headers($output); + return [ + 'class' => 'generaltable boxaligncenter', + 'headers' => $headers, + 'headerscount' => count($headers), + 'activities' => $this->export_activities($output), + ]; + } + + /** + * Exports activities data. + * + * @param \renderer_base $output + * @return array|\stdClass + */ + public function export_activities(\renderer_base $output) { + + $activities = []; + + $delegatedsections = $this->modinfo->get_sections_delegated_by_cm(); + $allsections = $this->modinfo->get_sections(); + foreach ($allsections as $sectionnum => $sectionmodules) { + // Add the section row. + if ($sectionnum > 0) { + $sectioninfo = $this->modinfo->get_section_info($sectionnum); + + // Don't show subsections here. We are showing them in the corresponding module. + if ($sectioninfo->is_delegated()) { + continue; + } + + if (!$sectioninfo->uservisible) { + continue; + } + + $activities[] = $this->export_section_data($output, $sectioninfo, false); + } + + // Add section modules and possibly subsections. + foreach ($sectionmodules as $cmid) { + $cm = $this->modinfo->cms[$cmid]; + + // Check if the module is delegating a section. + if (array_key_exists($cm->id, $delegatedsections)) { + $subsectioninfo = $delegatedsections[$cm->id]; + // Only non-empty are listed in allsections. We don't show empty sections. + if (!array_key_exists($subsectioninfo->sectionnum, $allsections)) { + continue; + } + + $activities[] = $this->export_section_data($output, $subsectioninfo, true); + + // Show activities inside the section. + $subsectionmodules = $allsections[$subsectioninfo->sectionnum]; + foreach ($subsectionmodules as $subsectioncmid) { + $cm = $this->modinfo->cms[$subsectioncmid]; + $activities[] = $this->export_activity_data($output, $cm, true); + } + } else { + // It's simply a module. + $activities[] = $this->export_activity_data($output, $cm); + } + } + } + + return $activities; + } + + /** + * Exports the headers for report table. + * + * @param \renderer_base $output + * @return array + */ + protected function export_headers(\renderer_base $output): array { + return [get_string('activity')]; + } + + + /** + * Exports the data for a single section. + * + * @param \renderer_base $output + * @param \section_info $sectioninfo Section to export information from. + * @param bool $isdelegated Whether the section is a delegated subsection or not. + * @return array + */ + public function export_section_data(\renderer_base $output, \section_info $sectioninfo, bool $isdelegated = false): array { + $datasection = [ + 'issection' => !$isdelegated, + 'isdelegated' => $isdelegated, + 'visible' => $sectioninfo->visible, + 'class' => 'section', + 'text' => get_section_name($sectioninfo->course, $sectioninfo->sectionnum), + ]; + + return $datasection; + } + + /** + * 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; + + if (!$cm->has_view()) { + return []; + } + if (!$cm->uservisible) { + return []; + } + + $modulename = get_string('modulename', $cm->modname); + + $dataactivity = [ + 'isactivity' => true, + 'indelegated' => $indelegated, + 'visible' => $cm->visible, + 'cells' => [], + ]; + $dataactivity['activitycolumn'] = [ + 'activityicon' => $output->pix_icon('monologo', $modulename, $cm->modname, ['class' => 'icon']), + 'link' => "$CFG->wwwroot/mod/$cm->modname/view.php?id=$cm->id", + 'text' => $cm->name, + ]; + return $dataactivity; + } +} diff --git a/report/outline/classes/output/activitieslist.php b/report/outline/classes/output/activitieslist.php new file mode 100644 index 00000000000..f33e7851938 --- /dev/null +++ b/report/outline/classes/output/activitieslist.php @@ -0,0 +1,145 @@ +. + +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; + } +} diff --git a/report/outline/index.php b/report/outline/index.php index 2078ebb5f66..6b251ead3e3 100644 --- a/report/outline/index.php +++ b/report/outline/index.php @@ -73,6 +73,7 @@ $event = \report_outline\event\activity_report_viewed::create(array('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)) { @@ -80,12 +81,6 @@ if (array_search('lastaccess', $hiddenfields) !== false and !has_capability('moo } $stractivityreport = get_string('pluginname', 'report_outline'); -$stractivity = get_string('activity'); -$strlast = get_string('lastaccess'); -$strreports = get_string('reports'); -$strviews = get_string('views'); -$strrelatedblogentries = get_string('relatedblogentries', 'blog'); - $PAGE->set_title($course->shortname .': '. $stractivityreport); $PAGE->set_heading($course->fullname); echo $OUTPUT->header(); @@ -121,22 +116,6 @@ if ($useinternalreader) { $filterform->display(); -echo $OUTPUT->container(get_string('computedfromlogs', 'admin', userdate($minlog)), 'loginfo'); - -$outlinetable = new html_table(); -$outlinetable->attributes['class'] = 'generaltable boxaligncenter'; -$outlinetable->cellpadding = 5; -$outlinetable->id = 'outlinetable'; -$outlinetable->head = array($stractivity, $strviews); - -if (!empty($CFG->enableblogs) && $CFG->useblogassociations) { - $outlinetable->head[] = $strrelatedblogentries; -} - -if ($showlastaccess) { - $outlinetable->head[] = $strlast; -} - $modinfo = get_fast_modinfo($course); // If using legacy log then get users from old table. @@ -220,88 +199,8 @@ if ($useinternalreader) { } } -$prevsecctionnum = 0; -foreach ($modinfo->sections as $sectionnum=>$section) { - foreach ($section as $cmid) { - $cm = $modinfo->cms[$cmid]; - if (!$cm->has_view()) { - continue; - } - if (!$cm->uservisible) { - continue; - } - if ($prevsecctionnum != $sectionnum) { - $sectionrow = new html_table_row(); - $sectionrow->attributes['class'] = 'section'; - $sectioncell = new html_table_cell(); - $sectioncell->colspan = count($outlinetable->head); - - $sectiontitle = get_section_name($course, $sectionnum); - - $sectioncell->text = $OUTPUT->heading($sectiontitle, 3); - $sectionrow->cells[] = $sectioncell; - $outlinetable->data[] = $sectionrow; - - $prevsecctionnum = $sectionnum; - } - - $dimmed = $cm->visible ? '' : 'class="dimmed"'; - $modulename = get_string('modulename', $cm->modname); - - $reportrow = new html_table_row(); - $activitycell = new html_table_cell(); - $activitycell->attributes['class'] = 'activity'; - - $activityicon = $OUTPUT->pix_icon('monologo', $modulename, $cm->modname, array('class'=>'icon')); - - $attributes = array(); - if (!$cm->visible) { - $attributes['class'] = 'dimmed'; - } - - $activitycell->text = $activityicon . html_writer::link("$CFG->wwwroot/mod/$cm->modname/view.php?id=$cm->id", format_string($cm->name), $attributes); - - $reportrow->cells[] = $activitycell; - - $numviewscell = new html_table_cell(); - $numviewscell->attributes['class'] = 'numviews'; - - if (!empty($views[$cm->id]->numviews)) { - $numviewscell->text = get_string('numviews', 'report_outline', $views[$cm->id]); - } else { - $numviewscell->text = '-'; - } - - $reportrow->cells[] = $numviewscell; - - if (!empty($CFG->enableblogs) && $CFG->useblogassociations) { - require_once($CFG->dirroot.'/blog/lib.php'); - $blogcell = new html_table_cell(); - $blogcell->attributes['class'] = 'blog'; - if ($blogcount = blog_get_associated_count($course->id, $cm->id)) { - $blogurl = new moodle_url('/blog/index.php', array('modid' => $cm->id)); - $blogcell->text = html_writer::link($blogurl, $blogcount); - } else { - $blogcell->text = '-'; - } - $reportrow->cells[] = $blogcell; - } - - if ($showlastaccess) { - $lastaccesscell = new html_table_cell(); - $lastaccesscell->attributes['class'] = 'lastaccess'; - - if (isset($views[$cm->id]->lasttime)) { - $timeago = format_time(time() - $views[$cm->id]->lasttime); - $lastaccesscell->text = userdate($views[$cm->id]->lasttime)." ($timeago)"; - } - $reportrow->cells[] = $lastaccesscell; - } - $outlinetable->data[] = $reportrow; - } -} -echo html_writer::table($outlinetable); - +$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(); diff --git a/report/outline/styles.css b/report/outline/styles.css index 9bdcf9d8499..fe95ffe2d05 100644 --- a/report/outline/styles.css +++ b/report/outline/styles.css @@ -1,15 +1,3 @@ -#page-report-outline-index td.numviews { - text-align: right; -} - -#page-report-outline-index tr.section { - text-align: center; -} - -#page-report-outline-index td.lastaccess { - font-size: 0.8em; -} - #page-report-outline-user .section .content { margin-left: 30px; margin-right: 30px; @@ -30,3 +18,4 @@ border-style: solid; padding: 10px; } + diff --git a/report/outline/templates/activity.mustache b/report/outline/templates/activity.mustache new file mode 100644 index 00000000000..208e7b74c08 --- /dev/null +++ b/report/outline/templates/activity.mustache @@ -0,0 +1,63 @@ +{{! + 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}} + diff --git a/report/outline/templates/delegatedsection.mustache b/report/outline/templates/delegatedsection.mustache new file mode 100644 index 00000000000..979458aba4a --- /dev/null +++ b/report/outline/templates/delegatedsection.mustache @@ -0,0 +1,48 @@ +{{! + 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}} + + diff --git a/report/outline/templates/report.mustache b/report/outline/templates/report.mustache new file mode 100644 index 00000000000..6652de3359b --- /dev/null +++ b/report/outline/templates/report.mustache @@ -0,0 +1,86 @@ +{{! + 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}} +
diff --git a/report/outline/templates/section.mustache b/report/outline/templates/section.mustache new file mode 100644 index 00000000000..d67654f640c --- /dev/null +++ b/report/outline/templates/section.mustache @@ -0,0 +1,48 @@ +{{! + 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}} + + diff --git a/theme/boost/scss/moodle/reports.scss b/theme/boost/scss/moodle/reports.scss index 79289a17eee..cad9c3e8014 100644 --- a/theme/boost/scss/moodle/reports.scss +++ b/theme/boost/scss/moodle/reports.scss @@ -12,3 +12,25 @@ #page-report-participation-index .participationselectform div label[for=menuinstanceid] { margin-left: 0; // No left margin for LTR. } + +// Outline report styles +#page-report-outline-index .font-lg { + font-size: $font-size-base * 1.1; +} +#page-report-outline-index .generaltable tbody { + tr { + background-color: $white; + } + tr.section { + padding-left: map-get($spacers, 5); + h3 { + font-size: $h5-font-size; + } + h4 { + font-size: $font-size-base * 1.1; + } + } + td.delegated { + padding-left: $spacer * 3.5; + } +} diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 21235984334..f0bc8c06201 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -34986,6 +34986,26 @@ img.userpicture { margin-left: 0; } +#page-report-outline-index .font-lg { + font-size: 1.03125rem; +} + +#page-report-outline-index .generaltable tbody tr { + background-color: #fff; +} +#page-report-outline-index .generaltable tbody tr.section { + padding-left: 2rem; +} +#page-report-outline-index .generaltable tbody tr.section h3 { + font-size: 1.171875rem; +} +#page-report-outline-index .generaltable tbody tr.section h4 { + font-size: 1.03125rem; +} +#page-report-outline-index .generaltable tbody td.delegated { + padding-left: 3.5rem; +} + .path-backup .mform { /* These are long labels with checkboxes on the right. */ } diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 635c48289e5..2551635fa50 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -34986,6 +34986,26 @@ img.userpicture { margin-left: 0; } +#page-report-outline-index .font-lg { + font-size: 1.03125rem; +} + +#page-report-outline-index .generaltable tbody tr { + background-color: #fff; +} +#page-report-outline-index .generaltable tbody tr.section { + padding-left: 2rem; +} +#page-report-outline-index .generaltable tbody tr.section h3 { + font-size: 1.171875rem; +} +#page-report-outline-index .generaltable tbody tr.section h4 { + font-size: 1.03125rem; +} +#page-report-outline-index .generaltable tbody td.delegated { + padding-left: 3.5rem; +} + .path-backup .mform { /* These are long labels with checkboxes on the right. */ }