Merge branch 'MDL-83888-main-v02' of https://github.com/ferranrecio/moodle

This commit is contained in:
Sara Arjona 2025-03-05 08:31:28 +01:00
commit 01a5feb9b5
No known key found for this signature in database
19 changed files with 1254 additions and 174 deletions

View File

@ -0,0 +1,7 @@
issueNumber: MDL-83888
notes:
mod_assign:
- message: >-
The assign_course_index_summary is now deprecated. The assign index is
now generated using the mod_assign\course\overview integration class.
type: deprecated

View File

@ -0,0 +1,104 @@
---
layout: docs
title: "Notification badges"
description: "Notification badges used in Moodle"
date: 2025-02-25T09:40:32+01:00
draft: false
weight: 50
tags:
- Available
---
## How to use notification badges
Notification badges are used to display concise information or status indicators. They can be used to indicate the user an element has relevant information or new information.
## Example
{{< example show_markup="false">}}
<button class="btn btn-outline-secondary">
Grade
<span class="ms-1 badge rounded-pill text-bg-primary" title="Needs grading"><span class="visually-hidden"> (</span>1<span class="visually-hidden">)</span></span>
</button>
{{< /example >}}
### Usage
The `core_renderer` provides a `notice_badge` method to create a badge. The method accepts the following parameters:
- **contents** (`string`): The content to display inside the badge.
- **badgestyle** (`core\output\local\properties\badge`): The badge style to use. This is an enum that defines all possible badge styles. By default it will use primary style.
- **title** (`string`): An optional title attribute for the badge.
This is an example of how to render a notice badge using the `core_renderer` (the example uses dependency injection to get the renderer instance, see [Dependency Injection](https://moodledev.io/docs/5.0/apis/core/di) for more information):
```php
// Get the core renderer.
$renderer = \core\di::get(\core\output\renderer_helper::class)->get_core_renderer();
// Save the badge into a variable.
$badge = $renderer->notice_badge(
contents: ($needgrading > 0) ? $needgrading : '',
title: get_string('numberofsubmissionsneedgrading', 'assign'),
badgestyle: \core\output\local\properties\badge::SECONDARY,
);
$content = new action_link(
url: new url('/some/index.php'),
text: get_string('gradeverb') . $badge,
);
echo $renderer->render($content);
```
### Badge Styling
The badge can be styled using the `\core\output\local\properties\badge` enum. The enum provides the following badge styles:
- `badge::PRIMARY`: 'primary' (default style for badges)
- `badge::SECONDARY`: 'secondary' (usually in dark gray color)
- `badge::SUCCESS`: 'success' (usually in green color)
- `badge::DANGER`: 'danger' (usually in red color)
- `badge::WARNING`: 'warning' (usually in yellow color)
- `badge::INFO`: 'info' (usually in blue color)
This is how every badge style looks like:
{{< example show_markup="false">}}
<ul class="list-group">
<li class="list-group-item">
Primary badge
<span class="ms-1 badge rounded-pill text-bg-primary" title=""><span class="visually-hidden"> (</span>1<span class="visually-hidden">)</span></span>
</li>
<li class="list-group-item">
Secondary badge
<span class="ms-1 badge rounded-pill text-bg-secondary" title=""><span class="visually-hidden"> (</span>1<span class="visually-hidden">)</span></span>
</li>
<li class="list-group-item">
Success badge
<span class="ms-1 badge rounded-pill text-bg-success" title=""><span class="visually-hidden"> (</span>1<span class="visually-hidden">)</span></span>
</li>
<li class="list-group-item">
Danger badge
<span class="ms-1 badge rounded-pill text-bg-danger" title=""><span class="visually-hidden"> (</span>1<span class="visually-hidden">)</span></span>
</li>
<li class="list-group-item">
Warning badge
<span class="ms-1 badge rounded-pill text-bg-warning" title=""><span class="visually-hidden"> (</span>1<span class="visually-hidden">)</span></span>
</li>
<li class="list-group-item">
Info badge
<span class="ms-1 badge rounded-pill text-bg-info" title=""><span class="visually-hidden"> (</span>1<span class="visually-hidden">)</span></span>
</li>
</ul>
{{< /example >}}
### Validate badges in behat
For behat and screen readers, the badge is read as a text in parentheses. For example, the badge with the text "1" is read as "1 (1)". However, it is not recommended to test the badge text directly in combination with the parent element text because mustache templates may add some line breaks. Instead, you should test the badge text separately.
This is an example of how to test the example "Grade" badge in behat:
```gherkin
And I should see "Grade" in the "Assign with pending grades" "table_row"
And I should see "(2)" in the "Assign with pending grades" "table_row"
```

View File

@ -23,6 +23,8 @@ use core_courseformat\local\overview\overviewitem;
use core_courseformat\output\local\overview\activityname;
use core_courseformat\output\local\overview\overviewpage;
use core_courseformat\base as courseformat;
use grade_item;
use grade_grade;
/**
* Base class for activity overview.
@ -170,4 +172,87 @@ abstract class activityoverviewbase {
content: $completion,
);
}
/**
* Retrieves the grades overview items for the activity.
*
* Most activities will have none or one grade. However, some activities
* may have multiple grades, such as workshop or quiz.
*
* It is not recommended to override this method unless the plugin
* has specific requirements. Instead, plugins should override
* get_grade_item_names to provide the grade item names.
*
* @return overviewitem[] Array of overview items representing the grades.
*/
public function get_grades_overviews(): array {
global $CFG, $USER;
// This overview is to see the own grades, users with full gradebook
// access will see all grades in the gradebook.
if (has_capability('moodle/grade:viewall', $this->context)) {
return [];
}
if (!plugin_supports('mod', $this->cm->modname, FEATURE_GRADE_HAS_GRADE, false)) {
return [];
}
require_once($CFG->libdir . '/gradelib.php');
$items = grade_item::fetch_all([
'itemtype' => 'mod',
'itemmodule' => $this->cm->modname,
'iteminstance' => $this->cm->instance,
'courseid' => $this->course->id,
]);
if (empty($items)) {
return [];
}
$itemnames = $this->get_grade_item_names($items);
$result = [];
foreach ($items as $item) {
// Plugins may decide to hide a specific grade item by not setting a name.
if (empty($itemnames[$item->id])) {
continue;
}
$gradegrade = grade_grade::fetch(['itemid' => $item->id, 'userid' => $USER->id]);
if (
!$gradegrade
|| ($gradegrade->is_hidden() && !has_capability('moodle/grade:viewhidden', $this->context))
) {
$result[] = new overviewitem(
name: $itemnames[$item->id],
value: '-',
content: '-',
);
continue;
}
$result[] = new overviewitem(
name: $itemnames[$item->id],
value: $gradegrade->finalgrade,
content: grade_format_gradevalue($gradegrade->finalgrade, $item),
);
}
return $result;
}
/**
* Retrieves the grade item names for the activity.
*
* By default, the overview will display the grade if the activities
* has only one grade item. The name of the grade item will be 'Grade'.
* For plugins with multiple grade items, the plugin must override this method
* and provide names for each grade item that want to be displayed.
*
* @param grade_item[] $items
* @return array<integer, string> the grade item names indexed by item id.
*/
protected function get_grade_item_names(array $items): array {
if (count($items) == 1) {
return [reset($items)->id => get_string('gradenoun')];
}
return [];
}
}

View File

@ -226,6 +226,13 @@ class overviewtable implements renderable, named_templatable {
$row = array_merge($row, $overview->get_extra_overview_items($output));
$gradeitems = $overview->get_grades_overviews();
if (!empty($gradeitems)) {
foreach ($gradeitems as $gradeitem) {
$row[$gradeitem->get_name()] = $gradeitem;
}
}
// Actions are always the last column, if any.
$row['actions'] = $overview->get_actions_overview($output);

View File

@ -31,6 +31,7 @@ final class activityoverviewbase_test extends \advanced_testcase {
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
require_once($CFG->dirroot . '/course/format/tests/fixtures/fake_activityoverview.php');
require_once($CFG->libdir . '/gradelib.php');
parent::setUpBeforeClass();
}
@ -166,4 +167,168 @@ final class activityoverviewbase_test extends \advanced_testcase {
$this->assertEquals(null, $result->get_value());
$this->assertEquals('-', $result->get_content());
}
/**
* Test get_grades_overviews method.
*
* @covers ::get_grades_overviews
*/
public function test_get_grades_overviews(): void {
$this->resetAfterTest();
$this->setAdminUser();
$generator = $this->getDataGenerator();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create some modules.
$assign = $this->getDataGenerator()->create_module(
'assign',
['course' => $course->id]
);
$workshop = $this->getDataGenerator()->create_module(
'workshop',
['course' => $course->id],
['grade' => 100.0],
);
$page = $this->getDataGenerator()->create_module(
'page',
['course' => $course->id]
);
// Assignments have one grade item.
$assignitems = \grade_item::fetch_all([
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => (int) $assign->id,
'courseid' => $course->id,
]);
$gradegrade = new \grade_grade();
$gradegrade->itemid = reset($assignitems)->id;
$gradegrade->userid = (int) $student->id;
$gradegrade->rawgrade = 88;
$gradegrade->finalgrade = 88;
$gradegrade->insert();
// Workshops have two grade items.
$workshopitems = array_values(
\grade_item::fetch_all([
'itemtype' => 'mod',
'itemmodule' => 'workshop',
'iteminstance' => (int) $workshop->id,
'courseid' => $course->id,
['grade' => 100.0],
])
);
$gradegrade = new \grade_grade();
$gradegrade->itemid = reset($workshopitems)->id;
$gradegrade->userid = (int) $student->id;
$gradegrade->rawgrade = 77;
$gradegrade->finalgrade = 77;
$gradegrade->insert();
// Validate student grades.
$this->setUser($student);
$modinfo = get_fast_modinfo($course);
// Validate assign gradeitems.
$cm = $modinfo->get_cm($assign->cmid);
$overview = new \core_courseformat\fake_activityoverview($cm);
$result = $overview->get_grades_overviews();
$this->assertCount(1, $result);
$this->assertEquals(get_string('gradenoun'), $result[0]->get_name());
$this->assertEquals(88, $result[0]->get_value());
$this->assertEquals('88.00', $result[0]->get_content());
// Validate workshop gradeitems (having two grade, they should return an empty array).
$cm = $modinfo->get_cm($workshop->cmid);
$overview = new \core_courseformat\fake_activityoverview($cm);
$result = $overview->get_grades_overviews();
$this->assertEmpty($result);
// Validate page has no gradeitems.
$cm = $modinfo->get_cm($page->cmid);
$overview = new \core_courseformat\fake_activityoverview($cm);
$result = $overview->get_grades_overviews();
$this->assertEmpty($result);
// Validate teacher does not has grade overiviews items.
$this->setUser($teacher);
$modinfo = get_fast_modinfo($course);
// Validate assign gradeitems.
$cm = $modinfo->get_cm($assign->cmid);
$overview = new \core_courseformat\fake_activityoverview($cm);
$result = $overview->get_grades_overviews();
$this->assertEmpty($result);;
// Validate workshop gradeitems (having two grade, they should return an empty array).
$cm = $modinfo->get_cm($workshop->cmid);
$overview = new \core_courseformat\fake_activityoverview($cm);
$result = $overview->get_grades_overviews();
$this->assertEmpty($result);
// Validate page has no gradeitems.
$cm = $modinfo->get_cm($page->cmid);
$overview = new \core_courseformat\fake_activityoverview($cm);
$result = $overview->get_grades_overviews();
$this->assertEmpty($result);
}
/**
* Test get_grades_overviews when the grade item is hidden.
*
* @covers ::get_grades_overviews
*/
/**
* Test get_grades_overviews method.
*
* @covers ::get_grades_overviews
*/
public function test_get_grades_overviews_hidden(): void {
$this->resetAfterTest();
$this->setAdminUser();
$generator = $this->getDataGenerator();
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create some modules.
$assign = $this->getDataGenerator()->create_module(
'assign',
['course' => $course->id]
);
// Assignments have one grade item.
$assignitems = \grade_item::fetch_all([
'itemtype' => 'mod',
'itemmodule' => 'assign',
'iteminstance' => (int) $assign->id,
'courseid' => $course->id,
]);
$gradeitem = reset($assignitems);
$gradegrade = new \grade_grade();
$gradegrade->itemid = $gradeitem->id;
$gradegrade->userid = (int) $student->id;
$gradegrade->rawgrade = 88;
$gradegrade->finalgrade = 88;
$gradegrade->insert();
// Hide some grades.
$gradeitem->set_hidden(1, true);
// Validate student grades.
$this->setUser($student);
$modinfo = get_fast_modinfo($course);
// Validate assign gradeitems.
$cm = $modinfo->get_cm($assign->cmid);
$overview = new \core_courseformat\fake_activityoverview($cm);
$result = $overview->get_grades_overviews();
$this->assertCount(1, $result);
$this->assertEquals(get_string('gradenoun'), $result[0]->get_name());
$this->assertEquals('-', $result[0]->get_value());
$this->assertEquals('-', $result[0]->get_content());
}
}

View File

@ -79,13 +79,13 @@ Feature: Users can access the course activities overview page
And I am on the "Course 1" "course > activities" page logged in as "teacher1"
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 "Go to Assignments overview" 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"
When I click on "Expand" "link" in the "assign_overview_collapsible" "region"
Then I should see "Go to Assignments overview" 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 click on "Collapse" "link" in the "assign_overview_collapsible" "region"
And I should not see "Go to Assignments overview" 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"
Scenario: Course overview shows the course present activity types

View File

@ -42,6 +42,7 @@ use core\hook\output\before_standard_footer_html_generation;
use core\hook\output\before_standard_top_of_body_html_generation;
use core\output\actions\component_action;
use core\output\actions\popup_action;
use core\output\local\properties\badge;
use core\plugin_manager;
use moodleform;
use moodle_page;
@ -2976,6 +2977,35 @@ EOD;
) . ' ';
}
/**
* Outputs a screen reader only inline text.
*
* @param string $contents The content of the badge
* @param badge $badgestyle The style of the badge (default is PRIMARY)
* @param string $title An optional title of the badge
* @return string the HTML to output.
*/
public function notice_badge(
string $contents,
badge $badgestyle = badge::PRIMARY,
string $title = '',
): string {
if ($contents === '') {
return '';
}
// We want the badges to be read as content in parentesis.
$contents = trim($this->visually_hidden_text(' ('))
. $contents
. trim($this->visually_hidden_text(')'));
$attributes = ['class' => 'ms-1 ' . $badgestyle->classes()];
if ($title !== '') {
$attributes['title'] = $title;
}
return html_writer::tag('span', $contents, $attributes);
}
/**
* Outputs a container.
*

View File

@ -0,0 +1,51 @@
<?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\output\local\properties;
/**
* Enum badge.
*
* This enum is used to define the different types of badges that can be used in the application.
*
* @package core
* @copyright 2025 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
enum badge: string {
case PRIMARY = 'primary';
case SECONDARY = 'secondary';
case SUCCESS = 'success';
case DANGER = 'danger';
case WARNING = 'warning';
case INFO = 'info';
/**
* Returns the CSS classes for the property based on its type.
*
* @return string The CSS classes.
*/
public function classes(): string {
return match ($this) {
self::PRIMARY => ' badge rounded-pill text-bg-primary',
self::SECONDARY => ' badge rounded-pill text-bg-secondary',
self::SUCCESS => ' badge rounded-pill text-bg-success',
self::DANGER => ' badge rounded-pill text-bg-danger',
self::WARNING => ' badge rounded-pill text-bg-warning',
self::INFO => ' badge rounded-pill text-bg-info',
};
}
}

