diff --git a/.upgradenotes/MDL-85852-2025071709274388.yml b/.upgradenotes/MDL-85852-2025071709274388.yml new file mode 100644 index 00000000000..f8d2093be47 --- /dev/null +++ b/.upgradenotes/MDL-85852-2025071709274388.yml @@ -0,0 +1,16 @@ +issueNumber: MDL-85852 +notes: + core_courseformat: + - message: >- + New needs_filtering_by_groups() and get_groups_for_filtering() had been + created in activityoverviewbase class for a better management of groups + filtering in Activities overview page by activities. + needs_filtering_by_groups() returns whether the user needs to filter by + groups in the current module, and get_groups_for_filtering() returns + which is the filter the user should use with groups API. + type: improved + - message: >- + A new has_error() function has been created in activityoverviewbase + class to raise when a user is trying to check information about a module + set as SEPARATE_GROUPS but the user is not in any group. + type: improved diff --git a/.upgradenotes/MDL-85852-2025071807250090.yml b/.upgradenotes/MDL-85852-2025071807250090.yml new file mode 100644 index 00000000000..8c27d9084df --- /dev/null +++ b/.upgradenotes/MDL-85852-2025071807250090.yml @@ -0,0 +1,8 @@ +issueNumber: MDL-85852 +notes: + core_courseformat: + - message: >- + New optional $nogroupserror parameter has been added to activityname + class constructor. A set_nogroupserror() setter to change the value + after the constructor has been also added. + type: improved diff --git a/public/course/format/classes/activityoverviewbase.php b/public/course/format/classes/activityoverviewbase.php index beb95a9992f..33dc299ded6 100644 --- a/public/course/format/classes/activityoverviewbase.php +++ b/public/course/format/classes/activityoverviewbase.php @@ -46,6 +46,15 @@ abstract class activityoverviewbase { /** @var courseformat $format the course format */ protected courseformat $format; + /** @var ?bool $needsfiltering whether the current user needs to filter by groups or not in the current module */ + protected ?bool $needsfiltering = null; + + /** @var array $groupstofilterby the array of groups to use as parameter for the groups API. Empty array for all groups */ + protected array $groupstofilterby; + + /** @var bool $nogroupserror Whether the user has no permission to view any student */ + protected bool $nogroupserror; + /** * Activity Overview Base class constructor. * @@ -70,6 +79,8 @@ abstract class activityoverviewbase { $this->context = $cm->context; $this->course = $cm->get_course(); $this->format = courseformat::instance($this->course); + + $this->nogroupserror = ($this->needs_filtering_by_groups() && empty($this->get_groups_for_filtering())); } /** @@ -82,6 +93,45 @@ abstract class activityoverviewbase { redirect(overviewpage::get_modname_url($courseid, $modname)); } + /** + * Checks the module's group mode, the user's capabilities and returns + * whether overview page needs to filter by group. + * + * @return bool Whether current user needs to filter by group in the current module. + */ + public function needs_filtering_by_groups(): bool { + if ($this->needsfiltering != null) { + return $this->needsfiltering; + } + if (has_capability('moodle/site:accessallgroups', $this->context)) { + $this->needsfiltering = false; + return $this->needsfiltering; + } + $groupmode = groups_get_activity_groupmode($this->cm); + if ($groupmode != SEPARATEGROUPS) { + $this->needsfiltering = false; + return $this->needsfiltering; + } + $this->needsfiltering = true; + return $this->needsfiltering; + } + + /** + * Returns an array of the groups to filter by using groups API. + * Empty array for non-filtering by groups. + * + * @return array Groups to filter by. + */ + public function get_groups_for_filtering(): array { + if (!$this->needsfiltering) { + return []; + } + if (!isset($this->groupstofilterby)) { + $this->groupstofilterby = groups_get_activity_allowed_groups($this->cm); + } + return $this->groupstofilterby; + } + /** * Get the plugin specific overview items for the activity. * @@ -102,7 +152,7 @@ abstract class activityoverviewbase { return new overviewitem( name: get_string('name'), value: $this->cm->name, - content: new activityname($this->cm), + content: new activityname($this->cm, $this->nogroupserror), ); } @@ -255,4 +305,13 @@ abstract class activityoverviewbase { } return []; } + + /** + * Wether the activity should show an error because the user is not in any group and they should be. + * + * @return bool nogroupserror property. + */ + public function has_error(): bool { + return $this->nogroupserror; + } } diff --git a/public/course/format/classes/output/local/overview/activityname.php b/public/course/format/classes/output/local/overview/activityname.php index 2f2754b9796..07477b57176 100644 --- a/public/course/format/classes/output/local/overview/activityname.php +++ b/public/course/format/classes/output/local/overview/activityname.php @@ -39,9 +39,22 @@ class activityname implements renderable, named_templatable { public function __construct( /** @var cm_info The course module. */ protected cm_info $cm, + /** @var bool Should show no groups error */ + protected bool $nogroupserror = false, ) { } + /** + * nogroupserror property setter + * + * @param bool $nogroupserror New value fpr nogroupserror property + * @return $this + */ + public function set_nogroupserror(bool $nogroupserror): self { + $this->nogroupserror = $nogroupserror; + return $this; + } + /** * Export this data so it can be used as the context for a mustache template. * @@ -59,6 +72,7 @@ class activityname implements renderable, named_templatable { 'activityurl' => $cm->url, 'hidden' => empty($cm->visible), 'stealth' => $cm->is_stealth(), + 'nogroupserror' => $this->nogroupserror, ]; if ($format->uses_sections()) { $result->sectiontitle = $format->get_section_name($section); diff --git a/public/course/format/classes/output/local/overview/overviewtable.php b/public/course/format/classes/output/local/overview/overviewtable.php index 62199272e11..59c2b7f43e2 100644 --- a/public/course/format/classes/output/local/overview/overviewtable.php +++ b/public/course/format/classes/output/local/overview/overviewtable.php @@ -20,6 +20,7 @@ use core\output\named_templatable; use core\output\renderable; use core\output\renderer_base; use core\plugin_manager; +use core_courseformat\activityoverviewbase; use core_courseformat\local\overview\overviewitem; use core_courseformat\local\overview\overviewfactory; use cm_info; @@ -55,7 +56,7 @@ class overviewtable implements renderable, named_templatable { #[\Override] public function export_for_template(renderer_base $output): stdClass { - $activities = $this->load_all_overviews_from_each_activity($output); + $activities = $this->load_all_overviews_from_each_activity(); $headers = $this->export_headers(); $result = (object) [ 'caption' => $this->get_table_caption(), @@ -120,6 +121,7 @@ class overviewtable implements renderable, named_templatable { } $result[] = [ 'cmid' => $activity['cmid'], + 'haserror' => $activity['haserror'], 'overviews' => $items, ]; } @@ -129,18 +131,19 @@ class overviewtable implements renderable, named_templatable { /** * Loads all overviews from activities for the given course and module name. * - * @param renderer_base $output * @return array An array of overviews. */ - private function load_all_overviews_from_each_activity(renderer_base $output): array { + private function load_all_overviews_from_each_activity(): array { $result = []; foreach ($this->get_related_course_modules() as $cm) { if (!$this->is_cm_displayable($cm)) { continue; } + $overview = overviewfactory::create($cm); $result[] = [ 'cmid' => $cm->id, - 'overviews' => $this->load_overview_items_from_activity($output, $cm), + 'haserror' => $overview->has_error(), + 'overviews' => $this->load_overview_items_from_activity($overview), ]; } return $result; @@ -218,21 +221,21 @@ class overviewtable implements renderable, named_templatable { /** * Loads overview items from a given activity. * - * @param renderer_base $output - * @param cm_info $cm + * @param activityoverviewbase $overview * @return array An associative array containing the overview items for the activity. */ - private function load_overview_items_from_activity(renderer_base $output, cm_info $cm): array { - global $PAGE; - $overview = overviewfactory::create($cm); + private function load_overview_items_from_activity(activityoverviewbase $overview): array { + if ($overview->has_error()) { + return ['name' => $overview->get_name_overview()]; + } $row = [ - 'name' => $overview->get_name_overview($output), - 'duedate' => $overview->get_due_date_overview($output), - 'completion' => $overview->get_completion_overview($output), + 'name' => $overview->get_name_overview(), + 'duedate' => $overview->get_due_date_overview(), + 'completion' => $overview->get_completion_overview(), ]; - $row = array_merge($row, $overview->get_extra_overview_items($output)); + $row = array_merge($row, $overview->get_extra_overview_items()); $gradeitems = $overview->get_grades_overviews(); if (!empty($gradeitems)) { @@ -242,7 +245,7 @@ class overviewtable implements renderable, named_templatable { } // Actions are always the last column, if any. - $row['actions'] = $overview->get_actions_overview($output); + $row['actions'] = $overview->get_actions_overview(); $row = array_filter($row, function ($item) { return $item !== null; diff --git a/public/course/format/templates/local/overview/activityname.mustache b/public/course/format/templates/local/overview/activityname.mustache index 746587e0624..fe2743f9e15 100644 --- a/public/course/format/templates/local/overview/activityname.mustache +++ b/public/course/format/templates/local/overview/activityname.mustache @@ -25,7 +25,8 @@ "activityurl": "http://moodle.com", "sectiontitle": "Section title", "visible": true, - "stealth": false + "stealth": false, + "nogroupserror": false } }}