MDL-81771 report_outline: Add subsections to course Activity report

This commit is contained in:
Amaia Anabitarte 2024-06-19 13:57:07 +02:00
parent f9497ceade
commit 6912ddb3c6
12 changed files with 640 additions and 116 deletions

View File

@ -0,0 +1,5 @@
issueNumber: MDL-81771
notes:
core_report:
- message: New coursestructure output general class has been created
type: improved

View File

@ -0,0 +1,179 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace core_report\output;
/**
* Course sections, subsections and activities structure for reports.
*
* @package core_report
* @copyright 2024 Amaia Anabitarte <amaia@moodle.com>
* @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;
}
}

View File

@ -0,0 +1,145 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace report_outline\output;
use core_report\output\coursestructure;
use course_modinfo;
/**
* Activities list page.
*
* @package report_outline
* @copyright 2024 Amaia Anabitarte <amaia@moodle.com>
* @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;
}
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@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"
}]
}
}}
<tr>
{{#activitycolumn}}
<td class="cell activityname {{#indelegated}}delegated{{/indelegated}}{{^indelegated}}pl-5{{/indelegated}}">
{{{ activityicon }}}
{{#link}}<a href="{{ link }}">{{/link}}{{{ text }}}{{#link}}</a>{{/link}}
{{^visible}}
<div class="activity-badges my-1" data-region="visibility">
<span class="badge rounded-pill bg-secondary text-dark">{{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}</span>
</div>
{{/visible}}
</td>
{{/activitycolumn}}
{{#cells}}
<td class="cell {{ activityclass }}">
{{#text}}{{{ text }}}{{/text}}
{{#link}}
{{> core/action_link}}
{{/link}}
</td>
{{/cells}}
</tr>

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@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"
}
}}
<tr class="{{ class }}">
<td class="cell lastcol pl-5"{{#headerscount}} colspan="{{headerscount}}"{{/headerscount}}>
<h4>{{{ text }}}</h4>
{{^visible}}
<div class="activity-badges my-1" data-region="visibility">
<span class="badge rounded-pill bg-secondary text-dark">{{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}</span>
</div>
{{/visible}}
</td>
</tr>

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@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"
}]
}
}
}}
<div class="loginfo font-lg my-5 pb-2">{{#str}} computedfromlogs, admin, {{{ minlog }}} {{/str}}</div>
<div class="table-responsive">
{{#table}}
<table class="{{ class }}"{{#id}} id = "{{ id }}"{{/id}}>
<thead>
<tr>
{{#headers}}
<th class="header font-lg" style="" scope="col">{{{ . }}}</th>
{{/headers}}
</tr>
</thead>
<tbody>
{{#activities}}
{{#issection}}
{{>report_outline/section }}
{{/issection}}
{{#isdelegated}}
{{>report_outline/delegatedsection }}
{{/isdelegated}}
{{#isactivity}}
{{>report_outline/activity }}
{{/isactivity}}
{{/activities}}
</tbody>
</table>
{{/table}}
</div>

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@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"
}
}}
<tr class="{{ class }}">
<td class="cell lastcol"{{#headerscount}} colspan="{{headerscount}}"{{/headerscount}}>
<h3>{{{ text }}}</h3>
{{^visible}}
<div class="activity-badges my-1" data-region="visibility">
<span class="badge rounded-pill bg-secondary text-dark">{{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}</span>
</div>
{{/visible}}
</td>
</tr>

View File

@ -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;
}
}

View File

@ -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. */
}

View File

@ -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. */
}