View File

@ -0,0 +1,71 @@
<?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\output;
use moodle_page;
/**
* Class renderer helper.
*
* This class is created to access output renderers from a
* dependency injection container. $PAGE and $OUTPUT globals
* haven't got a constant value, so we can't use them in
* services or factories.
*
* this class wraps all the globals logic and provides a
* way to access the renderers.
*
* @package core
* @copyright 2025 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer_helper {
/**
* Get the renderer for a particular part of Moodle.
*
* @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
* @param string|null $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
* @return renderer_base
*/
public function get_renderer(string $component, ?string $subtype = null): renderer_base {
global $PAGE;
return $PAGE->get_renderer($component);
}
/**
* Get the core renderer.
*
* This method is a shortcut to get the core renderer with the correct
* return type declaration. This way the IDE can provide better autocompletion.
*
* @return core_renderer
*/
public function get_core_renderer(): core_renderer {
global $PAGE;
return $PAGE->get_renderer('core');
}
/**
* Get the current page instance.
*
* @return moodle_page
*/
public function get_page(): moodle_page {
global $PAGE;
return $PAGE;
}
}

View File

@ -0,0 +1,186 @@
<?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 mod_assign\courseformat;
use assign;
use cm_info;
use core_courseformat\local\overview\overviewitem;
use core\output\action_link;
use core\output\local\properties\text_align;
use core\output\local\properties\button;
use core\url;
use mod_assign\dates;
/**
* Assignment overview integration.
*
* @package mod_assign
* @copyright 2025 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class overview extends \core_courseformat\activityoverviewbase {
/** @var assign $assign the assign instance. */
private assign $assign;
/**
* Constructor.
*
* @param cm_info $cm the course module instance.
* @param \core\output\renderer_helper $rendererhelper the renderer helper.
*/
public function __construct(
cm_info $cm,
/** @var \core\output\renderer_helper $rendererhelper the renderer helper */
protected readonly \core\output\renderer_helper $rendererhelper,
) {
global $CFG;
require_once($CFG->dirroot . '/mod/assign/locallib.php');
parent::__construct($cm);
$this->assign = new assign($this->context, $this->cm, $this->cm->get_course());
}
#[\Override]
public function get_due_date_overview(): ?overviewitem {
global $USER;
$dates = new dates($this->cm, $USER->id);
$duedate = $dates->get_due_date();
if (empty($duedate)) {
return new overviewitem(
name: get_string('duedate', 'assign'),
value: null,
content: '-',
);
}
$content = userdate($duedate);
return new overviewitem(
name: get_string('duedate', 'assign'),
value: $duedate,
content: $content,
);
}
#[\Override]
public function get_actions_overview(): ?overviewitem {
if (!has_capability('mod/assign:grade', $this->context)) {
return null;
}
$needgrading = $this->assign->count_submissions_need_grading();
$renderer = $this->rendererhelper->get_core_renderer();
$badge = '';
if ($needgrading > 0) {
$badge = $renderer->notice_badge(
contents: $needgrading,
title: get_string('numberofsubmissionsneedgrading', 'assign'),
);
}
$content = new action_link(
url: new url('/mod/assign/view.php', ['id' => $this->cm->id, 'action' => 'grading']),
text: get_string('gradeverb') . $badge,
attributes: ['class' => button::SECONDARY_OUTLINE->classes()],
);
return new overviewitem(
name: get_string('actions'),
value: $needgrading,
content: $content,
textalign: text_align::CENTER,
);
}
#[\Override]
public function get_extra_overview_items(): array {
return [
'submissions' => $this->get_extra_submissions_overview(),
'submissionstatus' => $this->get_extra_submission_status_overview(),
];
}
/**
* Retrieves an overview of submissions for the assignment.
*
* @return overviewitem|null An overview item c, or null if the user lacks the required capability.
*/
private function get_extra_submissions_overview(): ?overviewitem {
global $USER;
if (!has_capability('mod/assign:grade', $this->cm->context)) {
return null;
}
$activitygroup = groups_get_activity_group($this->cm);
$submissions = $this->assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
$total = $this->assign->count_participants($activitygroup);
$content = new action_link(
url: new url('/mod/assign/view.php', ['id' => $this->cm->id, 'action' => 'grading']),
text: get_string(
'count_of_total',
'core',
['count' => $submissions, 'total' => $total]
),
attributes: ['class' => button::SECONDARY_OUTLINE->classes()],
);
return new overviewitem(
name: get_string('submissions', 'assign'),
value: $submissions,
content: $content,
textalign: text_align::CENTER,
);
}
/**
* Retrieves the submission status overview for the current user.
*
* @return overviewitem|null The overview item, or null if the user does not have the required capabilities.
*/
private function get_extra_submission_status_overview(): ?overviewitem {
global $USER;
if (
!has_capability('mod/assign:submit', $this->context)
|| has_capability('moodle/site:config', $this->context)
) {
return null;
}
if ($this->assign->get_instance()->teamsubmission) {
$usersubmission = $this->assign->get_group_submission($USER->id, 0, false);
} else {
$usersubmission = $this->assign->get_user_submission($USER->id, false);
}
if (!empty($usersubmission->status)) {
$submittedstatus = get_string('submissionstatus_' . $usersubmission->status, 'assign');
} else {
$submittedstatus = get_string('submissionstatus_', 'assign');
}
return new overviewitem(
name: get_string('submissionstatus', 'assign'),
value: $submittedstatus,
content: $submittedstatus,
);
}
}

