Esempio n. 1
0
 /**
  * Builds and returns the rows that will make up the right part of the grader report
  * @param boolean $displayaverages whether to display average rows in the table
  * @return array Array of html_table_row objects
  */
 public function get_right_rows($displayaverages)
 {
     global $CFG, $USER, $OUTPUT, $DB, $PAGE;
     $rows = array();
     $this->rowcount = 0;
     $numrows = count($this->gtree->get_levels());
     $numusers = count($this->users);
     $gradetabindex = 1;
     $columnstounset = array();
     $strgrade = $this->get_lang_string('grade');
     $strfeedback = $this->get_lang_string("feedback");
     $arrows = $this->get_sort_arrows();
     $jsarguments = array('cfg' => array('ajaxenabled' => false), 'items' => array(), 'users' => array(), 'feedback' => array(), 'grades' => array());
     $jsscales = array();
     // Get preferences once.
     $showactivityicons = $this->get_pref('showactivityicons');
     $quickgrading = $this->get_pref('quickgrading');
     $showquickfeedback = $this->get_pref('showquickfeedback');
     $enableajax = $this->get_pref('enableajax');
     $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');
     foreach ($this->gtree->get_levels() as $key => $row) {
         $headingrow = new html_table_row();
         $headingrow->attributes['class'] = 'heading_name_row';
         foreach ($row as $columnkey => $element) {
             $sortlink = clone $this->baseurl;
             if (isset($element['object']->id)) {
                 $sortlink->param('sortitemid', $element['object']->id);
             }
             $eid = $element['eid'];
             $object = $element['object'];
             $type = $element['type'];
             $categorystate = @$element['categorystate'];
             if (!empty($element['colspan'])) {
                 $colspan = $element['colspan'];
             } else {
                 $colspan = 1;
             }
             if (!empty($element['depth'])) {
                 $catlevel = 'catlevel' . $element['depth'];
             } else {
                 $catlevel = '';
             }
             // Element is a filler
             if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') {
                 $fillercell = new html_table_cell();
                 $fillercell->attributes['class'] = $type . ' ' . $catlevel;
                 $fillercell->colspan = $colspan;
                 $fillercell->text = ' ';
                 // This is a filler cell; don't use a <th>, it'll confuse screen readers.
                 $fillercell->header = false;
                 $headingrow->cells[] = $fillercell;
             } else {
                 if ($type == 'category') {
                     // 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);
                         }
                         $headingrow->cells[] = $categorycell;
                     }
                 } else {
                     // Element is a grade_item
                     if ($element['object']->id == $this->sortitemid) {
                         if ($this->sortorder == 'ASC') {
                             $arrow = $this->get_sort_arrow('up', $sortlink);
                         } else {
                             $arrow = $this->get_sort_arrow('down', $sortlink);
                         }
                     } else {
                         $arrow = $this->get_sort_arrow('move', $sortlink);
                     }
                     $headerlink = $this->gtree->get_element_header($element, true, $showactivityicons, false, false, true);
                     $itemcell = new html_table_cell();
                     $itemcell->attributes['class'] = $type . ' ' . $catlevel . ' highlightable' . ' i' . $element['object']->id;
                     $itemcell->attributes['data-itemid'] = $element['object']->id;
                     if ($element['object']->is_hidden()) {
                         $itemcell->attributes['class'] .= ' dimmed_text';
                     }
                     $singleview = '';
                     // FIXME: MDL-52678 This is extremely hacky we should have an API for inserting grade column links.
                     if (get_capability_info('gradereport/singleview:view')) {
                         if (has_all_capabilities(array('gradereport/singleview:view', 'moodle/grade:viewall', 'moodle/grade:edit'), $this->context)) {
                             $url = new moodle_url('/grade/report/singleview/index.php', array('id' => $this->course->id, 'item' => 'grade', 'itemid' => $element['object']->id));
                             $singleview = $OUTPUT->action_icon($url, new pix_icon('t/editstring', get_string('singleview', 'grades', $element['object']->get_name())));
                         }
                     }
                     $itemcell->colspan = $colspan;
                     $itemcell->text = shorten_text($headerlink) . $arrow . $singleview;
                     $itemcell->header = true;
                     $itemcell->scope = 'col';
                     $headingrow->cells[] = $itemcell;
                 }
             }
         }
         $rows[] = $headingrow;
     }
     $rows = $this->get_right_icons_row($rows);
     // Preload scale objects for items with a scaleid and initialize tab indices
     $scaleslist = array();
     $tabindices = array();
     foreach ($this->gtree->get_items() as $itemid => $item) {
         $scale = null;
         if (!empty($item->scaleid)) {
             $scaleslist[] = $item->scaleid;
             $jsarguments['items'][$itemid] = array('id' => $itemid, 'name' => $item->get_name(true), 'type' => 'scale', 'scale' => $item->scaleid, 'decimals' => $item->get_decimals());
         } else {
             $jsarguments['items'][$itemid] = array('id' => $itemid, 'name' => $item->get_name(true), 'type' => 'value', 'scale' => false, 'decimals' => $item->get_decimals());
         }
         $tabindices[$item->id]['grade'] = $gradetabindex;
         $tabindices[$item->id]['feedback'] = $gradetabindex + $numusers;
         $gradetabindex += $numusers * 2;
     }
     $scalesarray = array();
     if (!empty($scaleslist)) {
         $scalesarray = $DB->get_records_list('scale', 'id', $scaleslist);
     }
     $jsscales = $scalesarray;
     // Get all the grade items if the user can not view hidden grade items.
     // It is possible that the user is simply viewing the 'Course total' by switching to the 'Aggregates only' view
     // and that this user does not have the ability to view hidden items. In this case we still need to pass all the
     // grade items (in case one has been hidden) as the course total shown needs to be adjusted for this particular
     // user.
     if (!$this->canviewhidden) {
         $allgradeitems = grade_item::fetch_all(array('courseid' => $this->courseid));
     }
     foreach ($this->users as $userid => $user) {
         if ($this->canviewhidden) {
             $altered = array();
             $unknown = array();
         } else {
             $usergrades = $this->allgrades[$userid];
             $hidingaffected = grade_grade::get_hiding_affected($usergrades, $allgradeitems);
             $altered = $hidingaffected['altered'];
             $unknown = $hidingaffected['unknown'];
             unset($hidingaffected);
         }
         $itemrow = new html_table_row();
         $itemrow->id = 'user_' . $userid;
         $fullname = fullname($user);
         $jsarguments['users'][$userid] = $fullname;
         foreach ($this->gtree->items as $itemid => $unused) {
             $item =& $this->gtree->items[$itemid];
             $grade = $this->grades[$userid][$item->id];
             $itemcell = new html_table_cell();
             $itemcell->id = 'u' . $userid . 'i' . $itemid;
             $itemcell->attributes['data-itemid'] = $itemid;
             // Get the decimal points preference for this item
             $decimalpoints = $item->get_decimals();
             if (in_array($itemid, $unknown)) {
                 $gradeval = null;
             } else {
                 if (array_key_exists($itemid, $altered)) {
                     $gradeval = $altered[$itemid];
                 } else {
                     $gradeval = $grade->finalgrade;
                 }
             }
             if (!empty($grade->finalgrade)) {
                 $gradevalforjs = null;
                 if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
                     $gradevalforjs = (int) $gradeval;
                 } else {
                     $gradevalforjs = format_float($gradeval, $decimalpoints);
                 }
                 $jsarguments['grades'][] = array('user' => $userid, 'item' => $itemid, 'grade' => $gradevalforjs);
             }
             // MDL-11274
             // Hide grades in the grader report if the current grader doesn't have 'moodle/grade:viewhidden'
             if (!$this->canviewhidden and $grade->is_hidden()) {
                 if (!empty($CFG->grade_hiddenasdate) and $grade->get_datesubmitted() and !$item->is_category_item() and !$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 = "<span class='datesubmitted'>" . userdate($grade->get_datesubmitted(), $strftimedatetimeshort) . "</span>";
                 } else {
                     $itemcell->text = '-';
                 }
                 $itemrow->cells[] = $itemcell;
                 continue;
             }
             // emulate grade element
             $eid = $this->gtree->get_grade_eid($grade);
             $element = array('eid' => $eid, 'object' => $grade, 'type' => 'grade');
             $itemcell->attributes['class'] .= ' grade i' . $itemid;
             if ($item->is_category_item()) {
                 $itemcell->attributes['class'] .= ' cat';
             }
             if ($item->is_course_item()) {
                 $itemcell->attributes['class'] .= ' course';
             }
             if ($grade->is_overridden()) {
                 $itemcell->attributes['class'] .= ' overridden';
                 $itemcell->attributes['aria-label'] = get_string('overriddengrade', 'gradereport_grader');
             }
             if (!empty($grade->feedback)) {
                 $feedback = wordwrap(trim(format_string($grade->feedback, $grade->feedbackformat)), 34, '<br>');
                 $itemcell->attributes['data-feedback'] = $feedback;
                 $jsarguments['feedback'][] = array('user' => $userid, 'item' => $itemid, 'content' => $feedback);
             }
             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 <span> or text.
                 $itemcell->text .= " <span class='excludedfloater'>" . $strexcludedgrades . "</span> ";
             }
             // Do not show any icons if no grade (no record in DB to match)
             if (!$item->needsupdate and $USER->gradeediting[$this->courseid]) {
                 $itemcell->text .= $this->get_icons($element);
             }
             $hidden = '';
             if ($grade->is_hidden()) {
                 $hidden = ' dimmed_text ';
             }
             $gradepass = '******';
             if ($grade->is_passed($item)) {
                 $gradepass = '******';
             } else {
                 if (is_null($grade->is_passed($item))) {
                     $gradepass = '';
                 }
             }
             // 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 .= "<span class='gradingerror{$hidden}'>" . $strerror . "</span>";
             } else {
                 if ($USER->gradeediting[$this->courseid]) {
                     if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
                         $itemcell->attributes['class'] .= ' grade_type_scale';
                     } else {
                         if ($item->gradetype == GRADE_TYPE_VALUE) {
                             $itemcell->attributes['class'] .= ' grade_type_value';
                         } else {
                             if ($item->gradetype == GRADE_TYPE_TEXT) {
                                 $itemcell->attributes['class'] .= ' grade_type_text';
                             }
                         }
                     }
                     if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
                         $scale = $scalesarray[$item->scaleid];
                         $gradeval = (int) $gradeval;
                         // scales use only integers
                         $scales = explode(",", $scale->scale);
                         // reindex because scale is off 1
                         // MDL-12104 some previous scales might have taken up part of the array
                         // so this needs to be reset
                         $scaleopt = array();
                         $i = 0;
                         foreach ($scales as $scaleoption) {
                             $i++;
                             $scaleopt[$i] = $scaleoption;
                         }
                         if ($quickgrading and $grade->is_editable()) {
                             $oldval = empty($gradeval) ? -1 : $gradeval;
                             if (empty($item->outcomeid)) {
                                 $nogradestr = $this->get_lang_string('nograde');
                             } else {
                                 $nogradestr = $this->get_lang_string('nooutcome', 'grades');
                             }
                             $attributes = array('tabindex' => $tabindices[$item->id]['grade'], 'id' => 'grade_' . $userid . '_' . $item->id);
                             $gradelabel = $fullname . ' ' . $item->itemname;
                             $itemcell->text .= html_writer::label(get_string('useractivitygrade', 'gradereport_grader', $gradelabel), $attributes['id'], false, array('class' => 'accesshide'));
                             $itemcell->text .= html_writer::select($scaleopt, 'grade[' . $userid . '][' . $item->id . ']', $gradeval, array(-1 => $nogradestr), $attributes);
                         } else {
                             if (!empty($scale)) {
                                 $scales = explode(",", $scale->scale);
                                 // invalid grade if gradeval < 1
                                 if ($gradeval < 1) {
                                     $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>-</span>";
                                 } else {
                                     $gradeval = $grade->grade_item->bounded_grade($gradeval);
                                     //just in case somebody changes scale
                                     $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>{$scales[$gradeval - 1]}</span>";
                                 }
                             }
                         }
                     } else {
                         if ($item->gradetype != GRADE_TYPE_TEXT) {
                             // Value type
                             if ($quickgrading and $grade->is_editable()) {
                                 $value = format_float($gradeval, $decimalpoints);
                                 $gradelabel = $fullname . ' ' . $item->itemname;
                                 $itemcell->text .= '<label class="accesshide" for="grade_' . $userid . '_' . $item->id . '">' . get_string('useractivitygrade', 'gradereport_grader', $gradelabel) . '</label>';
                                 $itemcell->text .= '<input size="6" tabindex="' . $tabindices[$item->id]['grade'] . '" type="text" class="text" title="' . $strgrade . '" name="grade[' . $userid . '][' . $item->id . ']" id="grade_' . $userid . '_' . $item->id . '" value="' . $value . '" />';
                             } else {
                                 $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>" . format_float($gradeval, $decimalpoints) . "</span>";
                             }
                         }
                     }
                     // If quickfeedback is on, print an input element
                     if ($showquickfeedback and $grade->is_editable()) {
                         $feedbacklabel = $fullname . ' ' . $item->itemname;
                         $itemcell->text .= '<label class="accesshide" for="feedback_' . $userid . '_' . $item->id . '">' . get_string('useractivityfeedback', 'gradereport_grader', $feedbacklabel) . '</label>';
                         $itemcell->text .= '<input class="quickfeedback" tabindex="' . $tabindices[$item->id]['feedback'] . '" id="feedback_' . $userid . '_' . $item->id . '" size="6" title="' . $strfeedback . '" type="text" name="feedback[' . $userid . '][' . $item->id . ']" value="' . s($grade->feedback) . '" />';
                     }
                 } else {
                     // Not editing
                     $gradedisplaytype = $item->get_displaytype();
                     if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
                         $itemcell->attributes['class'] .= ' grade_type_scale';
                     } else {
                         if ($item->gradetype == GRADE_TYPE_VALUE) {
                             $itemcell->attributes['class'] .= ' grade_type_value';
                         } else {
                             if ($item->gradetype == GRADE_TYPE_TEXT) {
                                 $itemcell->attributes['class'] .= ' grade_type_text';
                             }
                         }
                     }
                     // Only allow edting if the grade is editable (not locked, not in a unoverridable category, etc).
                     if ($enableajax && $grade->is_editable()) {
                         // If a grade item is type text, and we don't have show quick feedback on, it can't be edited.
                         if ($item->gradetype != GRADE_TYPE_TEXT || $showquickfeedback) {
                             $itemcell->attributes['class'] .= ' clickable';
                         }
                     }
                     if ($item->needsupdate) {
                         $itemcell->text .= "<span class='gradingerror{$hidden}{$gradepass}'>" . $error . "</span>";
                     } else {
                         // The max and min for an aggregation may be different to the grade_item.
                         if (!is_null($gradeval)) {
                             $item->grademax = $grade->get_grade_max();
                             $item->grademin = $grade->get_grade_min();
                         }
                         $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>" . grade_format_gradevalue($gradeval, $item, true, $gradedisplaytype, null) . "</span>";
                         if ($showanalysisicon) {
                             $itemcell->text .= $this->gtree->get_grade_analysis_icon($grade);
                         }
                     }
                 }
             }
             // Enable keyboard navigation if the grade is editable (not locked, not in a unoverridable category, etc).
             if ($enableajax && $grade->is_editable()) {
                 // If a grade item is type text, and we don't have show quick feedback on, it can't be edited.
                 if ($item->gradetype != GRADE_TYPE_TEXT || $showquickfeedback) {
                     $itemcell->attributes['class'] .= ' gbnavigable';
                 }
             }
             if (!empty($this->gradeserror[$item->id][$userid])) {
                 $itemcell->text .= $this->gradeserror[$item->id][$userid];
             }
             $itemrow->cells[] = $itemcell;
         }
         $rows[] = $itemrow;
     }
     if ($enableajax) {
         $jsarguments['cfg']['ajaxenabled'] = true;
         $jsarguments['cfg']['scales'] = array();
         foreach ($jsscales as $scale) {
             // Trim the scale values, as they may have a space that is ommitted from values later.
             $jsarguments['cfg']['scales'][$scale->id] = array_map('trim', explode(',', $scale->scale));
         }
         $jsarguments['cfg']['feedbacktrunclength'] = $this->feedback_trunc_length;
         // Student grades and feedback are already at $jsarguments['feedback'] and $jsarguments['grades']
     }
     $jsarguments['cfg']['isediting'] = (bool) $USER->gradeediting[$this->courseid];
     $jsarguments['cfg']['courseid'] = $this->courseid;
     $jsarguments['cfg']['studentsperpage'] = $this->get_students_per_page();
     $jsarguments['cfg']['showquickfeedback'] = (bool) $showquickfeedback;
     $module = array('name' => 'gradereport_grader', 'fullpath' => '/grade/report/grader/module.js', 'requires' => array('base', 'dom', 'event', 'event-mouseenter', 'event-key', 'io-queue', 'json-parse', 'overlay'));
     $PAGE->requires->js_init_call('M.gradereport_grader.init_report', $jsarguments, false, $module);
     $PAGE->requires->strings_for_js(array('addfeedback', 'feedback', 'grade'), 'grades');
     $PAGE->requires->strings_for_js(array('ajaxchoosescale', 'ajaxclicktoclose', 'ajaxerror', 'ajaxfailedupdate', 'ajaxfieldchanged'), 'gradereport_grader');
     if (!$enableajax && $USER->gradeediting[$this->courseid]) {
         $PAGE->requires->yui_module('moodle-core-formchangechecker', 'M.core_formchangechecker.init', array(array('formid' => 'gradereport_grader')));
         $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
     }
     $rows = $this->get_right_range_row($rows);
     if ($displayaverages) {
         $rows = $this->get_right_avg_row($rows, true);
         $rows = $this->get_right_avg_row($rows);
     }
     return $rows;
 }
Esempio n. 2
0
 /**
  * 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));
                 }
             }
         }
     }
 }