diff --git a/grade/lib.php b/grade/lib.php
index 2d337b7ecea..7c2f8d5f14f 100644
--- a/grade/lib.php
+++ b/grade/lib.php
@@ -1710,6 +1710,23 @@ class grade_structure {
['title' => $title, 'aria-label' => $title]);
}
+ /**
+ * Returns a link leading to the grade analysis page
+ *
+ * @param grade_grade $grade
+ * @param string $gradeanalysisstring Language string
+ * @return string|null
+ */
+ public function get_grade_analysis_link(grade_grade $grade, string $gradeanalysisstring): ?string {
+ $url = $this->get_grade_analysis_url($grade);
+ if (is_null($url)) {
+ return null;
+ }
+
+ return html_writer::link($url, $gradeanalysisstring,
+ ['class' => 'dropdown-item', 'aria-label' => $gradeanalysisstring, 'role' => 'menuitem']);
+ }
+
/**
* Returns an action menu for the grade.
*
@@ -1915,6 +1932,40 @@ class grade_structure {
}
}
+ /**
+ * Returns a link leading to the edit grade/grade item/category page
+ *
+ * @param array $element An array representing an element in the grade_tree
+ * @param object $gpr A grade_plugin_return object
+ * @param array $langstrings Language strings
+ * @return string|null
+ */
+ public function get_edit_link(array $element, object $gpr, array $langstrings): ?string {
+ $url = null;
+ $title = '';
+ if (!has_capability('moodle/grade:manage', $this->context)) {
+ if (!($element['type'] == 'grade') || !has_capability('moodle/grade:edit', $this->context)) {
+ return null;
+ }
+ }
+
+ $object = $element['object'];
+
+ if ($element['type'] == 'grade') {
+ if (empty($object->id)) {
+ $url = new moodle_url('/grade/edit/tree/grade.php',
+ ['courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid]);
+ } else {
+ $url = new moodle_url('/grade/edit/tree/grade.php',
+ ['courseid' => $this->courseid, 'id' => $object->id]);
+ }
+ $title = $langstrings[0];
+ }
+ $gpr->add_url_params($url);
+ return html_writer::link($url, $title,
+ ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']);
+ }
+
/**
* Return hiding icon for give element
*
@@ -1973,6 +2024,50 @@ class grade_structure {
return $hideicon;
}
+ /**
+ * Returns a link with url to hide/unhide grade/grade item/grade category
+ *
+ * @param array $element An array representing an element in the grade_tree
+ * @param object $gpr A grade_plugin_return object
+ * @param array $langstrings Language strings
+ * @return string|null
+ */
+ public function get_hiding_link(array $element, object $gpr, array $langstrings): ?string {
+ if (!$element['object']->can_control_visibility() ||
+ (!has_capability('moodle/grade:manage', $this->context) &&
+ !has_capability('moodle/grade:hide', $this->context))) {
+ return null;
+ }
+
+ $url = new moodle_url('/grade/edit/tree/action.php',
+ ['id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']]);
+ $url = $gpr->add_url_params($url);
+
+ if ($element['object']->is_hidden()) {
+ $url->param('action', 'show');
+ $title = $langstrings[0];
+ } else {
+ $url->param('action', 'hide');
+ $title = $langstrings[1];
+ }
+
+ $url = html_writer::link($url, $title,
+ ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']);
+
+ if ($element['type'] == 'grade') {
+ $item = $element['object']->grade_item;
+ if ($item->hidden) {
+ $strparamobj = new stdClass();
+ $strparamobj->itemname = $item->get_name(true, true);
+ $strnonunhideable = get_string('nonunhideableverbose', 'grades', $strparamobj);
+ $url = html_writer::span($title, 'text-muted dropdown-item',
+ ['title' => $strnonunhideable, 'aria-label' => $title, 'role' => 'menuitem']);
+ }
+ }
+
+ return $url;
+ }
+
/**
* Return locking icon for given element
*
@@ -2030,6 +2125,54 @@ class grade_structure {
return $action;
}
+ /**
+ * Returns link to lock/unlock grade/grade item/grade category
+ *
+ * @param array $element An array representing an element in the grade_tree
+ * @param object $gpr A grade_plugin_return object
+ * @param array $langstrings Language strings
+ *
+ * @return string|null
+ */
+ public function get_locking_link(array $element, object $gpr, array $langstrings): ?string {
+
+ $title = '';
+ $url = new moodle_url('/grade/edit/tree/action.php',
+ ['id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']]);
+ $url = $gpr->add_url_params($url);
+
+ if (($element['type'] == 'grade') && ($element['object']->grade_item->is_locked())) {
+ // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon.
+ $strparamobj = new stdClass();
+ $strparamobj->itemname = $element['object']->grade_item->get_name(true, true);
+ $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj);
+ $title = $langstrings[0];
+ return html_writer::span($title, 'text-muted dropdown-item', ['title' => $strnonunlockable,
+ 'aria-label' => $title, 'role' => 'menuitem']);
+ } else if ($element['object']->is_locked()) {
+ $title = $langstrings[0];
+ if (!has_capability('moodle/grade:manage', $this->context) &&
+ !has_capability('moodle/grade:unlock', $this->context)) {
+ return html_writer::span($title, 'text-muted dropdown-item',
+ ['aria-label' => $title, 'role' => 'menuitem']);
+ } else {
+ $url->param('action', 'unlock');
+ }
+ } else {
+ $title = $langstrings[1];
+ if (!has_capability('moodle/grade:manage', $this->context) &&
+ !has_capability('moodle/grade:lock', $this->context)) {
+ return html_writer::span($title, 'text-muted dropdown-item',
+ ['aria-label' => $title, 'role' => 'menuitem']);
+ } else {
+ $url->param('action', 'lock');
+ }
+ }
+
+ return html_writer::link($url, $title,
+ ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']);
+ }
+
/**
* Return calculation icon for given element
*
diff --git a/grade/report/grader/index.php b/grade/report/grader/index.php
index 4e10cf0553a..2d9058d8d4f 100644
--- a/grade/report/grader/index.php
+++ b/grade/report/grader/index.php
@@ -119,7 +119,7 @@ $numusers = $report->get_numusers(true, true);
$actionbar = new \gradereport_grader\output\action_bar($context, $report, $numusers);
print_grade_page_head($COURSE->id, 'report', 'grader', $reportname, false, $buttons, true,
- null, null, null, $actionbar);
+ null, null, null, $actionbar, false);
// make sure separate group does not prevent view
if ($report->currentgroup == -2) {
diff --git a/grade/report/grader/lang/en/gradereport_grader.php b/grade/report/grader/lang/en/gradereport_grader.php
index 84d4e467f8f..b331885947f 100644
--- a/grade/report/grader/lang/en/gradereport_grader.php
+++ b/grade/report/grader/lang/en/gradereport_grader.php
@@ -51,6 +51,7 @@ $string['privacy:request:preference:grade_report_grader_collapsed_categories'] =
$string['summarygrader'] = 'A table with the names of students in the first column, with assessable activities grouped by course and category across the top.';
$string['useractivitygrade'] = '{$a} grade';
$string['overriddengrade'] = 'Overridden grade';
+$string['cellactions'] = 'Cell actions';
// Deprecated since Moodle 4.2.
$string['privacy:metadata:preference:grade_report_showquickfeedback'] = 'Whether to display a feedback text input box with a dotted border for each grade, allowing the feedback for many grades to be edited at the same time';
diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php
index 634e3725482..4b84fa038ac 100644
--- a/grade/report/grader/lib.php
+++ b/grade/report/grader/lib.php
@@ -199,7 +199,7 @@ class grade_report_grader extends grade_report {
$needsupdate = false;
- // skip, not a grade
+ // Skip, not a grade.
if (strpos($varname, 'grade') === 0) {
$datatype = 'grade';
} else {
@@ -773,12 +773,13 @@ class grade_report_grader extends grade_report {
// Get preferences once.
$showactivityicons = $this->get_pref('showactivityicons');
$quickgrading = $this->get_pref('quickgrading');
- $showanalysisicon = $this->get_pref('showanalysisicon');
// Get strings which are re-used inside the loop.
$strftimedatetimeshort = get_string('strftimedatetimeshort');
- $strexcludedgrades = get_string('excluded', 'grades');
$strerror = get_string('error');
+ $stroverridengrade = get_string('overridden', 'grades');
+ $strfail = get_string('fail', 'grades');
+ $strpass = get_string('pass', 'grades');
$viewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
foreach ($this->gtree->get_levels() as $row) {
@@ -945,6 +946,7 @@ class grade_report_grader extends grade_report {
$itemcell->id = 'u' . $userid . 'i' . $itemid;
$itemcell->attributes['data-itemid'] = $itemid;
+ $itemcell->attributes['class'] = 'gradecell';
// Get the decimal points preference for this item.
$decimalpoints = $item->get_decimals();
@@ -957,6 +959,8 @@ class grade_report_grader extends grade_report {
$gradeval = $grade->finalgrade;
}
+ $context = new stdClass();
+
// MDL-11274: Hide grades in the grader report if the current grader
// doesn't have 'moodle/grade:viewhidden'.
if (!$this->canviewhidden && $grade->is_hidden()) {
@@ -964,11 +968,12 @@ class grade_report_grader extends grade_report {
&& !$item->is_category_item() && !$item->is_course_item()) {
// The problem here is that we do not have the time when grade value was modified,
// 'timemodified' is general modification date for grade_grades records.
- $itemcell->text = "" .
- userdate($grade->get_datesubmitted(), $strftimedatetimeshort) . "";
+ $context->text = userdate($grade->get_datesubmitted(), $strftimedatetimeshort);
+ $context->extraclasses = 'datesubmitted';
} else {
- $itemcell->text = '-';
+ $context->text = '-';
}
+ $itemcell->text = $OUTPUT->render_from_template('gradereport_grader/cell', $context);
$itemrow->cells[] = $itemcell;
continue;
}
@@ -989,39 +994,27 @@ class grade_report_grader extends grade_report {
$itemcell->attributes['aria-label'] = $stroverridengrade;
}
- if ($grade->is_excluded()) {
- // Adding white spaces before and after to prevent a screenreader from
- // thinking that the words are attached to the next/previous or text.
- $itemcell->text .= " " . $strexcludedgrades . " ";
- }
-
- // Do not show any icons if no grade (no record in DB to match)
- if (!$item->needsupdate and !empty($USER->editing)) {
- $itemcell->text .= $this->get_icons($element);
- }
-
$hidden = '';
if ($grade->is_hidden()) {
$hidden = ' dimmed_text ';
}
-
$gradepass = ' gradefail ';
- $gradepassicon = $OUTPUT->pix_icon('i/invalid', $strfail);
+ $context->gradepassicon = $OUTPUT->pix_icon('i/invalid', $strfail);
if ($grade->is_passed($item)) {
$gradepass = ' gradepass ';
- $gradepassicon = $OUTPUT->pix_icon('i/valid', $strpass);
+ $context->gradepassicon = $OUTPUT->pix_icon('i/valid', $strpass);
} else if (is_null($grade->is_passed($item))) {
$gradepass = '';
- $gradepassicon = '';
+ $context->gradepassicon = '';
}
+ $context->statusicons = $this->set_grade_status_icons($grade);
// If in editing mode, we need to print either a text box or a drop down (for scales)
// grades in item of type grade category or course are not directly editable.
if ($item->needsupdate) {
- $itemcell->text .= "" . $strerror . "";
-
+ $context->text = $strerror;
+ $context->extraclasses = 'gradingerror';
} else if (!empty($USER->editing)) {
-
if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
$itemcell->attributes['class'] .= ' grade_type_scale';
} else if ($item->gradetype == GRADE_TYPE_VALUE) {
@@ -1030,7 +1023,13 @@ class grade_report_grader extends grade_report {
$itemcell->attributes['class'] .= ' grade_type_text';
}
+ if ($grade->is_locked()) {
+ $itemcell->attributes['class'] .= ' locked';
+ }
+
if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
+ $context->scale = true;
+
$scale = $scalesarray[$item->scaleid];
$gradeval = (int)$gradeval; // Scales use only integers.
$scales = explode(",", $scale->scale);
@@ -1046,6 +1045,7 @@ class grade_report_grader extends grade_report {
}
if ($quickgrading && $grade->is_editable()) {
+ $context->iseditable = true;
if (empty($item->outcomeid)) {
$nogradestr = $this->get_lang_string('nograde');
} else {
@@ -1056,38 +1056,50 @@ class grade_report_grader extends grade_report {
'id' => 'grade_' . $userid . '_' . $item->id
];
$gradelabel = $fullname . ' ' . $item->get_name(true);
- $itemcell->text .= html_writer::label(
- get_string('useractivitygrade', 'gradereport_grader', $gradelabel), $attributes['id'], false,
- ['class' => 'accesshide']);
- $itemcell->text .= html_writer::select($scaleopt, 'grade['.$userid.']['.$item->id.']', $gradeval, [-1=>$nogradestr], $attributes);
+
+ if ($context->statusicons) {
+ $attributes['class'] = 'statusicons';
+ }
+
+ $context->label = html_writer::label(
+ get_string('useractivitygrade', 'gradereport_grader', $gradelabel),
+ $attributes['id'], false, ['class' => 'accesshide']);
+ $context->select = html_writer::select($scaleopt, 'grade['.$userid.']['.$item->id.']',
+ $gradeval, [-1 => $nogradestr], $attributes);
} else if (!empty($scale)) {
$scales = explode(",", $scale->scale);
- // invalid grade if gradeval < 1
+ $context->extraclasses = 'gradevalue' . $hidden . $gradepass;
+ // Invalid grade if gradeval < 1.
if ($gradeval < 1) {
- $itemcell->text .= $gradepassicon .
- "-";
+ $context->text = '-';
} else {
// Just in case somebody changes scale.
$gradeval = $grade->grade_item->bounded_grade($gradeval);
- $itemcell->text .= $gradepassicon .
- "{$scales[$gradeval - 1]}";
+ $context->text = $scales[$gradeval - 1];
}
}
} else if ($item->gradetype != GRADE_TYPE_TEXT) {
// Value type.
if ($quickgrading and $grade->is_editable()) {
+ $context->iseditable = true;
$value = format_float($gradeval, $decimalpoints);
$gradelabel = $fullname . ' ' . $item->get_name(true);
- $itemcell->text .= '';
- $itemcell->text .= '';
+
+ $context->id = 'grade_' . $userid . '_' . $item->id;
+ $context->name = 'grade[' . $userid . '][' . $item->id .']';
+ $context->value = $value;
+ $context->label = get_string('useractivitygrade', 'gradereport_grader', $gradelabel);
+ $context->title = $strgrade;
+ $context->tabindex = $tabindices[$item->id]['grade'];
+ $context->extraclasses = 'form-control';
+ if ($context->statusicons) {
+ $context->extraclasses .= ' statusicons';
+ }
} else {
- $itemcell->text .= $gradepassicon . "" .
- format_float($gradeval, $decimalpoints) . "";
+ $context->extraclasses = 'gradevalue' . $hidden . $gradepass;
+ $context->text = format_float($gradeval, $decimalpoints);
}
}
@@ -1104,7 +1116,8 @@ class grade_report_grader extends grade_report {
}
if ($item->needsupdate) {
- $itemcell->text .= $gradepassicon . "" . $error . "";
+ $context->text = $strerror;
+ $context->extraclasses = 'gradingerror' . $hidden . $gradepass;
} else {
// The max and min for an aggregation may be different to the grade_item.
if (!is_null($gradeval)) {
@@ -1112,14 +1125,18 @@ class grade_report_grader extends grade_report {
$item->grademin = $grade->get_grade_min();
}
- $itemcell->text .= $gradepassicon . "" .
- grade_format_gradevalue($gradeval, $item, true, $gradedisplaytype, null) . "";
- if ($showanalysisicon) {
- $itemcell->text .= $this->gtree->get_grade_analysis_icon($grade);
- }
+ $context->extraclasses = 'gradevalue ' . $hidden . $gradepass;
+ $context->text = grade_format_gradevalue($gradeval, $item, true,
+ $gradedisplaytype, null);
}
}
+ if (!$item->needsupdate) {
+ $context->actionmenu = $this->get_grade_action_menu($element);
+ }
+
+ $itemcell->text = $OUTPUT->render_from_template('gradereport_grader/cell', $context);
+
if (!empty($this->gradeserror[$item->id][$userid])) {
$itemcell->text .= $this->gradeserror[$item->id][$userid];
}
@@ -1143,6 +1160,45 @@ class grade_report_grader extends grade_report {
return $rows;
}
+ /**
+ * Sets status icons for the grade.
+ * @param grade_grade $grade Grade
+ *
+ * @return string status icons container HTML
+ */
+ public function set_grade_status_icons(grade_grade $grade) : string {
+ global $OUTPUT;
+
+ $attributes = ['class' => 'text-muted'];
+
+ $statusicons = '';
+
+ if ($grade->is_hidden()) {
+ $statusicons .= $OUTPUT->pix_icon('i/show', $this->get_lang_string('hidden', 'grades'),
+ 'moodle', $attributes);
+ }
+
+ if ($grade->is_locked()) {
+ $statusicons .= $OUTPUT->pix_icon('i/lock', $this->get_lang_string('locked', 'grades'),
+ 'moodle', $attributes);
+ }
+
+ if ($grade->is_overridden()) {
+ $statusicons .= $OUTPUT->pix_icon('i/overriden_grade',
+ $this->get_lang_string('overridden', 'grades'), 'moodle', $attributes);
+ }
+
+ if ($grade->is_excluded()) {
+ $statusicons .= $OUTPUT->pix_icon('i/excluded', $this->get_lang_string('excluded', 'grades'),
+ 'moodle', $attributes);
+ }
+
+ if ($statusicons) {
+ $statusicons = $OUTPUT->container($statusicons, 'grade_icons');
+ }
+ return $statusicons;
+ }
+
/**
* Depending on the style of report (fixedstudents vs traditional one-table),
* arranges the rows of data in one or two tables, and returns the output of
@@ -1619,6 +1675,60 @@ class grade_report_grader extends grade_report {
return $OUTPUT->container($editicon.$editcalculationicon.$showhideicon.$lockunlockicon.$gradeanalysisicon, 'grade_icons');
}
+ /**
+ * Returns an action menu for the grade.
+ *
+ * @param array $element Array with cell info.
+ * @return string
+ */
+ public function get_grade_action_menu(array $element) : string {
+ global $OUTPUT, $USER;
+
+ $editable = true;
+
+ $context = new stdClass();
+
+ $editstrings = [];
+ $editstrings[] = $this->get_lang_string('editgrade', 'grades');
+
+ $hidestrings = [];
+ $hidestrings[] = $this->get_lang_string('show');
+ $hidestrings[] = $this->get_lang_string('hide');
+
+ $lockstrings = [];
+ $lockstrings[] = $this->get_lang_string('unlock', 'grades');
+ $lockstrings[] = $this->get_lang_string('lock', 'grades');
+
+ $gradeanalysisstring = $this->get_lang_string('gradeanalysis', 'grades');
+
+ if ($element['type'] == 'grade') {
+ $context->isgrade = true;
+ $item = $element['object']->grade_item;
+ if ($item->is_course_item() || $item->is_category_item()) {
+ $editable = $this->overridecat;
+ }
+
+ if (!empty($USER->editing)) {
+ if ($editable) {
+ $context->editurl = $this->gtree->get_edit_link($element, $this->gpr, $editstrings);
+ }
+
+ if (has_capability('moodle/grade:manage', $this->context)) {
+ $context->hideurl = $this->gtree->get_hiding_link($element, $this->gpr, $hidestrings);
+ $context->lockurl = $this->gtree->get_locking_link($element, $this->gpr, $lockstrings);
+ }
+ }
+
+ $context->gradeanalysisurl = $this->gtree->get_grade_analysis_link($element['object'], $gradeanalysisstring);
+ }
+
+ if (!empty($USER->editing) || isset($context->gradeanalysisurl)) {
+ return $OUTPUT->render_from_template('gradereport_grader/grademenu', $context);
+ }
+ return '';
+ }
+
+
/**
* Given a category element returns collapsing +/- icon if available
*
diff --git a/grade/report/grader/styles.css b/grade/report/grader/styles.css
index 48699bfde27..dfb8217afe1 100644
--- a/grade/report/grader/styles.css
+++ b/grade/report/grader/styles.css
@@ -42,10 +42,29 @@
border-left-width: 0;
padding: 4px 5px;
vertical-align: middle;
- text-align: right;
+ text-align: center;
white-space: nowrap;
}
+.path-grade-report-grader .action-menu {
+ display: inline-block;
+}
+
+.path-grade-report-grader .overriddengrade {
+ color: #495057;
+ border: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+}
+
+.path-grade-report-grader .gradepass {
+ color: #357a32;
+}
+
+.path-grade-report-grader .gradefail {
+ color: #ca3120;
+}
+
/**
* All the floating divs.
*/
@@ -76,6 +95,16 @@
width: 200px;
white-space: normal;
vertical-align: top;
+ height: 100px;
+}
+
+.path-grade-report-grader .gradeparent .gradecell {
+ vertical-align: top;
+ padding-top: 20px;
+}
+
+.path-grade-report-grader .gradeparent .locked {
+ padding-top: 27px;
}
.path-grade-report-grader .gradeparent .user.cell .userpicture {
@@ -140,11 +169,6 @@
display: inline-block;
}
-.path-grade-report-grader .gradeparent tr:nth-child(n) td.overridden:nth-child(n) {
- /* Made very specific to override the default stripped style of the table. */
- background-color: #efd9a4;
-}
-
.path-grade-report-grader .gradeparent tr:nth-child(n) td.ajaxoverridden:nth-child(n) {
/* Made very specific to override the default stripped style of the table. */
background-color: #ffe3a0;
@@ -163,11 +187,11 @@
/**
* Editing fields.
+ * MDL-74286 - width 100% pushes action menu out of cell.
*/
.path-grade-report-grader .gradeparent select {
text-overflow: ellipsis;
min-width: 8rem;
- width: 100%;
}
.path-grade-report-grader .gradeparent .text {
@@ -178,6 +202,14 @@
text-align: center;
}
+.path-grade-report-grader .action-menu {
+ padding-left: 10px;
+}
+
+.path-grade-report-grader .dropdown-menu {
+ width: max-content;
+}
+
.path-grade-report-grader .yui3-overlay {
border: 0;
background: none;
diff --git a/grade/report/grader/templates/cell.mustache b/grade/report/grader/templates/cell.mustache
new file mode 100644
index 00000000000..38a53bd1172
--- /dev/null
+++ b/grade/report/grader/templates/cell.mustache
@@ -0,0 +1,48 @@
+{{!
+ 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 .
+}}
+{{!
+ @template gradereport_grader/cell
+
+ Cell template.
+
+ Example context (json):
+ {
+ "iseditable": "true",
+ "statusicons": "
",
+ "actionmenu": "",
+ "id": "grade_313_624",
+ "label": "grade_313_624",
+ "title": "Grade",
+ "extraclasses": "statusicons",
+ "value": "Text information",
+ "tabindex": "1",
+ "name": "grade[313][624]"
+ }
+}}
+{{#iseditable}}
+ {{#scale}}
+ {{>core_grades/grades/grader/scale}}
+ {{/scale}}
+ {{^scale}}
+ {{>core_grades/grades/grader/input}}
+ {{/scale}}
+{{/iseditable}}
+{{^iseditable}}
+ {{>core_grades/grades/grader/text}}
+{{/iseditable}}
+{{{actionmenu}}}
+{{{statusicons}}}
diff --git a/grade/report/grader/templates/grademenu.mustache b/grade/report/grader/templates/grademenu.mustache
new file mode 100644
index 00000000000..11c997dd7d5
--- /dev/null
+++ b/grade/report/grader/templates/grademenu.mustache
@@ -0,0 +1,38 @@
+{{!
+ 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 .
+}}
+{{!
+ @template block_timeline/nav-day-filter
+ This template renders the day range selector for the timeline view.
+ Example context (json):
+ {
+ "editurl": "Edit grade",
+ "hideurl": "Hide"
+ }
+}}
+
diff --git a/grade/templates/grades/grader/input.mustache b/grade/templates/grades/grader/input.mustache
new file mode 100644
index 00000000000..7316678e9c2
--- /dev/null
+++ b/grade/templates/grades/grader/input.mustache
@@ -0,0 +1,34 @@
+{{!
+ 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 .
+}}
+{{!
+ @template gradereport_grader/input
+
+ Input template for grader report cell.
+
+ Example context (json):
+ {
+ "id": "grade_313_624",
+ "label": "grade_313_624",
+ "title": "Grade",
+ "extraclasses": "statusicons",
+ "value": "Text information",
+ "tabindex": "1",
+ "name": "grade[313][624]"
+ }
+}}
+
+
diff --git a/grade/templates/grades/grader/scale.mustache b/grade/templates/grades/grader/scale.mustache
new file mode 100644
index 00000000000..d0c26a76292
--- /dev/null
+++ b/grade/templates/grades/grader/scale.mustache
@@ -0,0 +1,28 @@
+{{!
+ 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 .
+}}
+{{!
+ @template gradereport_grader/scale
+
+ Scale template for grader report cell.
+
+ Example context (json):
+ {
+ "label": "Text label",
+ "select": ""
+ }
+}}
+{{{label}}}{{{select}}}
diff --git a/grade/templates/grades/grader/text.mustache b/grade/templates/grades/grader/text.mustache
new file mode 100644
index 00000000000..9f1b2a84f8c
--- /dev/null
+++ b/grade/templates/grades/grader/text.mustache
@@ -0,0 +1,30 @@
+{{!
+ 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 .
+}}
+{{!
+ @template gradereport_grader/text
+
+ Text template for grader report cell.
+
+ Example context (json):
+ {
+ "gradepassicon": "",
+ "extraclasses": " dimmed_text",
+ "text": "Mostly separate knowing"
+ }
+}}
+{{{gradepassicon}}}
+{{{text}}}
diff --git a/grade/tests/behat/behat_grade.php b/grade/tests/behat/behat_grade.php
index 0c803a6e926..7c27d36d106 100644
--- a/grade/tests/behat/behat_grade.php
+++ b/grade/tests/behat/behat_grade.php
@@ -60,7 +60,7 @@ class behat_grade extends behat_base {
$gradeitem = behat_context_helper::escape($gradeitem);
if ($this->running_javascript()) {
- $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]";
+ $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')][not(contains(@class,'grader'))]";
if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
$this->execute("behat_action_menu::i_open_the_action_menu_in",
array("//tr[contains(.,$gradeitem)]",
diff --git a/lang/en/grades.php b/lang/en/grades.php
index 1b015fd62ec..992006559d2 100644
--- a/lang/en/grades.php
+++ b/lang/en/grades.php
@@ -570,6 +570,7 @@ $string['nolettergrade'] = 'No letter grade for';
$string['nomode'] = 'NA';
$string['nonnumericweight'] = 'Received non-numeric value for';
$string['nonunlockableverbose'] = 'This grade cannot be unlocked until {$a->itemname} is unlocked.';
+$string['nonunhideableverbose'] = 'This grade cannot be unhidden until {$a->itemname} is hidden.';
$string['nonweightedpct'] = 'non-weighted %';
$string['nooutcome'] = 'No outcome';
$string['nooutcomes'] = 'Outcome items must be linked to a course outcome, but there are no outcomes for this course. Would you like to add one?';
diff --git a/lib/classes/output/icon_system_fontawesome.php b/lib/classes/output/icon_system_fontawesome.php
index 6e8ef7cd72f..f44a463341c 100644
--- a/lib/classes/output/icon_system_fontawesome.php
+++ b/lib/classes/output/icon_system_fontawesome.php
@@ -236,6 +236,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:i/empty' => 'fa-fw',
'core:i/enrolmentsuspended' => 'fa-pause',
'core:i/enrolusers' => 'fa-user-plus',
+ 'core:i/excluded' => 'fa-minus-circle',
'core:i/expired' => 'fa-exclamation text-warning',
'core:i/export' => 'fa-download',
'core:i/link' => 'fa-link',
@@ -294,6 +295,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:i/open' => 'fa-folder-open',
'core:i/otherevent' => 'fa-calendar',
'core:i/outcomes' => 'fa-tasks',
+ 'core:i/overriden_grade' => 'fa-edit',
'core:i/payment' => 'fa-money',
'core:i/permissionlock' => 'fa-lock',
'core:i/permissions' => 'fa-pencil-square-o',
diff --git a/theme/boost/scss/moodle/grade.scss b/theme/boost/scss/moodle/grade.scss
index 65caaf4ad6f..487a9a9ce6f 100644
--- a/theme/boost/scss/moodle/grade.scss
+++ b/theme/boost/scss/moodle/grade.scss
@@ -530,6 +530,17 @@
}
}
+.gradereport-grader-table {
+ input[name^="grade"] {
+ width: 80px;
+ display: inline-block;
+ }
+ .dropdown-toggle::after {
+ display: none;
+ }
+}
+
+
.search-widget {
.dropdown-menu {
padding: 0.8rem 1.2rem;
diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css
index 8e86c40194b..5ccac87f573 100644
--- a/theme/boost/style/moodle.css
+++ b/theme/boost/style/moodle.css
@@ -19855,6 +19855,13 @@ p.arrow_button {
.path-grade-report-singleview .reporttable .dropdown-toggle::after {
display: none; }
+.gradereport-grader-table input[name^="grade"] {
+ width: 80px;
+ display: inline-block; }
+
+.gradereport-grader-table .dropdown-toggle::after {
+ display: none; }
+
.search-widget .dropdown-menu {
padding: 0.8rem 1.2rem; }
.search-widget .dropdown-menu.wide {
diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css
index ba3f8f23f5a..16efdb45320 100644
--- a/theme/classic/style/moodle.css
+++ b/theme/classic/style/moodle.css
@@ -19855,6 +19855,13 @@ p.arrow_button {
.path-grade-report-singleview .reporttable .dropdown-toggle::after {
display: none; }
+.gradereport-grader-table input[name^="grade"] {
+ width: 80px;
+ display: inline-block; }
+
+.gradereport-grader-table .dropdown-toggle::after {
+ display: none; }
+
.search-widget .dropdown-menu {
padding: 0.8rem 1.2rem; }
.search-widget .dropdown-menu.wide {