From bd7cc81be69091b2efcc8dab27ed4d910478edbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikel=20Mart=C3=ADn?= Date: Mon, 10 Jul 2023 16:03:06 +0200 Subject: [PATCH] MDL-78744 completion: Split activity_information in completion and dates Deprecate core_course\output\activity_information class and move its code to two new classes: core_course\output\activity_completion and core_course\output\activity_dates. In this process refactor activity_completion improving readability and and also deprecate renderer_base::activity_information() and core_course_renderer::render_activity_information(). --- course/classes/output/activity_completion.php | 156 ++++++++++++++++++ course/classes/output/activity_dates.php | 72 ++++++++ .../classes/output/activity_information.php | 10 ++ .../task/content_notification_task.php | 4 +- .../classes/output/local/content/cm.php | 24 ++- .../output/local/content/cm/completion.php | 30 +--- .../local/content/cm/activity.mustache | 8 +- course/renderer.php | 5 +- course/upgrade.txt | 5 + lib/classes/output/activity_header.php | 13 +- lib/outputrenderers.php | 3 + 11 files changed, 297 insertions(+), 33 deletions(-) create mode 100644 course/classes/output/activity_completion.php create mode 100644 course/classes/output/activity_dates.php diff --git a/course/classes/output/activity_completion.php b/course/classes/output/activity_completion.php new file mode 100644 index 00000000000..ffa74ce6175 --- /dev/null +++ b/course/classes/output/activity_completion.php @@ -0,0 +1,156 @@ +. + +namespace core_course\output; + +use cm_info; +use core_availability\info; +use core_completion\cm_completion_details; +use core_user; +use core_user\fields; +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * The activity completion renderable class. + * + * @package core_course + * @copyright 2023 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activity_completion implements renderable, templatable { + + /** + * Constructor. + * + * @param cm_info $cminfo The course module information. + * @param cm_completion_details $cmcompletion The course module information. + */ + public function __construct( + protected cm_info $cminfo, + protected cm_completion_details $cmcompletion + ) { + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output Renderer base. + * @return stdClass + */ + public function export_for_template(renderer_base $output): stdClass { + global $CFG; + + $overallcompletion = $this->cmcompletion->get_overall_completion(); + $overrideby = $this->get_overrideby(); + $course = $this->cminfo->get_course(); + + // Whether the completion of this activity controls the availability of other activities/sections in the course. + // An activity with manual completion tracking which is used to enable access to other activities/sections in + // the course needs to refresh the page after having its completion state toggled. This withavailability flag will enable + // this functionality on the course homepage. Otherwise, the completion toggling will just happen normally via ajax. + if ($this->cmcompletion->has_completion() && $this->cmcompletion->is_manual()) { + $withavailability = !empty($CFG->enableavailability) && info::completion_value_used($course, $this->cminfo->id); + } + + return (object) [ + 'cmid' => $this->cminfo->id, + 'activityname' => $this->cminfo->get_formatted_name(), + 'uservisible' => $this->cminfo->uservisible, + 'hascompletion' => $this->cmcompletion->has_completion(), + 'isautomatic' => $this->cmcompletion->is_automatic(), + 'ismanual' => $this->cmcompletion->is_manual(), + 'showmanualcompletion' => $this->cmcompletion->show_manual_completion(), + 'istrackeduser' => $this->cmcompletion->is_tracked_user(), + 'overallcomplete' => $overallcompletion == COMPLETION_COMPLETE, + 'overallincomplete' => $overallcompletion == COMPLETION_INCOMPLETE, + 'withavailability' => $withavailability ?? false, + 'overrideby' => $overrideby, + 'completiondetails' => $this->get_completion_details($overrideby), + 'accessibledescription' => $this->get_accessible_description($overrideby, $overallcompletion), + ]; + } + + /** + * Returns the name of the user overriding the completion condition, if available. + * + * @return string + */ + private function get_overrideby(): string { + $overrideby = $this->cmcompletion->overridden_by(); + if (!empty($overrideby)) { + $userfields = fields::for_name(); + $overridebyrecord = core_user::get_user($overrideby, 'id ' . $userfields->get_sql()->selects, MUST_EXIST); + return fullname($overridebyrecord); + } + return ''; + } + + /** + * Returns automatic completion details + * + * @param string $overrideby The name of the user overriding the completion condition, if available. + * @return array + */ + private function get_completion_details($overrideby): array { + $details = []; + + foreach ($this->cmcompletion->get_details() as $key => $detail) { + $detail->key = $key; + $detail->statuscomplete = in_array($detail->status, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]); + $detail->statuscompletefail = $detail->status == COMPLETION_COMPLETE_FAIL; + // This is not used by core themes but may be needed in custom themes. + $detail->statuscompletepass = $detail->status == COMPLETION_COMPLETE_PASS; + $detail->statusincomplete = $detail->status == COMPLETION_INCOMPLETE; + + // Add an accessible description to be used for title and aria-label attributes for overridden completion details. + if ($overrideby) { + $setbydata = (object)[ + 'condition' => $detail->description, + 'setby' => $overrideby, + ]; + $overridestatus = $detail->statuscomplete ? 'done' : 'todo'; + $detail->accessibledescription = get_string('completion_setby:auto:' . $overridestatus, 'course', $setbydata); + } + + unset($detail->status); + $details[] = $detail; + } + return $details; + } + + /** + * Returns the accessible description for manual completions with overridden completion state. + * + * @param string $overrideby The name of the user overriding the completion condition, if available. + * @param int $overallcompletion The overall completion state of the activity. + * @return string + */ + private function get_accessible_description($overrideby, $overallcompletion): string { + if ($this->cmcompletion->is_manual() && $overrideby) { + $setbydata = (object)[ + 'activityname' => $this->cminfo->get_formatted_name(), + 'setby' => $overrideby, + ]; + $isoverallcompleted = $overallcompletion == COMPLETION_COMPLETE; + $setbylangkey = $isoverallcompleted ? 'completion_setby:manual:done' : 'completion_setby:manual:markdone'; + return get_string($setbylangkey, 'course', $setbydata); + } + return ''; + } +} diff --git a/course/classes/output/activity_dates.php b/course/classes/output/activity_dates.php new file mode 100644 index 00000000000..3d00a6fe964 --- /dev/null +++ b/course/classes/output/activity_dates.php @@ -0,0 +1,72 @@ +. + +namespace core_course\output; + +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * The activity dates renderable class. + * + * @package core_course + * @copyright 2023 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activity_dates implements renderable, templatable { + + /** + * Constructor. + * + * @param array $activitydates The activity dates. + */ + public function __construct( + protected array $activitydates + ) { + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output Renderer base. + * @return stdClass + */ + public function export_for_template(renderer_base $output): stdClass { + $activitydates = []; + foreach ($this->activitydates as $date) { + if (empty($date['relativeto'])) { + $date['datestring'] = userdate($date['timestamp'], get_string('strftimedaydatetime', 'core_langconfig')); + } else { + $diffstr = get_time_interval_string($date['timestamp'], $date['relativeto']); + if ($date['timestamp'] >= $date['relativeto']) { + $date['datestring'] = get_string('relativedatessubmissionduedateafter', 'core_course', + ['datediffstr' => $diffstr]); + } else { + $date['datestring'] = get_string('relativedatessubmissionduedatebefore', 'core_course', + ['datediffstr' => $diffstr]); + } + } + $activitydates[] = $date; + } + + return (object) [ + 'hasdates' => !empty($this->activitydates), + 'activitydates' => $activitydates, + ]; + } +} diff --git a/course/classes/output/activity_information.php b/course/classes/output/activity_information.php index 18b456b93e5..779cc91b797 100644 --- a/course/classes/output/activity_information.php +++ b/course/classes/output/activity_information.php @@ -41,6 +41,9 @@ use templatable; /** * The activity information renderable class. * + * @deprecated since Moodle 4.3 MDL-78744 + * @todo MDL-78926 This class will be deleted in Moodle 4.7 + * * @package core_course * @copyright 2021 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -59,11 +62,14 @@ class activity_information implements renderable, templatable { /** * Constructor. * + * @deprecated since Moodle 4.3 + * * @param cm_info $cminfo The course module information. * @param cm_completion_details $cmcompletion The course module information. * @param array $activitydates The activity dates. */ public function __construct(cm_info $cminfo, cm_completion_details $cmcompletion, array $activitydates) { + debugging('activity_information class is deprecated. Use activity_completion and activity_dates instead.', DEBUG_DEVELOPER); $this->cminfo = $cminfo; $this->cmcompletion = $cmcompletion; $this->activitydates = $activitydates; @@ -72,10 +78,14 @@ class activity_information implements renderable, templatable { /** * Export this data so it can be used as the context for a mustache template. * + * @deprecated since Moodle 4.3 + * * @param renderer_base $output Renderer base. * @return stdClass */ public function export_for_template(renderer_base $output): stdClass { + debugging('activity_information class is deprecated. Use activity_completion and activity_dates instead.', DEBUG_DEVELOPER); + $data = $this->build_completion_data(); $data->cmid = $this->cminfo->id; diff --git a/course/classes/task/content_notification_task.php b/course/classes/task/content_notification_task.php index 073a86208be..5594e97281f 100644 --- a/course/classes/task/content_notification_task.php +++ b/course/classes/task/content_notification_task.php @@ -120,11 +120,9 @@ class content_notification_task extends adhoc_task { } $eventdata->customdata = $eventcustomdata; - $completion = \core_completion\cm_completion_details::get_instance($cm, $user->id); $activitydates = \core\activity_dates::get_dates_for_module($cm, $user->id); if (!empty($activitydates)) { - $activityinfo = new \core_course\output\activity_information($cm, $completion, $activitydates); - $data = $activityinfo->export_for_template($OUTPUT); + $data = (new \core_course\output\activity_dates($activitydates))->export_for_template($OUTPUT); foreach ($data->activitydates as $date) { $eventdata->fullmessagehtml .= \html_writer::div($date['label'] . ' ' . $date['datestring']); } diff --git a/course/format/classes/output/local/content/cm.php b/course/format/classes/output/local/content/cm.php index 3a1beddacf9..89063a63937 100644 --- a/course/format/classes/output/local/content/cm.php +++ b/course/format/classes/output/local/content/cm.php @@ -126,6 +126,7 @@ class cm implements named_templatable, renderable { $haspartials['availability'] = $this->add_availability_data($data, $output); $haspartials['alternative'] = $this->add_alternative_content_data($data, $output); $haspartials['completion'] = $this->add_completion_data($data, $output); + $haspartials['dates'] = $this->add_dates_data($data, $output); $haspartials['editor'] = $this->add_editor_data($data, $output); $haspartials['groupmode'] = $this->add_groupmode_data($data, $output); $haspartials['visibility'] = $this->add_visibility_data($data, $output); @@ -205,6 +206,26 @@ class cm implements named_templatable, renderable { return !empty($data->altcontent); } + /** + * Add activity dates information to the data structure. + * + * @param stdClass $data the current cm data reference + * @param renderer_base $output typically, the renderer that's calling this function + * @return bool the module has completion information + */ + protected function add_dates_data(stdClass &$data, renderer_base $output): bool { + global $USER; + $course = $this->mod->get_course(); + if (!$course->showactivitydates) { + return false; + } + $activitydates = \core\activity_dates::get_dates_for_module($this->mod, $USER->id); + $templatedata = new \core_course\output\activity_dates($activitydates); + $data->dates = $templatedata->export_for_template($output); + + return $data->dates->hasdates; + } + /** * Add activity completion information to the data structure. * @@ -216,7 +237,7 @@ class cm implements named_templatable, renderable { $completion = new $this->completionclass($this->format, $this->section, $this->mod); $templatedata = $completion->export_for_template($output); if ($templatedata) { - $data->activityinfo = $templatedata; + $data->completion = $templatedata; return true; } return false; @@ -256,6 +277,7 @@ class cm implements named_templatable, renderable { $this->mod->has_custom_cmlist_item() && !$haspartials['availability'] && !$haspartials['completion'] && + !$haspartials['dates'] && !$haspartials['groupmode'] && !isset($data->modhiddenfromstudents) && !isset($data->modstealth) && diff --git a/course/format/classes/output/local/content/cm/completion.php b/course/format/classes/output/local/content/cm/completion.php index 6e001f70bb5..9c69dde4091 100644 --- a/course/format/classes/output/local/content/cm/completion.php +++ b/course/format/classes/output/local/content/cm/completion.php @@ -17,14 +17,13 @@ namespace core_courseformat\output\local\content\cm; use cm_info; +use core_course\output\activity_completion; use section_info; use renderable; use stdClass; -use core\activity_dates; use core\output\named_templatable; use core\output\local\dropdown\dialog as dropdown_dialog; use core_completion\cm_completion_details; -use core_course\output\activity_information; use core_courseformat\base as course_format; use core_courseformat\output\local\courseformat_named_templatable; @@ -67,31 +66,20 @@ class completion implements named_templatable, renderable { $showcompletionconditions = $course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS; $completiondetails = cm_completion_details::get_instance($this->mod, $USER->id, $showcompletionconditions); - $activitydates = []; - if ($course->showactivitydates) { - $activitydates = activity_dates::get_dates_for_module($this->mod, $USER->id); - } - - // There are activity dates to be shown; or - // Completion info needs to be displayed - // * The activity tracks completion; AND - // * The showcompletionconditions setting is enabled OR an activity that tracks manual - // completion needs the manual completion button to be displayed on the course homepage. - $showcompletioninfo = $completiondetails->has_completion() && ($showcompletionconditions || - (!$completiondetails->is_automatic() && $completiondetails->show_manual_completion())); - if (!$showcompletioninfo && empty($activitydates)) { + $showcompletioninfo = $completiondetails->has_completion() && + ($showcompletionconditions || $completiondetails->show_manual_completion()); + if (!$showcompletioninfo) { return null; } - $activityinfodata = (object) [ 'hasdates' => false, 'hascompletion' => false ]; - $activityinfo = new activity_information($this->mod, $completiondetails, $activitydates); - $activityinfodata = $activityinfo->export_for_template($output); + $completion = new activity_completion($this->mod, $completiondetails); + $completiondata = $completion->export_for_template($output); - if ($activityinfodata->isautomatic || ($activityinfodata->ismanual && !$activityinfodata->istrackeduser)) { - $activityinfodata->completiondialog = $this->get_completion_dialog($output, $activityinfodata); + if ($completiondata->isautomatic || ($completiondata->ismanual && !$completiondata->istrackeduser)) { + $completiondata->completiondialog = $this->get_completion_dialog($output, $completiondata); } - return $activityinfodata; + return $completiondata; } /** diff --git a/course/format/templates/local/content/cm/activity.mustache b/course/format/templates/local/content/cm/activity.mustache index e7b67f6d9b5..73d8233a849 100644 --- a/course/format/templates/local/content/cm/activity.mustache +++ b/course/format/templates/local/content/cm/activity.mustache @@ -88,7 +88,7 @@ {{/hasname}} {{! Dates }} - {{#activityinfo}} + {{#dates}} {{#hasdates}}
{{#activitydates}} @@ -98,7 +98,7 @@ {{/activitydates}}
{{/hasdates}} - {{/activityinfo}} + {{/dates}} {{! Visibility }} {{$ core_courseformat/local/content/cm/badges }} @@ -118,7 +118,7 @@ {{/groupmodeinfo}} {{! Completion }} - {{#activityinfo}} + {{#completion}} {{#hascompletion}}
{{$ core_courseformat/local/content/cm/activity_info}} @@ -126,7 +126,7 @@ {{/ core_courseformat/local/content/cm/activity_info}}
{{/hascompletion}} - {{/activityinfo}} + {{/completion}} {{! Action menu }} {{#controlmenu}} diff --git a/course/renderer.php b/course/renderer.php index f86e24d1c9f..df627474f38 100644 --- a/course/renderer.php +++ b/course/renderer.php @@ -306,7 +306,7 @@ class core_course_renderer extends plugin_renderer_base { * @deprecated since Moodle 3.11 */ public function course_section_cm_completion() { - throw new coding_exception(__FUNCTION__ . ' is deprecated. Use the activity_information output component instead.'); + throw new coding_exception(__FUNCTION__ . ' is deprecated. Use the activity_completion output component instead.'); } /** @@ -1876,10 +1876,13 @@ class core_course_renderer extends plugin_renderer_base { * * Defer to template. * + * @deprecated since Moodle 4.3 MDL-78744 + * @todo MDL-78926 This method will be deleted in Moodle 4.7 * @param \core_course\output\activity_information $page * @return string html for the page */ public function render_activity_information(\core_course\output\activity_information $page) { + debugging('render_activity_information method is deprecated.', DEBUG_DEVELOPER); $data = $page->export_for_template($this->output); return $this->output->render_from_template('core_course/activity_info', $data); } diff --git a/course/upgrade.txt b/course/upgrade.txt index 72e6adb8218..d6847871a44 100644 --- a/course/upgrade.txt +++ b/course/upgrade.txt @@ -7,6 +7,11 @@ information provided here is intended especially for developers. the activity badge when the module implements it. * prepare_new_moduleinfo_data() now accepts a parameter "suffix" that will be added to the name of the completion rules. * The method core_course_bulk_activity_completion_renderer:: edit_default_completion() has been deprecated and will be removed. +* The `core_course\output\activity_information` output class has been deprecated. Use `core_course\output\activity_completion` + and `core_course\output\activity_dates` instead. + Alongside with that, the following methods have been deprecated too: + - `core_course_renderer::render_activity_information()` + - `renderer_base::activity_information()` === 4.2 === * course/mod.php now accepts parameter beforemod for adding course modules. It contains the course module id diff --git a/lib/classes/output/activity_header.php b/lib/classes/output/activity_header.php index 7b23b69cf7b..b2fa9522db1 100644 --- a/lib/classes/output/activity_header.php +++ b/lib/classes/output/activity_header.php @@ -175,11 +175,18 @@ class activity_header implements \renderable, \templatable { return ['title' => '']; } - $completion = null; + $activityinfo = null; if (!$this->hidecompletion) { $completiondetails = \core_completion\cm_completion_details::get_instance($this->page->cm, $this->user->id); $activitydates = \core\activity_dates::get_dates_for_module($this->page->cm, $this->user->id); - $completion = $output->activity_information($this->page->cm, $completiondetails, $activitydates); + + $activitycompletion = new \core_course\output\activity_completion($this->page->cm, $completiondetails); + $activitycompletiondata = (array) $activitycompletion->export_for_template($output); + $activitydates = new \core_course\output\activity_dates($activitydates); + $activitydatesdata = (array) $activitydates->export_for_template($output); + $data = array_merge($activitycompletiondata, $activitydatesdata); + + $activityinfo = $output->render_from_template('core_course/activity_info', $data); } $format = course_get_format($this->page->course); @@ -193,7 +200,7 @@ class activity_header implements \renderable, \templatable { return [ 'title' => $this->title, 'description' => $this->description, - 'completion' => $completion, + 'completion' => $activityinfo, 'additional_items' => $this->hideoverflow ? '' : $this->additionalnavitems, ]; } diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 3abf0ae1efb..33d2c10e1e0 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -975,6 +975,8 @@ class core_renderer extends renderer_base { /** * Returns information about an activity. * + * @deprecated since Moodle 4.3 MDL-78744 + * @todo MDL-78926 This method will be deleted in Moodle 4.7 * @param cm_info $cminfo The course module information. * @param cm_completion_details $completiondetails The completion details for this activity module. * @param array $activitydates The dates for this activity module. @@ -982,6 +984,7 @@ class core_renderer extends renderer_base { * @throws coding_exception */ public function activity_information(cm_info $cminfo, cm_completion_details $completiondetails, array $activitydates): string { + debugging('activity_information method is deprecated.', DEBUG_DEVELOPER); if (!$completiondetails->has_completion() && empty($activitydates)) { // No need to render the activity information when there's no completion info and activity dates to show. return '';