diff --git a/.upgradenotes/MDL-83838-2024121110301123.yml b/.upgradenotes/MDL-83838-2024121110301123.yml new file mode 100644 index 00000000000..49c49bce160 --- /dev/null +++ b/.upgradenotes/MDL-83838-2024121110301123.yml @@ -0,0 +1,10 @@ +issueNumber: MDL-83838 +notes: + report_progress: + - message: > + Add download widget for report to download multiple formats. + type: improved + - message: > + report_progress\output\renderer::render_download_buttons + No replacement. We no longer need to render the download custom button links. + type: deprecated diff --git a/public/report/progress/classes/output/renderer.php b/public/report/progress/classes/output/renderer.php index a287c1a0c30..b14acc9a4bf 100644 --- a/public/report/progress/classes/output/renderer.php +++ b/public/report/progress/classes/output/renderer.php @@ -139,8 +139,11 @@ class renderer extends plugin_renderer_base { * @param \moodle_url $url The base url. * @return string HTML * @throws \coding_exception + * @deprecated since 5.1 MDL-83838 - Please do not use this function any more. + * #[\core\attribute\deprecated(null, reason: 'It is no longer used', since: '5.1', mdl: 'MDL-83838')] */ public function render_download_buttons(\moodle_url $url): string { + \core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]); $downloadurl = fullclone($url); $downloadurl->remove_params(['page']); $downloadurl->param('format', 'csv'); diff --git a/public/report/progress/index.php b/public/report/progress/index.php index 640d589a2f3..0ac3d299ae1 100644 --- a/public/report/progress/index.php +++ b/public/report/progress/index.php @@ -41,10 +41,8 @@ $context = context_course::instance($course->id); $sort = optional_param('sort','',PARAM_ALPHA); $firstnamesort = $sort == 'firstname'; -// CSV format -$format = optional_param('format','',PARAM_ALPHA); -$excel = $format == 'excelcsv'; -$csv = $format == 'csv' || $excel; +// Download format. +$dataformat = optional_param('dataformat', '', PARAM_ALPHA); // Paging, sorting and filtering. $page = optional_param('page', 0, PARAM_INT); @@ -59,23 +57,13 @@ $activitysection = optional_param('activitysection', -1, PARAM_INT); $userfields = \core_user\fields::for_identity($context); $extrafields = $userfields->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]); $leftcols = 1 + count($extrafields); - -function csv_quote($value) { - global $excel; - if ($excel) { - return core_text::convert('"'.str_replace('"',"'",$value).'"','UTF-8','UTF-16LE'); - } else { - return '"'.str_replace('"',"'",$value).'"'; - } -} - $url = new moodle_url('/report/progress/index.php', array('course'=>$id)); $PAGE->navigation->override_active_url($url); if ($sort !== '') { $url->param('sort', $sort); } -if ($format !== '') { - $url->param('format', $format); +if ($dataformat !== '') { + $url->param('dataformat', $dataformat); } if ($page !== 0) { $url->param('page', $page); @@ -176,68 +164,151 @@ if ($total) { $where_params, $group, $firstnamesort ? 'u.firstname ASC, u.lastname ASC' : 'u.lastname ASC, u.firstname ASC', - $csv ? 0 : helper::COMPLETION_REPORT_PAGE, - $csv ? 0 : $page * helper::COMPLETION_REPORT_PAGE, + $dataformat ? 0 : helper::COMPLETION_REPORT_PAGE, + $dataformat ? 0 : $page * helper::COMPLETION_REPORT_PAGE, $context ); } -if ($csv && $grandtotal && count($activities)>0) { // Only show CSV if there are some users/actvs +if ($dataformat !== '' && $grandtotal && count($activities) > 0) { + $columnnames = ['']; + $alldata = []; - $shortname = format_string($course->shortname, true, array('context' => $context)); - header('Content-Disposition: attachment; filename=progress.'. - preg_replace('/[^a-z0-9-]/','_',core_text::strtolower(strip_tags($shortname))).'.csv'); - // Unicode byte-order mark for Excel - if ($excel) { - header('Content-Type: text/csv; charset=UTF-16LE'); - print chr(0xFF).chr(0xFE); - $sep="\t".chr(0); - $line="\n".chr(0); - } else { - header('Content-Type: text/csv; charset=UTF-8'); - $sep=","; - $line="\n"; + // Adding extra fields column names. + foreach ($extrafields as $field) { + $columnnames[] = \core_user\fields::get_display_name($field); } -} else { - - // Navigation and header - $strreports = get_string("reports"); - $strcompletion = get_string('activitycompletion', 'completion'); - - $PAGE->set_title($strcompletion); - $PAGE->set_heading($course->fullname); - echo $OUTPUT->header(); - - // Print the selected dropdown. - $pluginname = get_string('pluginname', 'report_progress'); - report_helper::print_report_selector($pluginname); - $PAGE->requires->js_call_amd('report_progress/completion_override', 'init', [fullname($USER)]); - - // Handle groups (if enabled). - echo $output->render_groups_select($url, $course, $group); - - // Display include activity filter. - echo $output->render_include_activity_select($url, $activitytypes, $activityinclude); - - // Display activity order options. - echo $output->render_activity_order_select($url, $activityorder); - - // Display section selector. - $modinfo = get_fast_modinfo($course); - $sections = []; - $cmids = array_keys($completion->get_activities()); - foreach ($modinfo->get_sections() as $sectionnum => $section) { - if (empty(array_intersect($section, $cmids))) { - continue; + foreach ($activities as $activity) { + // Handling date display formatting. + if ($activity->completionexpected) { + $datetext = $dataformat + ? userdate($activity->completionexpected, "%F %T") + : userdate($activity->completionexpected, get_string('strftimedate', 'langconfig')); + } else { + $datetext = ''; } - $sectionname = get_section_name($course, $sectionnum); - if (empty($sectionname)) { - $sectionname = get_string('section') . ' ' . $sectionnum; - } - $sections[$sectionnum] = $sectionname; + + // Shorten long names/labels (URLs). + $displayname = format_string($activity->name, true, ['context' => $activity->context]); + + // Add column names for activity. + $columnnames[] = $displayname; + $columnnames[] = $datetext; } - echo $output->render_activity_section_select($url, $activitysection, $sections); + + foreach ($progress as $user) { + $row = []; + $row['fullname'] = fullname($user, has_capability('moodle/site:viewfullnames', $context)); + + // Adding user extra fields to the row. + foreach ($extrafields as $field) { + $row[$field] = $user->{$field}; + } + $i = 0; + foreach ($activities as $activity) { + // Check if there's progress for this activity. + if (array_key_exists($activity->id, $user->progress)) { + $thisprogress = $user->progress[$activity->id]; + $overrideby = $thisprogress->overrideby; + $date = userdate($thisprogress->timemodified, "%F %T"); + $state = $thisprogress->completionstate; + } else { + $overrideby = 0; + $state = COMPLETION_INCOMPLETE; + $date = ''; + } + + // Determine the completion type. + switch ($state) { + case COMPLETION_INCOMPLETE: + $completiontype = 'n' . ($overrideby ? '-override' : ''); + break; + case COMPLETION_COMPLETE: + $completiontype = 'y' . ($overrideby ? '-override' : ''); + break; + case COMPLETION_COMPLETE_PASS: + $completiontype = 'pass'; + break; + case COMPLETION_COMPLETE_FAIL: + $completiontype = 'fail'; + break; + } + + // Describe the completion status. + if ($overrideby) { + $overridebyuser = \core_user::get_user($overrideby, '*', MUST_EXIST); + $describe = get_string('completion-' . $completiontype, 'completion', fullname($overridebyuser)); + } else { + $describe = get_string('completion-' . $completiontype, 'completion'); + } + + $row[$i++] = $describe; + $row[$i++] = $date; + } + // Add the row to alldata. + $alldata[] = $row; + } + // Download the data in the specified format. + \core\dataformat::download_data( + 'progress.' . preg_replace( + '/[^a-z0-9-]/', + '_', + core_text::strtolower( + strip_tags( + format_string( + $course->shortname, + true, + ['context' => $context] + ) + ) + ) + ), + $dataformat, + $columnnames, + $alldata + ); + + // Stop further script execution. + die; } +// Navigation and header. +$strreports = get_string("reports"); +$strcompletion = get_string('activitycompletion', 'completion'); + +$PAGE->set_title($strcompletion); +$PAGE->set_heading($course->fullname); +echo $OUTPUT->header(); + +// Print the selected dropdown. +$pluginname = get_string('pluginname', 'report_progress'); +report_helper::print_report_selector($pluginname); +$PAGE->requires->js_call_amd('report_progress/completion_override', 'init', [fullname($USER)]); + +// Handle groups (if enabled). +echo $output->render_groups_select($url, $course, $group); + +// Display include activity filter. +echo $output->render_include_activity_select($url, $activitytypes, $activityinclude); + +// Display activity order options. +echo $output->render_activity_order_select($url, $activityorder); + +// Display section selector. +$modinfo = get_fast_modinfo($course); +$sections = []; +$cmids = array_keys($completion->get_activities()); +foreach ($modinfo->get_sections() as $sectionnum => $section) { + if (empty(array_intersect($section, $cmids))) { + continue; + } + $sectionname = get_section_name($course, $sectionnum); + if (empty($sectionname)) { + $sectionname = get_string('section') . ' ' . $sectionnum; + } + $sections[$sectionnum] = $sectionname; +} +echo $output->render_activity_section_select($url, $activitysection, $sections); + if (count($activities)==0) { echo $OUTPUT->container(get_string('err_noactivities', 'completion'), 'errorbox errorboxcontent'); @@ -275,47 +346,41 @@ $pagingbar .= $OUTPUT->paging_bar($total, $page, helper::COMPLETION_REPORT_PAGE, // Okay, let's draw the table of progress info, -// Start of table -if (!$csv) { - print '
'; // ugh +// Start of table. +print '
'; // Ugh. - print $pagingbar; +print $pagingbar; - if (!$total) { - echo $OUTPUT->notification(get_string('nothingtodisplay'), 'info', false); - echo $OUTPUT->footer(); - exit; - } +if (!$total) { + echo $OUTPUT->notification(get_string('nothingtodisplay'), 'info', false); + echo $OUTPUT->footer(); + exit; +} print '
'; print ''; print ''; - // User heading / sort option - print ''; - - // Print user identity columns - foreach ($extrafields as $field) { - echo ''; - } +$sorturl = fullclone($url); +if ($firstnamesort) { + $sorturl->param('sort', 'lastname'); + $sortlink = html_writer::link($sorturl, get_string('lastname')); + print + get_string('firstname') . " / $sortlink"; } else { - foreach ($extrafields as $field) { - echo $sep . csv_quote(\core_user\fields::get_display_name($field)); - } + $sorturl->param('sort', 'firstname'); + $sortlink = html_writer::link($sorturl, get_string('firstname')); + print "$sortlink / " . get_string('lastname'); +} +print ''; + +// Print user identity columns. +foreach ($extrafields as $field) { + echo ''; } // Activities @@ -324,7 +389,7 @@ foreach($activities as $activity) { $datepassed = $activity->completionexpected && $activity->completionexpected <= time(); $datepassedclass = $datepassed ? 'completion-expired' : ''; - if ($activity->completionexpected && !$csv) { + if ($activity->completionexpected && !$dataformat) { $datetext = userdate($activity->completionexpected, get_string('strftimedate', 'langconfig')); } else { $datetext = get_string('completed'); @@ -332,51 +397,34 @@ foreach($activities as $activity) { // Some names (labels) come URL-encoded and can be very long, so shorten them $displayname = format_string($activity->name, true, array('context' => $activity->context)); - - if ($csv) { - print $sep.csv_quote($displayname).$sep.csv_quote($datetext); - } else { - $shortenedname = shorten_text($displayname); - print ''; + $shortenedname = shorten_text($displayname); + print ''; $formattedactivities[$activity->id] = (object)array( 'datepassedclass' => $datepassedclass, 'displayname' => $displayname, ); } - -if ($csv) { - print $line; -} else { - print ''; -} +print ''; // Row for each user foreach($progress as $user) { // User name - if ($csv) { - print csv_quote(fullname($user, has_capability('moodle/site:viewfullnames', $context))); - foreach ($extrafields as $field) { - echo $sep . csv_quote($user->{$field}); - } - } else { - print ''; - foreach ($extrafields as $field) { - echo ''; - } + print ''; + foreach ($extrafields as $field) { + echo ''; } // Progress for each activity @@ -425,43 +473,32 @@ foreach($progress as $user) { $a->activity = $formattedactivities[$activity->id]->displayname; $fulldescribe=get_string('progress-title','completion',$a); - if ($csv) { - if ($date != '') { - $date = userdate($thisprogress->timemodified, "%F %T"); - } - print $sep.csv_quote($describe).$sep.csv_quote($date); - } else { - $celltext = $OUTPUT->pix_icon('i/' . $completionicon, s($fulldescribe)); - if (has_capability('moodle/course:overridecompletion', $context) && - $state != COMPLETION_COMPLETE_PASS && $state != COMPLETION_COMPLETE_FAIL) { - $newstate = ($state == COMPLETION_COMPLETE) ? COMPLETION_INCOMPLETE : COMPLETION_COMPLETE; - $changecompl = $user->id . '-' . $activity->id . '-' . $newstate; - $url = new moodle_url($PAGE->url, ['sesskey' => sesskey()]); - $celltext = html_writer::link($url, $celltext, array('class' => 'changecompl', 'data-changecompl' => $changecompl, - 'data-activityname' => $a->activity, - 'data-userfullname' => $a->user, - 'data-completiontracking' => $completiontrackingstring, - 'role' => 'button')); - } - print ''; + $celltext = $OUTPUT->pix_icon('i/' . $completionicon, s($fulldescribe)); + if ( + has_capability('moodle/course:overridecompletion', $context) && + $state != COMPLETION_COMPLETE_PASS && $state != COMPLETION_COMPLETE_FAIL + ) { + $newstate = ($state == COMPLETION_COMPLETE) ? COMPLETION_INCOMPLETE : COMPLETION_COMPLETE; + $changecompl = $user->id . '-' . $activity->id . '-' . $newstate; + $url = new moodle_url($PAGE->url, ['sesskey' => sesskey()]); + $celltext = html_writer::link($url, $celltext, ['class' => 'changecompl', 'data-changecompl' => $changecompl, + 'data-activityname' => $a->activity, + 'data-userfullname' => $a->user, + 'data-completiontracking' => $completiontrackingstring, + 'role' => 'button']); } + print ''; } - - if ($csv) { - print $line; - } else { - print ''; - } -} - -if ($csv) { - exit; + print ''; } print '
'; +// User heading / sort option. +print ''; - $sorturl = fullclone($url); - if ($firstnamesort) { - $sorturl->param('sort', 'lastname'); - $sortlink = html_writer::link($sorturl, get_string('lastname')); - print - get_string('firstname') . " / $sortlink"; - } else { - $sorturl->param('sort', 'firstname'); - $sortlink = html_writer::link($sorturl, get_string('firstname')); - print "$sortlink / " . get_string('lastname'); - } - print '' . - \core_user\fields::get_display_name($field) . '' . + \core_user\fields::get_display_name($field) . ''. - ''. - '
'.$shortenedname.'
'. - '
'. - $OUTPUT->image_icon('monologo', get_string('modulename', $activity->modname), $activity->modname) . - '
'. - '
'; - if ($activity->completionexpected) { - print '
'.$datetext.'
'; - } - print '
' . + '' . + '
' . $shortenedname . '
' . + '
' . + $OUTPUT->image_icon('monologo', get_string('modulename', $activity->modname), $activity->modname) . + '
' . + '
'; + if ($activity->completionexpected) { + print '
' . $datetext . '
'; } + print '
' . - fullname($user, has_capability('moodle/site:viewfullnames', $context)) . '' . s($user->{$field}) . '
' . + fullname($user, has_capability('moodle/site:viewfullnames', $context)) . '' . s($user->{$field}) . ''. - $celltext . '' . + $celltext . '
'; print '
'; -echo $output->render_download_buttons($url); - +// Add buttons for exporting report. +if ($grandtotal && count($activities) > 0) { + echo $OUTPUT->download_dataformat_selector(get_string('downloadas', 'table'), $url, 'dataformat', [ + 'course' => $course->id, + ]); +} echo $OUTPUT->footer(); - diff --git a/public/report/progress/tests/behat/activity_completion_report.feature b/public/report/progress/tests/behat/activity_completion_report.feature index 799f98794eb..bcfa0f24920 100644 --- a/public/report/progress/tests/behat/activity_completion_report.feature +++ b/public/report/progress/tests/behat/activity_completion_report.feature @@ -9,20 +9,20 @@ Feature: Teacher can view and override users' activity completion data via the p | fullname | shortname | format | enablecompletion | | Course 1 | C1 | topics | 1 | And the following "activities" exist: - | activity | name | intro | course | idnumber | section | completion | completionview | completionusegrade | assignsubmission_onlinetext_enabled | submissiondrafts | - | assign | my assignment | A1 desc | C1 | assign1 | 0 | 1 | 0 | | 0 | 0 | - | assign | my assignment 2 | A2 desc | C1 | assign2 | 0 | 2 | 1 | | 0 | 0 | - | assign | my assignment 3 | A3 desc | C1 | assign3 | 0 | 2 | 1 | 1 | 1 | 0 | + | activity | name | intro | course | idnumber | section | completion | completionview | completionusegrade | assignsubmission_onlinetext_enabled | submissiondrafts | + | assign | my assignment | A1 desc | C1 | assign1 | 0 | 1 | 0 | | 0 | 0 | + | assign | my assignment 2 | A2 desc | C1 | assign2 | 0 | 2 | 1 | | 0 | 0 | + | assign | my assignment 3 | A3 desc | C1 | assign3 | 0 | 2 | 1 | 1 | 1 | 0 | And the following "users" exist: - | username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic | - | teacher1 | Teacher | One | teacher1@example.com | t1 | | fred | | | - | student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham | + | username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic | + | teacher1 | Teacher | One | teacher1@example.com | t1 | | fred | | | + | student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham | And the following "course enrolments" exist: - | user | course | role | - | teacher1 | C1 | editingteacher | - | student1 | C1 | student | + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | And the following config values are set as admin: - | fullnamedisplay | firstname | + | fullnamedisplay | firstname | | alternativefullnameformat | middlename, alternatename, firstname, lastname | # Course comprising one activity with auto completion (student must view it) and one with manual completion. @@ -108,3 +108,9 @@ Feature: Teacher can view and override users' activity completion data via the p And the manual completion button of "my assignment" overridden by "Teacher" is displayed as "Done" And I toggle the manual completion state of "my assignment" And the manual completion button of "my assignment" is displayed as "Mark as done" + + Scenario: Download button exist activity completion report. + Given I am on the "Course 1" Course page logged in as teacher1 + When I navigate to "Reports > Activity completion" in current page administration + # Without the ability to check the downloaded file, the absence of an exception being thrown here is considered a success. + Then I click on "Download" "button"