View File

@ -36,6 +36,9 @@ use core\activity_dates;
*/
class dates extends activity_dates {
/** @var int|null $timedue the activity due date */
private ?int $timedue;
/**
* Returns a list of important dates in mod_assign
*
@ -46,6 +49,8 @@ class dates extends activity_dates {
require_once($CFG->dirroot . '/mod/assign/locallib.php');
$this->timedue = null;
$course = get_course($this->cm->course);
$context = \context_module::instance($this->cm->id);
$assign = new \assign($context, $this->cm, $course);
@ -83,10 +88,11 @@ class dates extends activity_dates {
}
if ($timedue) {
$this->timedue = (int) $timedue;
$date = [
'dataid' => 'duedate',
'label' => get_string('activitydate:submissionsdue', 'mod_assign'),
'timestamp' => (int) $timedue,
'timestamp' => $this->timedue,
];
if ($course->relativedatesmode && $assign->can_view_grades()) {
$date['relativeto'] = $course->startdate;
@ -96,4 +102,15 @@ class dates extends activity_dates {
return $dates;
}
/**
* Returns the dues date data, if any.
* @return int|null the due date timestamp or null if not set.
*/
public function get_due_date(): ?int {
if (!isset($this->timedue)) {
$this->get_dates();
}
return $this->timedue;
}
}

