diff --git a/grade/edit/tree/lib.php b/grade/edit/tree/lib.php index a2bc4f44567..6227d0291f4 100644 --- a/grade/edit/tree/lib.php +++ b/grade/edit/tree/lib.php @@ -538,6 +538,13 @@ class grade_edit_tree { } $deepest_level = $this->get_deepest_level($child_el, $level, $deepest_level); } + + $category = grade_category::fetch(array('id' => $object->id)); + $item = $category->get_grade_item(); + if ($item->gradetype == GRADE_TYPE_NONE) { + // Add 1 more level for grade category that has no total. + $deepest_level++; + } } return $deepest_level; diff --git a/grade/lib.php b/grade/lib.php index 1a7c933b621..a5b050b912a 100644 --- a/grade/lib.php +++ b/grade/lib.php @@ -2361,6 +2361,44 @@ class grade_tree extends grade_structure { } } + /** + * Determines whether the grade tree item can be displayed. + * This is particularly targeted for grade categories that have no total (None) when rendering the grade tree. + * It checks if the grade tree item is of type 'category', and makes sure that the category, or at least one of children, + * can be output. + * + * @param array $element The grade category element. + * @return bool True if the grade tree item can be displayed. False, otherwise. + */ + public static function can_output_item($element) { + $canoutput = true; + + if ($element['type'] === 'category') { + $object = $element['object']; + $category = grade_category::fetch(array('id' => $object->id)); + // Category has total, we can output this. + if ($category->get_grade_item()->gradetype != GRADE_TYPE_NONE) { + return true; + } + + // Category has no total and has no children, no need to output this. + if (empty($element['children'])) { + return false; + } + + $canoutput = false; + // Loop over children and make sure at least one child can be output. + foreach ($element['children'] as $child) { + $canoutput = self::can_output_item($child); + if ($canoutput) { + break; + } + } + } + + return $canoutput; + } + /** * Static recursive helper - makes full tree (all leafes are at the same level) * @@ -2390,6 +2428,9 @@ class grade_tree extends grade_structure { if ($chd == $maxdepth) { continue; } + if (!self::can_output_item($element['children'][$chid])) { + continue; + } for ($i=0; $i < $maxdepth-$chd; $i++) { if ($chid == $first_child) { $type = 'fillerfirst'; @@ -2421,6 +2462,9 @@ class grade_tree extends grade_structure { } $count = 0; foreach ($element['children'] as $key=>$child) { + if (!self::can_output_item($child)) { + continue; + } $count += grade_tree::inject_colspans($element['children'][$key]); } $element['colspan'] = $count; diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php index c89f303ddfb..be4dbed3f90 100644 --- a/grade/report/grader/lib.php +++ b/grade/report/grader/lib.php @@ -819,20 +819,23 @@ class grade_report_grader extends grade_report { $fillercell->header = false; $headingrow->cells[] = $fillercell; } else if ($type == 'category') { - // Element is a category - $categorycell = new html_table_cell(); - $categorycell->attributes['class'] = 'category ' . $catlevel; - $categorycell->colspan = $colspan; - $categorycell->text = $this->get_course_header($element); - $categorycell->header = true; - $categorycell->scope = 'col'; + // Make sure the grade category has a grade total or at least has child grade items. + if (grade_tree::can_output_item($element)) { + // Element is a category. + $categorycell = new html_table_cell(); + $categorycell->attributes['class'] = 'category ' . $catlevel; + $categorycell->colspan = $colspan; + $categorycell->text = $this->get_course_header($element); + $categorycell->header = true; + $categorycell->scope = 'col'; - // Print icons - if ($USER->gradeediting[$this->courseid]) { - $categorycell->text .= $this->get_icons($element); + // Print icons. + if ($USER->gradeediting[$this->courseid]) { + $categorycell->text .= $this->get_icons($element); + } + + $headingrow->cells[] = $categorycell; } - - $headingrow->cells[] = $categorycell; } else { // Element is a grade_item if ($element['object']->id == $this->sortitemid) { diff --git a/grade/tests/lib_test.php b/grade/tests/lib_test.php new file mode 100644 index 00000000000..4969135b38a --- /dev/null +++ b/grade/tests/lib_test.php @@ -0,0 +1,172 @@ +. + +/** + * Unit tests for grade/lib.php. + * + * @package core_grades + * @category test + * @copyright 2016 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/grade/lib.php'); + +/** + * Unit tests for grade/lib.php. + * + * @package core_grades + * @category test + * @copyright 2016 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_grade_lib_test extends advanced_testcase { + + /** + * Test can_output_item. + */ + public function test_can_output_item() { + $this->resetAfterTest(); + + $generator = $this->getDataGenerator(); + + // Course level grade category. + $course = $generator->create_course(); + // Grade tree looks something like: + // - Test course (Rendered). + $gradetree = grade_category::fetch_course_tree($course->id); + $this->assertTrue(grade_tree::can_output_item($gradetree)); + + // Add a grade category with default settings. + $generator->create_grade_category(array('courseid' => $course->id)); + // Grade tree now looks something like: + // - Test course n (Rendered). + // -- Grade category n (Rendered). + $gradetree = grade_category::fetch_course_tree($course->id); + $this->assertNotEmpty($gradetree['children']); + foreach ($gradetree['children'] as $child) { + $this->assertTrue(grade_tree::can_output_item($child)); + } + + // Add a grade category with grade type = None. + $nototalcategory = 'No total category'; + $nototalparams = [ + 'courseid' => $course->id, + 'fullname' => $nototalcategory, + 'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN + ]; + $nototal = $generator->create_grade_category($nototalparams); + $catnototal = grade_category::fetch(array('id' => $nototal->id)); + // Set the grade type of the grade item associated to the grade category. + $catitemnototal = $catnototal->load_grade_item(); + $catitemnototal->gradetype = GRADE_TYPE_NONE; + $catitemnototal->update(); + + // Grade tree looks something like: + // - Test course n (Rendered). + // -- Grade category n (Rendered). + // -- No total category (Not rendered). + $gradetree = grade_category::fetch_course_tree($course->id); + foreach ($gradetree['children'] as $child) { + if ($child['object']->fullname == $nototalcategory) { + $this->assertFalse(grade_tree::can_output_item($child)); + } else { + $this->assertTrue(grade_tree::can_output_item($child)); + } + } + + // Add another grade category with default settings under 'No total category'. + $normalinnototalparams = [ + 'courseid' => $course->id, + 'fullname' => 'Normal category in no total category', + 'parent' => $nototal->id + ]; + $generator->create_grade_category($normalinnototalparams); + + // Grade tree looks something like: + // - Test course n (Rendered). + // -- Grade category n (Rendered). + // -- No total category (Rendered). + // --- Normal category in no total category (Rendered). + $gradetree = grade_category::fetch_course_tree($course->id); + foreach ($gradetree['children'] as $child) { + // All children are now visible. + $this->assertTrue(grade_tree::can_output_item($child)); + if (!empty($child['children'])) { + foreach ($child['children'] as $grandchild) { + $this->assertTrue(grade_tree::can_output_item($grandchild)); + } + } + } + + // Add a grade category with grade type = None. + $nototalcategory2 = 'No total category 2'; + $nototal2params = [ + 'courseid' => $course->id, + 'fullname' => $nototalcategory2, + 'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN + ]; + $nototal2 = $generator->create_grade_category($nototal2params); + $catnototal2 = grade_category::fetch(array('id' => $nototal2->id)); + // Set the grade type of the grade item associated to the grade category. + $catitemnototal2 = $catnototal2->load_grade_item(); + $catitemnototal2->gradetype = GRADE_TYPE_NONE; + $catitemnototal2->update(); + + // Add a category with no total under 'No total category'. + $nototalinnototalcategory = 'Category with no total in no total category'; + $nototalinnototalparams = [ + 'courseid' => $course->id, + 'fullname' => $nototalinnototalcategory, + 'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN, + 'parent' => $nototal2->id + ]; + $nototalinnototal = $generator->create_grade_category($nototalinnototalparams); + $catnototalinnototal = grade_category::fetch(array('id' => $nototalinnototal->id)); + // Set the grade type of the grade item associated to the grade category. + $catitemnototalinnototal = $catnototalinnototal->load_grade_item(); + $catitemnototalinnototal->gradetype = GRADE_TYPE_NONE; + $catitemnototalinnototal->update(); + + // Grade tree looks something like: + // - Test course n (Rendered). + // -- Grade category n (Rendered). + // -- No total category (Rendered). + // --- Normal category in no total category (Rendered). + // -- No total category 2 (Not rendered). + // --- Category with no total in no total category (Not rendered). + $gradetree = grade_category::fetch_course_tree($course->id); + foreach ($gradetree['children'] as $child) { + if ($child['object']->fullname == $nototalcategory2) { + $this->assertFalse(grade_tree::can_output_item($child)); + } else { + $this->assertTrue(grade_tree::can_output_item($child)); + } + if (!empty($child['children'])) { + foreach ($child['children'] as $grandchild) { + if ($grandchild['object']->fullname == $nototalinnototalcategory) { + $this->assertFalse(grade_tree::can_output_item($grandchild)); + } else { + $this->assertTrue(grade_tree::can_output_item($grandchild)); + } + } + } + } + } +}