Merge branch 'MDL-78082-master' of https://github.com/rezaies/moodle
This commit is contained in:
@@ -92,7 +92,7 @@ switch ($action) {
|
||||
if ($type == 'grade' and empty($object->id)) {
|
||||
$object->insert();
|
||||
}
|
||||
$object->set_locked(1, true, true);
|
||||
$object->set_locked(1, false, true);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -104,7 +104,7 @@ switch ($action) {
|
||||
if ($type == 'grade' and empty($object->id)) {
|
||||
$object->insert();
|
||||
}
|
||||
$object->set_locked(0, true, true);
|
||||
$object->set_locked(0, false, true);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class grade_edit_tree {
|
||||
$level++;
|
||||
$this->categories[$object->id] = $strippedname;
|
||||
$category = grade_category::fetch(array('id' => $object->id));
|
||||
$item = $category->get_grade_item();
|
||||
$category->load_grade_item();
|
||||
|
||||
// Add aggregation coef input if not a course item and if parent category has correct aggregation type
|
||||
// Before we print the category's row, we must find out how many rows will appear below it (for the filler cell's rowspan)
|
||||
@@ -271,7 +271,7 @@ class grade_edit_tree {
|
||||
$categoryrow->id = 'grade-item-' . $eid;
|
||||
$categoryrow->attributes['class'] = $courseclass . ' category ';
|
||||
$categoryrow->attributes['data-category'] = $eid;
|
||||
$categoryrow->attributes['data-itemid'] = $category->get_grade_item()->id;
|
||||
$categoryrow->attributes['data-itemid'] = $category->grade_item->id;
|
||||
$categoryrow->attributes['data-hidden'] = 'false';
|
||||
foreach ($rowclasses as $class) {
|
||||
$categoryrow->attributes['class'] .= ' ' . $class;
|
||||
@@ -1058,6 +1058,8 @@ class grade_edit_tree_column_status extends grade_edit_tree_column {
|
||||
*/
|
||||
public function get_category_cell($category, $levelclass, $params) {
|
||||
global $OUTPUT, $gtree;
|
||||
|
||||
$category->load_grade_item();
|
||||
$categorycell = parent::get_category_cell($category, $levelclass, $params);
|
||||
$element = [];
|
||||
$element['object'] = $category;
|
||||
|
||||
+41
-9
@@ -2379,7 +2379,26 @@ class grade_structure {
|
||||
['id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']]);
|
||||
$url = $gpr->add_url_params($url);
|
||||
|
||||
if (($element['type'] == 'grade') && ($element['object']->grade_item->is_locked())) {
|
||||
if ($element['type'] == 'category') {
|
||||
// Grade categories themselves cannot be locked. We lock/unlock their grade items.
|
||||
$children = $element['object']->get_children(true);
|
||||
$alllocked = true;
|
||||
foreach ($children as $child) {
|
||||
if (!$child['object']->is_locked()) {
|
||||
$alllocked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($alllocked && has_capability('moodle/grade:unlock', $this->context)) {
|
||||
$title = get_string('unlock', 'grades');
|
||||
$url->param('action', 'unlock');
|
||||
} else if (!$alllocked && has_capability('moodle/grade:lock', $this->context)) {
|
||||
$title = get_string('lock', 'grades');
|
||||
$url->param('action', 'lock');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else 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);
|
||||
@@ -2501,7 +2520,6 @@ class grade_structure {
|
||||
|
||||
$context = [
|
||||
'hidden' => $element['object']->is_hidden(),
|
||||
'locked' => $element['object']->is_locked(),
|
||||
];
|
||||
|
||||
if ($element['object'] instanceof grade_grade) {
|
||||
@@ -2511,18 +2529,32 @@ class grade_structure {
|
||||
$context['feedback'] = !empty($grade->feedback) && $grade->load_grade_item()->gradetype != GRADE_TYPE_TEXT;
|
||||
}
|
||||
|
||||
// Early return if there aren't any statuses that we need to show.
|
||||
if (!in_array(true, $context)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$context['classes'] = 'grade_icons data-collapse_gradeicons';
|
||||
|
||||
if (isset($element['type']) && ($element['type'] == 'category')) {
|
||||
if ($element['object'] instanceof grade_category) {
|
||||
$context['classes'] = 'category_grade_icons';
|
||||
|
||||
$children = $element['object']->get_children(true);
|
||||
$alllocked = true;
|
||||
foreach ($children as $child) {
|
||||
if (!$child['object']->is_locked()) {
|
||||
$alllocked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($alllocked) {
|
||||
$context['locked'] = true;
|
||||
}
|
||||
} else {
|
||||
$context['locked'] = $element['object']->is_locked();
|
||||
}
|
||||
|
||||
return $OUTPUT->render_from_template('core_grades/status_icons', $context);
|
||||
// Don't even attempt rendering if there is no status to show.
|
||||
if (in_array(true, $context)) {
|
||||
return $OUTPUT->render_from_template('core_grades/status_icons', $context);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"name": "Meaning of life"
|
||||
}
|
||||
}}
|
||||
<button class="btn btn-link btn-icon icon-size-3" data-hider="expand" data-col="{{field}}">
|
||||
<button type="button" class="btn btn-link btn-icon icon-size-3" data-hider="expand" data-col="{{field}}">
|
||||
<i class="icon fa fa-plus m-0" title="Reopen column" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}}reopencolumn, gradereport_grader, {{name}}{{/str}}</span>
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
Feature: Locking Grade Items and Categories in Gradebook
|
||||
In order to ensure that grade items and categories can be securely locked in the gradebook,
|
||||
As a teacher,
|
||||
I need to perform locking actions and verify the locking status.
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| teacher1 | Teacher | 1 |
|
||||
| student1 | Student | 1 |
|
||||
| student2 | Student | 2 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
And the following "grade categories" exist:
|
||||
| fullname | course |
|
||||
| Category 1 | C1 |
|
||||
And the following "grade items" exist:
|
||||
| itemname | course | gradecategory |
|
||||
| Manual grade 1 | C1 | Category 1 |
|
||||
| Manual grade 2 | C1 | Category 1 |
|
||||
And the following "grade items" exist:
|
||||
| itemname | course |
|
||||
| Manual grade 3 | C1 |
|
||||
|
||||
@javascript
|
||||
Scenario: Locking and unlocking a grade item preserves individual student locks
|
||||
Given I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
|
||||
And I turn editing mode on
|
||||
And I change window size to "large"
|
||||
When I click on "Cell actions" "button" in the "//td[count(//th[.//descendant::a[normalize-space(text())='Manual grade 1']]/preceding-sibling::*)][../th[1]/a[normalize-space(text())='Student 1']]" "xpath_element"
|
||||
And I choose "Lock" in the open action menu
|
||||
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
|
||||
And I choose "Lock" in the open action menu
|
||||
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
|
||||
And I choose "Unlock" in the open action menu
|
||||
Then "Locked" "icon" should exist in the "Student 1" "table_row"
|
||||
And "Locked" "icon" should not exist in the "Student 2" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Locking and unlocking a grade item through editing form preserves individual student locks
|
||||
Given I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
|
||||
And I turn editing mode on
|
||||
And I change window size to "large"
|
||||
When I click on "Cell actions" "button" in the "//td[count(//th[.//descendant::a[normalize-space(text())='Manual grade 1']]/preceding-sibling::*)][../th[1]/a[normalize-space(text())='Student 1']]" "xpath_element"
|
||||
And I choose "Edit grade" in the open action menu
|
||||
And I set the field "Locked" to "1"
|
||||
And I press "Save changes"
|
||||
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
|
||||
And I choose "Edit grade item" in the open action menu
|
||||
And I set the field "Locked" to "1"
|
||||
And I click on "Save" "button" in the "Edit grade item" "dialogue"
|
||||
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
|
||||
And I choose "Edit grade item" in the open action menu
|
||||
And I set the field "Locked" to "0"
|
||||
And I click on "Save" "button" in the "Edit grade item" "dialogue"
|
||||
Then "Locked" "icon" should exist in the "Student 1" "table_row"
|
||||
And "Locked" "icon" should not exist in the "Student 2" "table_row"
|
||||
@@ -68,7 +68,7 @@ define('GRADE_AGGREGATE_WEIGHTED_MEAN2', 11);
|
||||
define('GRADE_AGGREGATE_EXTRACREDIT_MEAN', 12);
|
||||
|
||||
/**
|
||||
* GRADE_AGGREGATE_WEIGHTED_MEAN2 - Use Natural in the category for grade aggregation.
|
||||
* GRADE_AGGREGATE_SUM - Use Natural in the category for grade aggregation.
|
||||
*/
|
||||
define('GRADE_AGGREGATE_SUM', 13);
|
||||
|
||||
|
||||
@@ -2536,25 +2536,21 @@ class grade_category extends grade_object {
|
||||
|
||||
$result = $this->grade_item->set_locked($lockedstate, $cascade, true);
|
||||
|
||||
if ($cascade) {
|
||||
//process all children - items and categories
|
||||
if ($children = grade_item::fetch_all(array('categoryid'=>$this->id))) {
|
||||
// Process all children - items and categories.
|
||||
if ($children = grade_item::fetch_all(['categoryid' => $this->id])) {
|
||||
foreach ($children as $child) {
|
||||
$child->set_locked($lockedstate, $cascade, false);
|
||||
|
||||
foreach ($children as $child) {
|
||||
$child->set_locked($lockedstate, true, false);
|
||||
|
||||
if (empty($lockedstate) and $refresh) {
|
||||
//refresh when unlocking
|
||||
$child->refresh_grades();
|
||||
}
|
||||
if (empty($lockedstate) && $refresh) {
|
||||
// Refresh when unlocking.
|
||||
$child->refresh_grades();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($children = grade_category::fetch_all(array('parent'=>$this->id))) {
|
||||
|
||||
foreach ($children as $child) {
|
||||
$child->set_locked($lockedstate, true, true);
|
||||
}
|
||||
if ($children = static::fetch_all(['parent' => $this->id])) {
|
||||
foreach ($children as $child) {
|
||||
$child->set_locked($lockedstate, $cascade, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -649,12 +649,16 @@ class grade_item extends grade_object {
|
||||
*/
|
||||
public function set_locked($lockedstate, $cascade=false, $refresh=true) {
|
||||
if ($lockedstate) {
|
||||
/// setting lock
|
||||
if ($this->needsupdate) {
|
||||
return false; // can not lock grade without first having final grade
|
||||
// Setting lock.
|
||||
if (empty($this->id)) {
|
||||
return false;
|
||||
} else if ($this->needsupdate) {
|
||||
// Can not lock grade without first having final grade,
|
||||
// so we schedule it to be locked as soon as regrading is finished.
|
||||
$this->locktime = time() - 1;
|
||||
} else {
|
||||
$this->locked = time();
|
||||
}
|
||||
|
||||
$this->locked = time();
|
||||
$this->update();
|
||||
|
||||
if ($cascade) {
|
||||
|
||||
@@ -784,13 +784,17 @@ class grade_category_test extends \grade_base_testcase {
|
||||
$category = new \grade_category($this->grade_categories[0]);
|
||||
$this->assertTrue(method_exists($category, 'set_locked'));
|
||||
|
||||
// Will return false as cannot lock a grade that needs updating.
|
||||
$this->assertFalse($category->set_locked(1));
|
||||
// Even though a grade that needs updating cannot be locked, set_locked will return true because it will successfully
|
||||
// schedule the locking for as soon as final grades are recalculated.
|
||||
$this->assertTrue($category->set_locked(1));
|
||||
// The category should not be locked yet as we are waiting for a recalculation.
|
||||
$this->assertFalse($category->is_locked());
|
||||
grade_regrade_final_grades($this->courseid);
|
||||
|
||||
// Get the category from the db again.
|
||||
$category = new \grade_category($this->grade_categories[0]);
|
||||
$this->assertTrue($category->set_locked(1));
|
||||
// The category is locked now.
|
||||
$this->assertTrue($category->is_locked());
|
||||
}
|
||||
|
||||
protected function sub_test_grade_category_is_hidden() {
|
||||
|
||||
+3
-1
@@ -64,7 +64,7 @@ information provided here is intended especially for developers.
|
||||
the page displays a heading for the activity (usually a h2 heading containing the activity name).
|
||||
* New method moodleform::filter_shown_headers() is created to show some expanded headers only and hide the rest.
|
||||
* count_words() and count_letters() have a new optional parameter called $format to format the text before doing the counting.
|
||||
* New core_renderer::sr_text method to generate screen reader only inline texts without using html_writter.
|
||||
* New core_renderer::sr_text method to generate screen reader only inline texts without using html_writer.
|
||||
* New events \core\event\qbank_plugin_enabled and \core\event\qbank_plugin_disabled are triggered when a qbank plugin is enabled or
|
||||
disabled respectively, with the plugin's frankenstyle name. Any plugins that need to perform an action in response to a qbank
|
||||
plugin being enabled or disabled should observe these events.
|
||||
@@ -163,6 +163,8 @@ being forced open in all behat tests.
|
||||
but a subset can be specified instead.
|
||||
* core/form-autocomplete now supports disabled options in the source select list. These will be displayed in the autocomplete
|
||||
options with the aria-disabled attribute, and will not be selectable.
|
||||
* The method grade_item::set_locked() now returns true if the grade item needs to be updated. The method schedules the locking of
|
||||
the grade item once the recalculations are completed. (This was fixed in 4.3, 4.2.2)
|
||||
|
||||
=== 4.2 ===
|
||||
|
||||
|
||||
Reference in New Issue
Block a user