View File

@ -1220,10 +1220,19 @@ class renderer extends \plugin_renderer_base {
/**
* Render a course index summary
*
* @deprecated since Moodle 5.0 (MDL-83888).
* @todo MDL-84429 Final deprecation in Moodle 6.0.
* @param \assign_course_index_summary $indexsummary
* @return string
*/
#[\core\attribute\deprecated(
since: '5.0',
mdl: 'MDL-83888',
reason: 'The assign_course_index_summary class is not used anymore.',
)]
public function render_assign_course_index_summary(\assign_course_index_summary $indexsummary) {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
$o = '';
$strplural = get_string('modulenameplural', 'assign');

View File

@ -25,28 +25,6 @@
require_once("../../config.php");
require_once($CFG->dirroot.'/mod/assign/locallib.php');
// For this type of page this is the course id.
$id = required_param('id', PARAM_INT);
$courseid = required_param('id', PARAM_INT);
$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
require_login($course);
$PAGE->set_url('/mod/assign/index.php', array('id' => $id));
$PAGE->set_pagelayout('incourse');
\mod_assign\event\course_module_instance_list_viewed::create_from_course($course)->trigger();
// Print the header.
$strplural = get_string("modulenameplural", "assign");
$PAGE->navbar->add($strplural);
$PAGE->set_title($strplural);
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
echo $OUTPUT->heading(format_string($strplural));
$context = context_course::instance($course->id);
require_capability('mod/assign:view', $context);
$assign = new assign($context, null, $course);
// Get the assign to render the page.
echo $assign->view('viewcourseindex');
\core_courseformat\activityoverviewbase::redirect_to_overview_page($courseid, 'assign');

View File

@ -611,6 +611,7 @@ You can see the status of your assignment submission:
$string['submissionreceipthtml'] = '<p>You have submitted an assignment submission for \'<i>{$a->assignment}</i>\'.</p>
<p>You can see the status of your <a href="{$a->url}">assignment submission</a>.</p>';
$string['submissionreceiptsmall'] = 'You have submitted your assignment submission for {$a->assignment}';
$string['submissions'] = 'Submissions';
$string['submissionslocked'] = 'This assignment is not accepting submissions';
$string['submissionslockedshort'] = 'Submission changes not allowed';
$string['submissionsclosed'] = 'Submissions closed';

View File

@ -699,8 +699,6 @@ class assign {
$o .= $this->view_plugin_grading_batch_operation();
} else if ($action == 'viewpluginpage') {
$o .= $this->view_plugin_page();
} else if ($action == 'viewcourseindex') {
$o .= $this->view_course_index();
} else if ($action == 'viewbatchsetmarkingworkflowstate') {
$o .= $this->view_batch_set_workflow_state();
} else if ($action == 'viewbatchmarkingallocation') {
@ -3255,97 +3253,6 @@ class assign {
return false;
}
/**
* View a summary listing of all assignments in the current course.
*
* @return string
*/
private function view_course_index() {
global $USER;
$o = '';
$course = $this->get_course();
$strplural = get_string('modulenameplural', 'assign');
if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
$o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
$o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
return $o;
}
$strsectionname = '';
$usesections = course_format_uses_sections($course->format);
$modinfo = get_fast_modinfo($course);
if ($usesections) {
$strsectionname = course_get_format($course)->get_generic_section_name();
$sections = $modinfo->get_section_info_all();
}
$courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
$timenow = time();
$currentsection = '';
foreach ($modinfo->instances['assign'] as $cm) {
if (!$cm->uservisible) {
continue;
}
$timedue = $cms[$cm->id]->duedate;
$sectionname = '';
if ($usesections && $cm->sectionnum) {
$sectionname = get_section_name($course, $sections[$cm->sectionnum]);
}
$submitted = '';
$context = context_module::instance($cm->id);
$assignment = new assign($context, $cm, $course);
// Apply overrides.
$assignment->update_effective_access($USER->id);
$timedue = $assignment->get_instance()->duedate;
if (has_capability('mod/assign:submit', $context) &&
!has_capability('moodle/site:config', $context)) {
$cangrade = false;
if ($assignment->get_instance()->teamsubmission) {
$usersubmission = $assignment->get_group_submission($USER->id, 0, false);
} else {
$usersubmission = $assignment->get_user_submission($USER->id, false);
}
if (!empty($usersubmission->status)) {
$submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
} else {
$submitted = get_string('submissionstatus_', 'assign');
}
$gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
!$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
$grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
} else {
$grade = '-';
}
} else if (has_capability('mod/assign:grade', $context)) {
$submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
$grade = $assignment->count_submissions_need_grading();
$cangrade = true;
}
$courseindexsummary->add_assign_info($cm->id, $cm->get_formatted_name(),
$sectionname, $timedue, $submitted, $grade, $cangrade);
}
$o .= $this->get_renderer()->render($courseindexsummary);
$o .= $this->view_footer();
return $o;
}
/**
* View a page rendered by a plugin.
*

View File

@ -622,6 +622,10 @@ class assign_grading_summary implements renderable {
/**
* Renderable course index summary
*
* @deprecated since Moodle 5.0 (MDL-83888).
* @todo MDL-84429 Final deprecation in Moodle 6.0.
*
* @package mod_assign
* @copyright 2012 NetSpot {@link http://www.netspot.com.au}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@ -637,10 +641,18 @@ class assign_course_index_summary implements renderable {
/**
* constructor
*
* @deprecated since Moodle 5.0 (MDL-83888).
* @todo MDL-84429 Final deprecation in Moodle 6.0.
* @param boolean $usesections - True if this course format uses sections
* @param string $courseformatname - The id of this course format
*/
#[\core\attribute\deprecated(
since: '5.0',
mdl: 'MDL-83888',
reason: 'The assign_course_index_summary class is not used anymore.',
)]
public function __construct($usesections, $courseformatname) {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
$this->usesections = $usesections;
$this->courseformatname = $courseformatname;
}
@ -648,6 +660,8 @@ class assign_course_index_summary implements renderable {
/**
* Add a row of data to display on the course index page
*
* @deprecated since Moodle 5.0 (MDL-83888).
* @todo MDL-84429 Final deprecation in Moodle 6.0.
* @param int $cmid - The course module id for generating a link
* @param string $cmname - The course module name for generating a link
* @param string $sectionname - The name of the course section (only if $usesections is true)
@ -657,7 +671,13 @@ class assign_course_index_summary implements renderable {
* @param string $gradeinfo - The current users grade if they have been graded and it is not hidden.
* @param bool cangrade - Does this user have grade capability?
*/
#[\core\attribute\deprecated(
since: '5.0',
mdl: 'MDL-83888',
reason: 'The assign_course_index_summary class is not used anymore.',
)]
public function add_assign_info($cmid, $cmname, $sectionname, $timedue, $submissioninfo, $gradeinfo, $cangrade = false) {
\core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]);
$this->assignments[] = ['cmid' => $cmid,
'cmname' => $cmname,
'sectionname' => $sectionname,

View File

@ -0,0 +1,115 @@
@mod @mod_assign
Feature: Testing overview integration in mod_assign
In order to summarize the assignments
As a user
I need to be able to see the assignment overview
Background:
Given the following "users" exist:
| username | firstname | lastname |
| student1 | Username | 1 |
| student2 | Username | 2 |
| student3 | Username | 3 |
| student4 | Username | 4 |
| student5 | Username | 5 |
| student6 | Username | 6 |
| student7 | Username | 7 |
| student8 | Username | 8 |
| teacher1 | Teacher | T |
And the following "courses" exist:
| fullname | shortname | groupmode |
| Course 1 | C1 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| student4 | C1 | student |
| student5 | C1 | student |
| student6 | C1 | student |
| student7 | C1 | student |
| student8 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | course | idnumber | duedate | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | submissiondrafts |
| assign | Date assign | C1 | assign1 | ##1 Jan 2040 08:00## | 1 | 0 | 0 |
| assign | No submissions | C1 | assign2 | ##1 Jan 2040 08:00## | 1 | 0 | 0 |
| assign | Pending grades | C1 | assign3 | | 1 | 0 | 0 |
And the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Date assign | student1 | This is a submission for assignment |
| Pending grades | student1 | This is a submission for assignment |
| Pending grades | student2 | This is a submission for assignment |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Pending grades | student1 | 50 |
Scenario: The assign overview report should generate log events
Given I am on the "Course 1" "course > activities > assign" page logged in as "teacher1"
When I am on the "Course 1" "course" page logged in as "teacher1"
And I navigate to "Reports" in current page administration
And I click on "Logs" "link"
And I click on "Get these logs" "button"
Then I should see "Course activities overview page viewed"
And I should see "viewed the instance list for the module 'assign'"
Scenario: Teachers can see relevant columns in the assign overview
When I am on the "Course 1" "course > activities > assign" page logged in as "teacher1"
# Check columns.
Then I should see "Name" in the "assign_overview_collapsible" "region"
And I should see "Due date" in the "assign_overview_collapsible" "region"
And I should see "Submissions" in the "assign_overview_collapsible" "region"
And I should see "Actions" in the "assign_overview_collapsible" "region"
# Check Due dates.
And I should see "1 January 2040" in the "Date assign" "table_row"
And I should see "1 January 2040" in the "No submissions" "table_row"
And I should see "-" in the "Pending grades" "table_row"
# Check Submissions.
And I should see "1 of 8" in the "Date assign" "table_row"
And I should see "0 of 8" in the "No submissions" "table_row"
And I should see "2 of 8" in the "Pending grades" "table_row"
# Check main actions.
And I should see "Grade" in the "Date assign" "table_row"
And I should see "Grade" in the "No submissions" "table_row"
And I should see "Grade" in the "Pending grades" "table_row"
And I should see "(2)" in the "Pending grades" "table_row"
# Check submission link.
And I click on "2 of 8" "link" in the "Pending grades" "table_row"
And I should see "50.00" in the "Username 1" "table_row"
And I should see "-" in the "Username 2" "table_row"
# Check grade link.
And I am on the "Course 1" "course > activities > assign" page logged in as "teacher1"
And I click on "Grade" "link" in the "Date assign" "table_row"
And I should see "Submitted for grading" in the "Username 1" "table_row"
And I should see "No submission" in the "Username 2" "table_row"
Scenario: Students can see relevant columns in the assign overview
When I am on the "Course 1" "course > activities > assign" page logged in as "student1"
# Check columns.
Then I should see "Name" in the "assign_overview_collapsible" "region"
And I should see "Due date" in the "assign_overview_collapsible" "region"
And I should see "Submission status" in the "assign_overview_collapsible" "region"
And I should see "Grade" in the "assign_overview_collapsible" "region"
# Check Due dates.
And I should see "1 January 2040" in the "Date assign" "table_row"
And I should see "1 January 2040" in the "No submissions" "table_row"
And I should see "-" in the "Pending grades" "table_row"
# Check Submission status.
And I should see "Submitted for grading" in the "Date assign" "table_row"
And I should see "No submission" in the "No submissions" "table_row"
And I should see "-" in the "Submitted for grading" "table_row"
# Check Grade.
And I should see "-" in the "Date assign" "table_row"
And I should see "-" in the "No submissions" "table_row"
And I should see "50.00" in the "Pending grades" "table_row"
Scenario: The assign index redirect to the activities overview
When I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I add the "Activities" block
And I click on "Assignments" "link" in the "Activities" "block"
Then I should see "View all the activities in this course"
And I should see "Name" in the "assign_overview_collapsible" "region"
And I should see "Due date" in the "assign_overview_collapsible" "region"
And I should see "Submissions" in the "assign_overview_collapsible" "region"
And I should see "Actions" in the "assign_overview_collapsible" "region"

View File

@ -0,0 +1,380 @@
<?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 mod_assign\courseformat;
use core_courseformat\local\overview\overviewfactory;
/**
* Tests for Assignment overview integration.
*
* @covers \mod_assign\course\overview
* @package mod_assign
* @category test
* @copyright 2025 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class overview_test extends \advanced_testcase {
#[\Override]
public static function setUpBeforeClass(): void {
global $CFG;
require_once($CFG->dirroot . '/mod/assign/locallib.php');
require_once($CFG->dirroot . '/mod/assign/tests/fixtures/testable_assign.php');
parent::setUpBeforeClass();
}
/**
* Test get_actions_overview method.
*
* @covers ::get_actions_overview
*/
public function test_get_actions_overview(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Setup the assignment.
$activity = $this->getDataGenerator()->create_module(
'assign',
['course' => $course->id],
);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$assign = new \mod_assign_testable_assign($cm->context, $cm, $course);
// Check for 0 submissions.
$this->setUser($teacher);
$item = overviewfactory::create($cm)->get_actions_overview();
$this->assertEquals(get_string('actions'), $item->get_name());
$this->assertEquals(0, $item->get_value());
// Simulate an assignment submission.
$this->setUser($student);
$submission = $assign->get_user_submission($student->id, true);
$submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
$assign->testable_update_submission($submission, $student->id, true, false);
$data = new \stdClass();
$data->onlinetext_editor = [
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text',
'format' => FORMAT_MOODLE,
];
$plugin = $assign->get_submission_plugin_by_type('onlinetext');
$plugin->save($submission, $data);
// Check for 1 ungraded submission.
$this->setUser($teacher);
$item = overviewfactory::create($cm)->get_actions_overview();
$this->assertEquals(get_string('actions'), $item->get_name());
$this->assertEquals(1, $item->get_value());
// Check students cannot access submissions.
$this->setUser($student);
$item = overviewfactory::create($cm)->get_actions_overview();
$this->assertNull($item);
}
/**
* Test get_due_date_overview method.
*
* @covers ::get_due_date_overview
* @dataProvider get_due_date_overview_provider
* @param int|null $timeincrement null if no due date, or due date increment.
*/
public function test_get_due_date_overview(
int|null $timeincrement,
): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
if ($timeincrement === null) {
$expectedtime = null;
} else {
$expectedtime = $this->mock_clock_with_frozen()->time() + $timeincrement;
}
$activity = $this->getDataGenerator()->create_module(
'assign',
[
'course' => $course->id,
'assignsubmission_onlinetext_enabled' => 1,
'duedate' => !empty($expectedtime) ? $expectedtime : 0,
],
);
$this->setUser($teacher);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$item = overviewfactory::create($cm)->get_due_date_overview();
$this->assertEquals(get_string('duedate', 'assign'), $item->get_name());
$this->assertEquals($expectedtime, $item->get_value());
}
/**
* Provider for get_due_date_overview.
*
* @return array
*/
public static function get_due_date_overview_provider(): array {
return [
'no_due' => [
'timeincrement' => null,
],
'past_due' => [
'timeincrement' => -1 * (4 * DAYSECS),
],
'future_due' => [
'timeincrement' => (4 * DAYSECS),
],
];
}
/**
* Test get_extra_submissions_overview method.
*
* @covers ::get_extra_submissions_overview
*/
public function test_get_extra_submissions_overview(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student2');
// Setup the assignment.
$activity = $this->getDataGenerator()->create_module(
'assign',
[
'course' => $course->id,
'assignsubmission_onlinetext_enabled' => 1,
],
);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$assign = new \mod_assign_testable_assign($cm->context, $cm, $course);
// Check teacher has 0 submissions.
$this->setUser($teacher);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submissions_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertEquals(get_string('submissions', 'assign'), $item->get_name());
$this->assertEquals(0, $item->get_value());
// Simulate an assignment submission.
$this->setUser($student);
$submission = $assign->get_user_submission($student->id, true);
$submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
$assign->testable_update_submission($submission, $student->id, true, false);
$data = new \stdClass();
$data->onlinetext_editor = [
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text',
'format' => FORMAT_MOODLE,
];
$plugin = $assign->get_submission_plugin_by_type('onlinetext');
$plugin->save($submission, $data);
// Check students cannot access submissions.
$this->setUser($student);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submissions_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertNull($item);
// Check teacher has 1 submissions.
$this->setUser($teacher);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submissions_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertEquals(get_string('submissions', 'assign'), $item->get_name());
$this->assertEquals(1, $item->get_value());
}
/**
* Test get_extra_submission_status_overview method.
*
* @covers ::get_extra_submission_status_overview
*/
public function test_get_extra_submission_status_overview(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Setup the assignment.
$activity = $this->getDataGenerator()->create_module(
'assign',
[
'course' => $course->id,
'assignsubmission_onlinetext_enabled' => 1,
],
);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$assign = new \mod_assign_testable_assign($cm->context, $cm, $course);
// Check teacher does not has submission status.
$this->setUser($teacher);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertNull($item);
// Admin does not have submission status.
$this->setAdminUser();
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertNull($item);
// Check student see the new status.
$this->setUser($student);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertEquals(get_string('submissionstatus', 'assign'), $item->get_name());
$this->assertEquals(get_string('submissionstatus_new', 'assign'), $item->get_value());
// Simulate an assignment submission.
$this->setUser($student);
$submission = $assign->get_user_submission($student->id, true);
$submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
$assign->testable_update_submission($submission, $student->id, true, false);
$data = new \stdClass();
$data->onlinetext_editor = [
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text',
'format' => FORMAT_MOODLE,
];
$plugin = $assign->get_submission_plugin_by_type('onlinetext');
$plugin->save($submission, $data);
// Check student see the new status.
$this->setUser($student);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertEquals(get_string('submissionstatus', 'assign'), $item->get_name());
$this->assertEquals(get_string('submissionstatus_submitted', 'assign'), $item->get_value());
}
/**
* Test get_extra_submission_status_overview method in group submissions.
*
* @covers ::get_extra_submission_status_overview
*/
public function test_get_extra_submission_status_overview_groups(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
groups_add_member($group, $student);
groups_add_member($group, $student2);
// Setup the assignment.
$activity = $this->getDataGenerator()->create_module(
'assign',
[
'course' => $course->id,
'assignsubmission_onlinetext_enabled' => 1,
'teamsubmission' => 1,
],
);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$assign = new \mod_assign_testable_assign($cm->context, $cm, $course);
// Check teacher does not has submission status.
$this->setUser($teacher);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertNull($item);
// Admin does not have submission status.
$this->setAdminUser();
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertNull($item);
// Check student see the new status.
$this->setUser($student);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertEquals(get_string('submissionstatus', 'assign'), $item->get_name());
$this->assertEquals(get_string('submissionstatus_new', 'assign'), $item->get_value());
// Simulate an assignment submission.
$this->setUser($student2);
$submission = $assign->get_group_submission($student2->id, $group->id, true);
$submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
$assign->testable_update_submission($submission, $student2->id, true, false);
$data = new \stdClass();
$data->onlinetext_editor = [
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text',
'format' => FORMAT_MOODLE,
];
$plugin = $assign->get_submission_plugin_by_type('onlinetext');
$plugin->save($submission, $data);
// Check student see the new status.
$this->setUser($student);
$cm = get_fast_modinfo($course)->get_cm($activity->cmid);
$overview = overviewfactory::create($cm);
$reflection = new \ReflectionClass($overview);
$method = $reflection->getMethod('get_extra_submission_status_overview');
$method->setAccessible(true);
$item = $method->invoke($overview);
$this->assertEquals(get_string('submissionstatus', 'assign'), $item->get_name());
$this->assertEquals(get_string('submissionstatus_submitted', 'assign'), $item->get_value());
}
}

