MDL-84469 core_course: add fallback activity overview

This commit is contained in:
ferran 2025-02-07 17:54:22 +01:00 committed by Sara Arjona
parent c8d1b8e0c0
commit 488244d12d
No known key found for this signature in database
11 changed files with 455 additions and 94 deletions

View File

@ -37,6 +37,18 @@ class resourceoverview extends \core_courseformat\activityoverviewbase {
* @return overviewitem|null
*/
private function get_extra_type_overview(): ?overviewitem {
// Only resource activities shows the type overview
// because they are aggregated in one table.
$archetype = plugin_supports(
type: 'mod',
name: $this->cm->modname,
feature: FEATURE_MOD_ARCHETYPE,
default: MOD_ARCHETYPE_OTHER
);
if ($archetype != MOD_ARCHETYPE_RESOURCE) {
return null;
}
return new overviewitem(
name: get_string('resource_type'),
value: $this->cm->modfullname,

View File

@ -0,0 +1,113 @@
<?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_courseformat\output\local\overview;
use core\output\action_link;
use core\output\named_templatable;
use core\output\renderable;
use core\output\notification;
use core\plugin_manager;
use core\url;
use stdClass;
/**
* Class missingoverviewnotice
*
* @package core_courseformat
* @copyright 2025 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class missingoverviewnotice implements renderable, named_templatable {
/**
* Constructor.
*
* @param stdClass $course The course object.
* @param string $modname The module name.
*/
public function __construct(
/** @var stdClass $course the course object */
private stdClass $course,
/** @var string $modname the activity module name */
private string $modname,
) {
}
#[\Override]
public function export_for_template(\renderer_base $output): stdClass {
if (!$this->activity_has_overview_integration($this->modname)) {
return $this->export_legacy_overview($output);
}
// The notice is not needed for plugins with overview class.
return (object) [];
}
/**
* Checks if a given activity module has an overview integration.
*
* The method search for an integration class named `\mod_{modname}\course\overview`.
*
* @param string $modname The name of the activity module.
* @return bool True if the activity module has an overview integration, false otherwise.
*/
private function activity_has_overview_integration(string $modname): bool {
$classname = 'mod_' . $modname . '\courseformat\overview';
if ($modname === 'resource') {
$classname = 'core_courseformat\local\overview\resourceoverview';
}
return class_exists($classname);
}
/**
* Exports the legacy overview for a given module.
*
* This export only applies to modules that do not have an overview integration.
*
* @param \renderer_base $output
* @return stdClass
*/
private function export_legacy_overview(
\renderer_base $output,
): stdClass {
$legacyoverview = '/mod/' . $this->modname . '/index.php';
$name = plugin_manager::instance()->plugin_name($this->modname);
$link = new action_link(
url: new url($legacyoverview, ['id' => $this->course->id]),
text: get_string('overview_modname', 'core_course', $name),
);
$notification = new notification(
message: get_string('overview_missing_notice', 'core_course', $output->render($link)),
messagetype: notification::NOTIFY_INFO,
closebutton: false,
title: get_string('overview_missing_title', 'core_course', $name),
titleicon: 'i/circleinfo',
);
return (object) [
'name' => $name,
'shortname' => $this->modname,
'notification' => $notification->export_for_template($output),
'missingoverview' => true,
];
}
#[\Override]
public function get_template_name(\renderer_base $renderer): string {
return 'core_courseformat/local/overview/missingoverviewnotice';
}
}

View File

@ -138,10 +138,6 @@ class overviewpage implements renderable, named_templatable {
string $modname,
string $modfullname
): stdClass {
if (!$this->activity_has_overview_integration($modname)) {
return $this->export_legacy_overview($output, $modname, $modfullname);
}
return (object) [
'fragment' => $this->export_overview_fragment($modname),
'icon' => $this->get_activity_overview_icon($output, $modname),
@ -165,64 +161,6 @@ class overviewpage implements renderable, named_templatable {
return $output->pix_icon('monologo', '', "mod_$modname", ['class' => 'icon iconsize-medium']);
}
/**
* Exports the legacy overview for a given module.
*
* This export only applies to modules that do not have an overview integration.
*
* @param \renderer_base $output
* @param string $modname
* @param string $modfullname
* @return stdClass
*/
private function export_legacy_overview(
\renderer_base $output,
string $modname,
string $modfullname
): stdClass {
if ($modname === 'resource') {
$legacyoverview = 'resources.php';
$message = get_string('overview_missing_title', 'core_course', get_string('resource'));
} else {
$legacyoverview = '/mod/' . $modname . '/index.php';
$pluginman = plugin_manager::instance();
$message = get_string('overview_missing_title', 'core_course', $pluginman->plugin_name($modname));
}
$notification = new notification(
message: get_string('overview_missing_notice', 'core_course'),
messagetype: \core\notification::INFO,
closebutton: false,
title: $message,
titleicon: 'i/circleinfo',
);
return (object)[
'overviewurl' => new url($legacyoverview, ['id' => $this->course->id]),
'icon' => $this->get_activity_overview_icon($output, $modname),
'name' => $modfullname,
'shortname' => $modname,
'notification' => $notification->export_for_template($output),
'open' => in_array($modname, $this->expanded),
];
}
/**
* Checks if a given activity module has an overview integration.
*
* The method search for an integration class named `\mod_{modname}\courseformat\overview`.
*
* @param string $modname The name of the activity module.
* @return bool True if the activity module has an overview integration, false otherwise.
*/
private function activity_has_overview_integration(string $modname): bool {
$classname = 'mod_' . $modname . '\courseformat\overview';
if ($modname === 'resource') {
$classname = 'core_courseformat\local\overview\resourceoverview';
}
return class_exists($classname);
}
/**
* Exports an overview fragment for a given module name.
*

View File

@ -0,0 +1,39 @@
{{!
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 core_courseformat/local/overview/missingoverviewnotice
Message to display when the activity plugin overview is missing.
Example context (json):
{
"name": "Course name",
"overviewurl": "https://moodle.org",
"notification": {
"message": "Notification message",
"level": "info"
},
"missingoverview": true
}
}}
{{#missingoverview}}
<div class="d-flex flex-column justify-content-between mb-3">
{{#notification}}
{{>core/notification_info}}
{{/notification}}
</div>
{{/missingoverview}}

View File

@ -59,23 +59,6 @@
</div>
{{/preloadedcontent}}
{{/fragment}}
{{^fragment}}
<div class="d-flex flex-column justify-content-between">
{{#notification}}
{{>core/notification_info}}
{{/notification}}
<div>
<a
class="btn btn-outline-secondary"
href="{{overviewurl}}"
role="button"
>
{{#str}} goto_overview, course, {{name}} {{/str}}
<span class="ms-2">{{#pix}} i/arrow-right, core {{/pix}}</span>
</a>
</div>
</div>
{{/fragment}}
{{/sectioncontent}}
{{/core/local/collapsable_section}}
{{/elements}}

View File

@ -42,6 +42,7 @@ final class overviewfactory_test extends \advanced_testcase {
*/
public function test_create_resource(
string $resourcetype,
?string $expected,
): void {
$this->resetAfterTest();
$this->setAdminUser();
@ -54,7 +55,7 @@ final class overviewfactory_test extends \advanced_testcase {
$overview = overviewfactory::create($cm);
$this->assertInstanceOf(resourceoverview::class, $overview);
$this->assertInstanceOf($expected, $overview);
}
/**
@ -64,20 +65,87 @@ final class overviewfactory_test extends \advanced_testcase {
*/
public static function create_resource_provider(): array {
return [
// Resource activities.
'book' => [
'resourcetype' => 'book',
'expected' => resourceoverview::class,
],
'folder' => [
'resourcetype' => 'folder',
'expected' => resourceoverview::class,
],
'page' => [
'resourcetype' => 'page',
'expected' => resourceoverview::class,
],
'resource' => [
'resourcetype' => 'resource',
'expected' => resourceoverview::class,
],
'url' => [
'resourcetype' => 'url',
'expected' => resourceoverview::class,
],
// Fallbacks and integrations.
'assign' => [
'resourcetype' => 'assign',
'expected' => \mod_assign\courseformat\overview::class,
],
'bigbluebuttonbn' => [
'resourcetype' => 'bigbluebuttonbn',
'expected' => resourceoverview::class,
],
'choice' => [
'resourcetype' => 'choice',
'expected' => resourceoverview::class,
],
'data' => [
'resourcetype' => 'data',
'expected' => resourceoverview::class,
],
'feedback' => [
'resourcetype' => 'feedback',
'expected' => \mod_feedback\courseformat\overview::class,
],
'forum' => [
'resourcetype' => 'forum',
'expected' => resourceoverview::class,
],
'glossary' => [
'resourcetype' => 'glossary',
'expected' => resourceoverview::class,
],
'h5pactivity' => [
'resourcetype' => 'h5pactivity',
'expected' => resourceoverview::class,
],
'lesson' => [
'resourcetype' => 'lesson',
'expected' => resourceoverview::class,
],
'lti' => [
'resourcetype' => 'lti',
'expected' => resourceoverview::class,
],
'qbank' => [
'resourcetype' => 'qbank',
'expected' => resourceoverview::class,
],
'quiz' => [
'resourcetype' => 'quiz',
'expected' => resourceoverview::class,
],
'scorm' => [
'resourcetype' => 'scorm',
'expected' => resourceoverview::class,
],
'wiki' => [
'resourcetype' => 'wiki',
'expected' => resourceoverview::class,
],
'workshop' => [
'resourcetype' => 'workshop',
'expected' => resourceoverview::class,
],
];
}

View File

@ -56,9 +56,11 @@ final class resourceoverview_test extends \advanced_testcase {
* @covers ::get_extra_type_overview
* @dataProvider get_extra_type_overview_provider
* @param string $resourcetype
* @param string|null $expected
*/
public function test_get_extra_type_overview(
string $resourcetype,
?string $expected,
): void {
$this->resetAfterTest();
$this->setAdminUser();
@ -74,9 +76,14 @@ final class resourceoverview_test extends \advanced_testcase {
$items = $overview->get_extra_overview_items();
$result = $items['type'];
if ($expected === null) {
$this->assertNull($result);
return;
}
$this->assertEquals(get_string('resource_type'), $result->get_name());
$this->assertEquals($cm->modfullname, $result->get_value());
$this->assertEquals($cm->modfullname, $result->get_content());
$this->assertEquals($expected, $result->get_value());
$this->assertEquals($expected, $result->get_content());
}
/**
@ -88,18 +95,72 @@ final class resourceoverview_test extends \advanced_testcase {
return [
'book' => [
'resourcetype' => 'book',
'expected' => 'Book',
],
'folder' => [
'resourcetype' => 'folder',
'expected' => 'Folder',
],
'page' => [
'resourcetype' => 'page',
'expected' => 'Page',
],
'resource' => [
'resourcetype' => 'resource',
'expected' => 'File',
],
'url' => [
'resourcetype' => 'url',
'expected' => 'URL',
],
// Activities without integration.
'bigbluebuttonbn' => [
'resourcetype' => 'bigbluebuttonbn',
'expected' => null,
],
'choice' => [
'resourcetype' => 'choice',
'expected' => null,
],
'data' => [
'resourcetype' => 'data',
'expected' => null,
],
'forum' => [
'resourcetype' => 'forum',
'expected' => null,
],
'glossary' => [
'resourcetype' => 'glossary',
'expected' => null,
],
'h5pactivity' => [
'resourcetype' => 'h5pactivity',
'expected' => null,
],
'lesson' => [
'resourcetype' => 'lesson',
'expected' => null,
],
'lti' => [
'resourcetype' => 'lti',
'expected' => null,
],
'qbank' => [
'resourcetype' => 'qbank',
'expected' => null,
],
'quiz' => [
'resourcetype' => 'quiz',
'expected' => null,
],
'scorm' => [
'resourcetype' => 'scorm',
'expected' => null,
],
'wiki' => [
'resourcetype' => 'wiki',
'expected' => null,
],
];
}

View File

@ -0,0 +1,90 @@
<?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_courseformat\output\local\overview;
/**
* Tests for courseformat
*
* @package core_courseformat
* @category test
* @copyright 2025 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core_courseformat\output\local\overview\missingoverviewnotice
*/
final class missingoverviewnotice_test extends \advanced_testcase {
/**
* Test overview integrations.
*
* @covers ::export_for_template
* @covers ::activity_has_overview_integration
* @dataProvider overview_integrations_provider
* @param string $modname
* @param bool $expectempty
*/
public function test_overview_integrations(
string $modname,
bool $expectempty,
): void {
global $PAGE;
$this->resetAfterTest();
$renderer = $PAGE->get_renderer('core');
$course = $this->getDataGenerator()->create_course();
$missingoverviewnotice = new missingoverviewnotice($course, $modname);
$export = $missingoverviewnotice->export_for_template($renderer);
if ($expectempty) {
$this->assertEquals((object) [], $export);
} else {
$this->assertNotEquals((object) [], $export);
}
}
/**
* Data provider for test_overview_integrations.
*
* @return array
*/
public static function overview_integrations_provider(): array {
return [
'assign' => ['modname' => 'assign', 'expectempty' => true],
'bigbluebuttonbn' => ['modname' => 'bigbluebuttonbn', 'expectempty' => false],
'book' => ['modname' => 'book', 'expectempty' => false],
'choice' => ['modname' => 'choice', 'expectempty' => false],
'data' => ['modname' => 'data', 'expectempty' => false],
'feedback' => ['modname' => 'feedback', 'expectempty' => true],
'folder' => ['modname' => 'folder', 'expectempty' => false],
'forum' => ['modname' => 'forum', 'expectempty' => false],
'glossary' => ['modname' => 'glossary', 'expectempty' => false],
'h5pactivity' => ['modname' => 'h5pactivity', 'expectempty' => false],
'imscp' => ['modname' => 'imscp', 'expectempty' => false],
'label' => ['modname' => 'label', 'expectempty' => false],
'lesson' => ['modname' => 'lesson', 'expectempty' => false],
'lti' => ['modname' => 'lti', 'expectempty' => false],
'page' => ['modname' => 'page', 'expectempty' => false],
'qbank' => ['modname' => 'qbank', 'expectempty' => false],
'quiz' => ['modname' => 'quiz', 'expectempty' => false],
'resource' => ['modname' => 'resource', 'expectempty' => true],
'scorm' => ['modname' => 'scorm', 'expectempty' => false],
'url' => ['modname' => 'url', 'expectempty' => false],
'wiki' => ['modname' => 'wiki', 'expectempty' => false],
'workshop' => ['modname' => 'workshop', 'expectempty' => false],
];
}
}

View File

@ -4868,6 +4868,12 @@ function course_output_fragment_course_overview($args) {
$format = course_get_format($course);
$renderer = $format->get_renderer($PAGE);
// Plugins with not implemented overview table will have an extra link to the index.php.
$overvietableclass = $format->get_output_classname('overview\missingoverviewnotice');
/** @var \core_courseformat\output\local\overview\missingoverviewnotice $output */
$output = new $overvietableclass($course, $modname);
$content .= $renderer->render($output);
$overvietableclass = $format->get_output_classname('overview\\overviewtable');
/** @var \core_courseformat\output\local\overview\overviewtable $output */
$output = new $overvietableclass($course, $modname);

View File

@ -20,14 +20,14 @@ Feature: Users can access the course activities overview page
| activity | course | section | idnumber | name |
| assign | C1 | 1 | 1 | Test assignment name |
Scenario: Teacher can access the course overview page
Scenario: Teacher can navigate to the course overview page
Given I am on the "C1" "Course" page logged in as "teacher1"
When I follow "Activities"
Then I should see "Activities"
And I should see "View all the activities in this course" in the "region-main" "region"
And I should see "Assignments" in the "region-main" "region"
Scenario: Student can access the course overview page
Scenario: Student can navigate to the course overview page
Given I am on the "C1" "Course" page logged in as "student1"
When I follow "Activities"
Then I should see "Activities"
@ -80,13 +80,13 @@ Feature: Users can access the course activities overview page
And I should see "Assignments" in the "assign_overview_collapsible" "region"
And I should see "Forums" in the "forum_overview_collapsible" "region"
And I should not see "Test assignment name" in the "assign_overview_collapsible" "region"
And I should not see "Go to Forums overview" in the "forum_overview_collapsible" "region"
And I should not see "Forum overview page" in the "forum_overview_collapsible" "region"
When I click on "Expand" "link" in the "assign_overview_collapsible" "region"
Then I should see "Test assignment name" in the "assign_overview_collapsible" "region"
And I should not see "Go to Forums overview" in the "forum_overview_collapsible" "region"
And I should not see "Forum overview page" in the "forum_overview_collapsible" "region"
And I click on "Collapse" "link" in the "assign_overview_collapsible" "region"
And I should not see "Test assignment name" in the "assign_overview_collapsible" "region"
And I should not see "Go to Forums overview" in the "forum_overview_collapsible" "region"
And I should not see "Forum overview page" in the "forum_overview_collapsible" "region"
Scenario: Course overview shows the course present activity types
Given the following "activities" exist:
@ -207,11 +207,11 @@ Feature: Users can access the course activities overview page
Scenario: Students can see the automatic completion criterias in the course overview
Given the following "activity" exists:
| activity | folder |
| name | Activity 1 |
| course | C1 |
| completion | 2 |
| completionview | 1 |
| activity | folder |
| name | Activity 1 |
| course | C1 |
| completion | 2 |
| completionview | 1 |
When I am on the "Course 1" "course > activities > resource" page logged in as "student1"
Then I should see "To do" in the "Activity 1" "table_row"
And I should see "View" in the "Activity 1" "table_row"
@ -250,3 +250,54 @@ Feature: Users can access the course activities overview page
And I click on "Get these logs" "button"
Then I should see "Course activities overview page viewed"
And I should see "viewed the list of resources"
Scenario: Users can see a link to the old index when the activity does not provide overview information
Given the following "activities" exist:
| activity | course | name |
| wiki | C1 | Activity 1 |
| wiki | C1 | Activity 2 |
| assign | C1 | Activity 3 |
When I am on the "Course 1" "course > activities > wiki" page logged in as "student1"
And I should see "Wiki overview page"
And I follow "Wiki overview"
And I should see "Activity 1"
And I should see "Activity 2"
# Check activities with integration do not show the link.
And I am on the "Course 1" "course > activities > assign" page
And I should not see "Assignment overview page"
Scenario: Activities overview provide completion information to the student
Given the following "activities" exist:
| activity | course | name | completion | completionview |
| choice | C1 | Activity 1 | 2 | 1 |
| choice | C1 | Activity 2 | 2 | 1 |
| choice | C1 | Activity 3 | 1 | 0 |
| choice | C1 | Activity 4 | 0 | 0 |
And I am on the "Activity 1" "activity" page logged in as "student1"
When I am on the "Course 1" "course > activities > choice" page logged in as "student1"
Then I should see "Completion status" in the "choice_overview_collapsible" "region"
And I should see "Done" in the "Activity 1" "table_row"
And I should see "To do" in the "Activity 2" "table_row"
And I should see "Mark as done" in the "Activity 3" "table_row"
And I should see "-" in the "Activity 4" "table_row"
And I am on the "Course 1" "course > activities > choice" page logged in as "teacher1"
And I should not see "Completion status" in the "choice_overview_collapsible" "region"
And I should not see "To do" in the "Activity 2" "table_row"
And I should not see "Mark as done" in the "Activity 3" "table_row"
And I should not see "-" in the "Activity 4" "table_row"
Scenario: Activities overview provide grade information to the student
Given the following "activities" exist:
| activity | course | name |
| lesson | C1 | Activity 1 |
| lesson | C1 | Activity 2 |
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
And I turn editing mode on
And I give the grade "42" to the user "Student 1" for the grade item "Activity 1"
And I press "Save changes"
When I am on the "Course 1" "course > activities > lesson" page logged in as "student1"
Then I should see "Grade" in the "lesson_overview_collapsible" "region"
And I should see "42.00" in the "Activity 1" "table_row"
And I should see "-" in the "Activity 2" "table_row"
When I am on the "Course 1" "course > activities > lesson" page logged in as "teacher1"
And I should not see "Grade" in the "lesson_overview_collapsible" "region"

View File

@ -100,7 +100,6 @@ $string['filterbothactive'] = 'First ({$a->first}) Last ({$a->last})';
$string['filterbyname'] = 'Filter by name';
$string['filterfirstactive'] = 'First ({$a->first})';
$string['filterlastactive'] = 'Last ({$a->last})';
$string['goto_overview'] = 'Go to {$a} overview';
$string['gradetopassnotset'] = 'This course does not have a grade to pass set. It may be set in the grade item of the course (Gradebook setup).';
$string['hideendedcoursestask'] = 'Hide courses on end date';
$string['informationformodule'] = 'Information about the {$a} activity';
@ -117,8 +116,9 @@ $string['norecentaccessesinfomessage'] = 'Hi {$a->userfirstname},
$string['noteachinginfomessage'] = 'Hi {$a->userfirstname},
<p>Courses with start dates in the next week have been identified as having no teacher or student enrolments.</p>';
$string['overview_info'] = 'View all the activities in this course, including key details like due date and updates.';
$string['overview_missing_notice'] = 'Check the overview page for more details.';
$string['overview_missing_notice'] = 'Go to {$a} for more details.';
$string['overview_missing_title'] = 'Information not available here for {$a} activities';
$string['overview_modname'] = '{$a} overview page';
$string['overview_page_title'] = 'Course activities: {$a}';
$string['overview_table_caption'] = 'Table listing all {$a} activities';
$string['participants:perpage'] = 'Number of participants per page';