View File

@ -4469,59 +4469,6 @@ Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
$this->assertSame('This one should be re-created', $event2->description);
}
/**
* Test submissions that need grading output after one ungraded submission
*/
public function test_submissions_need_grading(): void {
global $PAGE;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Setup the assignment.
$this->setUser($teacher);
$time = time();
$assign = $this->create_instance($course, [
'assignsubmission_onlinetext_enabled' => 1,
]);
$PAGE->set_url(new \moodle_url('/mod/assign/view.php', [
'id' => $assign->get_course_module()->id,
'action' => 'grading',
]));
// Check for 0 submissions.
$summary = $assign->view('viewcourseindex');
$this->assertStringContainsString('/mod/assign/view.php?id=' .
$assign->get_course_module()->id . '&amp;action=grading">' .
get_string('numberofsubmissionsneedgradinglabel', 'assign', 0) . '</a>', $summary);
// Simulate an assignment submission.
$this->setUser($student);
$submission = $assign->get_user_submission($student->id, true);
$submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
$assign->testable_update_submission($submission, $student->id, true, false);
$data = new \stdClass();
$data->onlinetext_editor = [
'itemid' => file_get_unused_draft_itemid(),
'text' => 'Submission text',
'format' => FORMAT_MOODLE,
];
$plugin = $assign->get_submission_plugin_by_type('onlinetext');
$plugin->save($submission, $data);
// Check for 1 ungraded submission.
$this->setUser($teacher);
$summary = $assign->view('viewcourseindex');
$this->assertStringContainsString('/mod/assign/view.php?id=' .
$assign->get_course_module()->id . '&amp;action=grading">' .
get_string('numberofsubmissionsneedgradinglabel', 'assign', 1) . '</a>', $summary);
}
/**
* Test that attachments should not be provided if \assign->show_intro returns false.
*