diff --git a/grade/report.php b/grade/report.php index 8a63da1b4c2..c82967e8fab 100644 --- a/grade/report.php +++ b/grade/report.php @@ -29,7 +29,6 @@ $courseid = required_param('id'); // course id $report = optional_param('report', 'user', PARAM_FILE); // course id $edit = optional_param('edit', -1, PARAM_BOOL); // sticky editting mode - $feedback = optional_param('feedback', -1, PARAM_BOOL); // sticky feedback mode /// Make sure they can even access this course @@ -99,7 +98,7 @@ $USER->gradeediting = 0; } - // params for the turn editting on and feedback buttons + // params for the turn editting on $options['id'] = $courseid; $options['report'] = $report; diff --git a/grade/report/grader/grader_report.php b/grade/report/grader/grader_report.php new file mode 100644 index 00000000000..2a5f0aa84e4 --- /dev/null +++ b/grade/report/grader/grader_report.php @@ -0,0 +1,1136 @@ +libdir.'/tablelib.php'); +require_once($CFG->libdir.'/gradelib.php'); +require_once($CFG->dirroot.'/grade/report/lib.php'); + +/** + * Class providing an API for the grader report building and displaying. + * @package gradebook + */ +class grade_report_grader { + /** + * The courseid. + * @var int $courseid + */ + var $courseid; + + /** + * The context. + * @var int $context + */ + var $context; + + /** + * The grade_tree object. + * @var object $gtree + */ + var $gtree; + + /** + * The final grades. + * @var array $finalgrades + */ + var $finalgrades; + + /** + * The grade items. + * @var array $items + */ + var $items; + + /** + * Array of errors for bulk grades updating. + * @var array $gradeserror + */ + var $gradeserror = array(); + +//// USER PREFERENCES + + /** + * Number of users on a page. + * @var int $perpage + */ + var $studentsperpage; + + /** + * Number of digits after the decimal point. + * @var int $decimalspoints + */ + var $decimalspoints; + + /** + * Whether or not to display the grandtotals row. + * @var bool $showgrandtotals + */ + var $showgrandtotals; + + /** + * Whether or not to display group selector, total row and other group-related elements. + * @var bool $showgroups + */ + var $showgroups; + + /** + * The position of the Aggregation column in relation to the raw grade items. + * @var int $aggregation_position + */ + var $aggregation_position; + + /** + * Whether or not to display a row of scales/ranges for each grade_item. + * @var bool $showscales + */ + var $showscales; + + /** + * Whether or not to use quickgrading. + * @var bool $quickgrading + */ + var $quickgrading; + + /** + * Whether or not to use quickfeedback. + * @var bool $quickfeedback + */ + var $quickfeedback; + +//// SQL-RELATED + + /** + * The roles for this report. + * @var string $gradebookroles + */ + var $gradebookroles; + + /** + * base url for sorting by first/last name. + * @var string $baseurl + */ + var $baseurl; + + /** + * base url for paging. + * @var string $pbarurl + */ + var $pbarurl; + + /** + * Current page (for paging). + * @var int $page + */ + var $page; + + /** + * The id of the grade_item by which this report will be sorted. + * @var int $sortitemid + */ + var $sortitemid; + + /** + * Sortorder used in the SQL selections. + * @var int $sortorder + */ + var $sortorder; + + /** + * An SQL fragment affecting the search for users. + * @var string $userselect + */ + var $userselect; + +//// GROUP VARIABLES (including SQL) + + /** + * The current group being displayed. + * @var int $currentgroup + */ + var $currentgroup; + + /** + * A HTML select element used to select the current group. + * @var string $group_selector + */ + var $group_selector; + + /** + * An SQL fragment used to add linking information to the group tables. + * @var string $groupsql + */ + var $groupsql; + + /** + * An SQL constraint to append to the queries used by this object to build the report. + * @var string $groupwheresql + */ + var $groupwheresql; + + + + /** + * Constructor. Sets local copies of user preferences and initialises grade_tree. + * @param int $courseid + */ + function grade_report_grader($courseid, $context, $page=null, $sortitemid=null) { + global $CFG; + + $this->courseid = $courseid; + $this->context = $context; + $this->page = $page; + $this->sortitemid = $sortitemid; + + // roles to be displayed in the gradebook + $this->gradebookroles = $CFG->gradebookroles; + + // User preferences + $this->studentsperpage = get_user_preferences('grade_report_studentsperpage', + $CFG->grade_report_studentsperpage); + $this->decimalpoints = get_user_preferences('grade_report_decimalpoints', + $CFG->grade_report_decimalpoints); + $this->showgrandtotals = get_user_preferences('grade_report_showgrandtotals', + $CFG->grade_report_showgrandtotals); + $this->showgroups = get_user_preferences('grade_report_showgroups', + $CFG->grade_report_showgroups); + $this->aggregation_position = get_user_preferences('grade_report_aggregationposition', + $CFG->grade_report_aggregationposition); + $this->showscales = get_user_preferences('grade_report_showscales', + $CFG->grade_report_showscales); + $this->quickgrading = get_user_preferences('grade_report_quickgrading', + $CFG->grade_report_quickgrading); + $this->quickfeedback = get_user_preferences('grade_report_quickfeedback', + $CFG->grade_report_quickfeedback); + + // Grab the grade_tree for this course + $this->gtree = new grade_tree($this->courseid, true, false, $this->aggregation_position); + + // base url for sorting by first/last name + $this->baseurl = 'report.php?id='.$this->courseid.'&perpage='.$this->studentsperpage.'&report=grader&page='.$this->page; + // + $this->pbarurl = 'report.php?id='.$this->courseid.'&perpage='.$this->studentsperpage.'&report=grader&'; + + if ($this->showgroups) { + $this->setup_groups(); + } + + $this->setup_sortitemid(); + } + + /** + * Uses set_user_preferences() to update the value of a user preference. + * Also updates the object's corresponding variable. + * @param string $pref_name The name of the preference. + * @param mixed $pref_value The value of the preference. + * @return bool Success or failure. + * TODO print visual feedback + */ + function set_user_pref($pref_name, $pref_value) { + if ($result = set_user_preferences(array($pref_name => $pref_value))) { + $this->$pref_name = $pref_value; + } + return $result; + } + + /** + * Processes the data sent by the form (grades and feedbacks). + * @var array $data + * @return bool Success or Failure (array of errors). + */ + function process_data($data) { + // always initialize all arrays + $queue = array(); + + foreach ($data as $varname => $postedvalue) { + // this is a bit tricky - we have to first load all grades into memory, + // check if changed and only then start updating the final grades because + // columns might depend one on another - the result would be overriden calculated and category grades + + $needsupdate = false; + $note = false; // TODO implement note?? + + // skip, not a grade nor feedback + $data_type = ''; + if (strstr($varname, 'grade')) { + $data_type = 'grade'; + } elseif (strstr($varname, 'feedback')) { + $data_type = 'feedback'; + } else { + continue; + } + + $gradeinfo = explode("_", $varname); + + $userid = clean_param($gradeinfo[1], PARAM_INT); + $itemid = clean_param($gradeinfo[2], PARAM_INT); + + if (!$grade_item = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid))) { // we must verify course id here! + error('Incorrect grade item id'); + } + + // Pre-process grade + if ($data_type == 'grade') { + + if ($grade_item->gradetype == GRADE_TYPE_SCALE) { + if ($postedvalue == -1) { // -1 means no grade + $finalgrade = null; + } else { + $finalgrade = (float)$postedvalue; + } + } else { + if ($postedvalue == '') { // empty string means no grade + $finalgrade = null; + } else { + $finalgrade = format_grade($postedvalue); + } + } + + if (!is_null($finalgrade) and ($finalgrade < $grade_item->grademin or $finalgrade > $grade_item->grademax)) { + $this->gradeserror[$grade_item->id][$userid] = 'outofrange'; //TODO: localize + // another possiblity is to use bounded number instead + continue; + } + } + + // Get the grade object to compare old value with new value + if ($grade = grade_grades::fetch(array('userid'=>$userid, 'itemid'=>$grade_item->id))) { + if ($data_type == 'feedback') { + $finalgrade = false; + $text = $grade->load_text(); + if ($text != s($postedvalue)) { + $feedback = s($postedvalue); + $feedbackformat = GRADER_REPORT_FEEDBACK_FORMAT_TEXT; + $needsupdate = true; + } + } elseif ($data_type == 'grade') { + $feedback = false; + $feedbackformat = false; + if (!is_null($grade->finalgrade)) { + $grade->finalgrade = (float)$grade->finalgrade; + } + if ($grade->finalgrade === $finalgrade) { + $needsupdate = true; + } + } + + } + + // we must not update all grades, only changed ones - we do not want to mark everything as overriden + if ($needsupdate) { + $gradedata = new object(); + $gradedata->grade_item = $grade_item; + $gradedata->userid = $userid; + $gradedata->note = $note; + $gradedata->finalgrade = $finalgrade; + $gradedata->feedback = $feedback; + $gradedata->feedbackformat = $feedbackformat; + + $queue[] = $gradedata; + } + } + + // now we update the new final grade for each changed grade + foreach ($queue as $gradedata) { + $gradedata->grade_item->update_final_grade($gradedata->userid, $gradedata->finalgrade, 'gradebook', + $gradedata->note, $gradedata->feedback, $gradedata->feedbackformat); + } + + return true; + } + + /** + * Sets up this object's group variables, mainly to restrict the selection of users to display. + */ + function setup_groups() { + global $CFG; + + /// find out current groups mode + $course = get_record('course', 'id', $this->courseid); + $groupmode = $course->groupmode; + ob_start(); + $this->currentgroup = setup_and_print_groups($course, $groupmode, $this->baseurl); + $this->group_selector = ob_get_clean(); + + // update paging after group + $this->baseurl .= 'group='.$this->currentgroup.'&'; + $this->pbarurl .= 'group='.$this->currentgroup.'&'; + + if ($this->currentgroup) { + $this->groupsql = " LEFT JOIN {$CFG->prefix}groups_members gm ON gm.userid = u.id "; + $this->groupwheresql = " AND gm.groupid = $this->currentgroup "; + } + } + + /** + * Setting the sort order, this depends on last state + * all this should be in the new table class that we might need to use + * for displaying grades. + */ + function setup_sortitemid() { + if ($this->sortitemid) { + if (!isset($SESSION->gradeuserreport->sort)) { + $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; + } else { + // this is the first sort, i.e. by last name + if (!isset($SESSION->gradeuserreport->sortitemid)) { + $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; + } else if ($SESSION->gradeuserreport->sortitemid == $this->sortitemid) { + // same as last sort + if ($SESSION->gradeuserreport->sort == 'ASC') { + $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC'; + } else { + $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; + } + } else { + $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; + } + } + $SESSION->gradeuserreport->sortitemid = $this->sortitemid; + } else { + // not requesting sort, use last setting (for paging) + + if (isset($SESSION->gradeuserreport->sortitemid)) { + $this->sortitemid = $SESSION->gradeuserreport->sortitemid; + } + if (isset($SESSION->gradeuserreport->sort)) { + $this->sortorder = $SESSION->gradeuserreport->sort; + } else { + $this->sortorder = 'ASC'; + } + } + } + + /** + * Processes a single action against a category, grade_item or grade. + * @param string $target Sortorder + * @param string $action Which action to take (edit, delete etc...) + * @return + * TODO Update this, it's quite old and needs a major makeover + */ + function process_action($target, $action) { + $element = $this->gtree->locate_element($target); + + switch ($action) { + case 'edit': + break; + case 'delete': + if ($confirm == 1) { // Perform the deletion + //TODO: add proper delete support for grade items and categories + //$element['object']->delete(); + // Print result message + + } else { // Print confirmation dialog + $eid = $element['eid']; + $strdeletecheckfull = get_string('deletecheck', '', $element['object']->get_name()); + $linkyes = "category.php?target=$eid&action=delete&confirm=1$this->gtree->commonvars"; + $linkno = "category.php?$this->gtree->commonvars"; + notice_yesno($strdeletecheckfull, $linkyes, $linkno); + } + break; + + case 'hide': + // TODO Implement calendar for selection of a date to hide element until + $element['object']->set_hidden(1); + $this->gtree = new grade_tree($this->courseid); + break; + case 'show': + $element['object']->set_hidden(0); + $this->gtree = new grade_tree($this->courseid); + break; + case 'lock': + // TODO Implement calendar for selection of a date to lock element after + if (!$element['object']->set_locked(1)) { + debugging("Could not update the element's locked state!"); + } + $this->gtree = new grade_tree($this->courseid); + break; + case 'unlock': + if (!$element['object']->set_locked(0)) { + debugging("Could not update the element's locked state!"); + } + $this->gtree = new grade_tree($this->courseid); + break; + default: + break; + } + + } + + /** + * pulls out the userids of the users to be display, and sort them + * the right outer join is needed because potentially, it is possible not + * to have the corresponding entry in grade_grades table for some users + * this is check for user roles because there could be some users with grades + * but not supposed to be displayed + */ + function load_users() { + global $CFG; + + if (is_numeric($this->sortitemid)) { + $sql = "SELECT u.id, u.firstname, u.lastname + FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN + {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid) + LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid + $this->groupsql + WHERE ra.roleid in ($this->gradebookroles) + $this->groupwheresql + AND ra.contextid ".get_related_contexts_string($this->context)." + ORDER BY g.finalgrade $this->sortorder"; + $this->users = get_records_sql($sql, $this->studentsperpage * $this->page, $this->studentsperpage); + } else { + // default sort + // get users sorted by lastname + $this->users = get_role_users(@implode(',', $CFG->gradebookroles), $this->context, false, + 'u.id, u.firstname, u.lastname', 'u.'.$this->sortitemid .' '. $this->sortorder, + false, $this->page * $this->studentsperpage, $this->studentsperpage, $this->currentgroup); + // need to cut users down by groups + + } + + if (empty($this->users)) { + $this->userselect = ''; + $this->users = array(); + } else { + $this->userselect = 'AND g.userid in ('.implode(',', array_keys($this->users)).')'; + } + + return $this->users; + } + + /** + * Fetches and returns a count of all the users that will be shows on this page. + * @return int Count of users + */ + function get_numusers() { + global $CFG; + $countsql = "SELECT COUNT(DISTINCT u.id) + FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN + {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid) + LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid + $this->groupsql + WHERE ra.roleid in ($this->gradebookroles) + $this->groupwheresql + AND ra.contextid ".get_related_contexts_string($this->context); + return count_records_sql($countsql); + } + + /** + * we supply the userids in this query, and get all the grades + * pulls out all the grades, this does not need to worry about paging + */ + function load_final_grades() { + global $CFG; + + $sql = "SELECT g.id, g.itemid, g.userid, g.finalgrade, g.hidden, g.locked, g.locktime, g.overridden, gt.feedback + FROM {$CFG->prefix}grade_items gi, + {$CFG->prefix}grade_grades g + LEFT JOIN {$CFG->prefix}grade_grades_text gt ON g.id = gt.gradeid + WHERE g.itemid = gi.id + AND gi.courseid = $this->courseid $this->userselect"; + + if ($grades = get_records_sql($sql)) { + foreach ($grades as $grade) { + $this->finalgrades[$grade->userid][$grade->itemid] = $grade; + } + } + } + + /** + * Builds and returns a div with on/off toggles. + * @return string HTML code + */ + function get_toggles_html() { + global $USER; + $html = '
'; + if ($USER->gradeediting) { + $html .= $this->print_toggle('eyecons', true); + $html .= $this->print_toggle('locks', true); + $html .= $this->print_toggle('calculations', true); + } + + $html .= $this->print_toggle('grandtotals', true); + $html .= $this->print_toggle('groups', true); + $html .= $this->print_toggle('scales', true); + $html .= '
'; + return $html; + } + + /** + * Shortcut function for printing the grader report toggles. + * @param string $type The type of toggle + * @param bool $return Whether to return the HTML string rather than printing it + * @return void + */ + function print_toggle($type, $return=false) { + global $CFG; + + $icons = array('eyecons' => 'hide', + 'calculations' => 'calc', + 'locks' => 'lock', + 'grandtotals' => 'sigma'); + + $pref_name = 'grade_report_show' . $type; + $show_pref = get_user_preferences($pref_name, $CFG->$pref_name); + + $strshow = get_string('show' . $type, 'grades'); + $strhide = get_string('hide' . $type, 'grades'); + + $show_hide = 'show'; + $toggle_action = 1; + + if ($show_pref) { + $show_hide = 'hide'; + $toggle_action = 0; + } + + if (array_key_exists($type, $icons)) { + $image_name = $icons[$type]; + } else { + $image_name = $type; + } + + $string = ${'str' . $show_hide}; + + $img = ''
+                      .$string.''. "\n"; + + $retval = '
' . $img . '" + . $string . '
'; + + if ($return) { + return $retval; + } else { + echo $retval; + } + } + + /** + * Builds and returns the HTML code for the headers. + * @return string $headerhtml + */ + function get_headerhtml() { + global $CFG, $USER; + + $strsortasc = get_string('sortasc', 'grades'); + $strsortdesc = get_string('sortdesc', 'grades'); + if ($this->sortitemid === 'lastname') { + if ($this->sortorder == 'ASC') { + $lastarrow = print_arrow('up', $strsortasc, true); + } else { + $lastarrow = print_arrow('down', $strsortdesc, true); + } + } else { + $lastarrow = ''; + } + + if ($this->sortitemid === 'firstname') { + if ($this->sortorder == 'ASC') { + $firstarrow = print_arrow('up', $strsortasc, true); + } else { + $firstarrow = print_arrow('down', $strsortdesc, true); + } + } else { + $firstarrow = ''; + } + // Prepare Table Headers + $headerhtml = ''; + + $numrows = count($this->gtree->levels); + + foreach ($this->gtree->levels as $key=>$row) { + if ($key == 0) { + // do not diplay course grade category + // continue; + } + + $headerhtml .= ''; + + if ($key == $numrows - 1) { + $headerhtml .= 'Firstname ' //TODO: localize + . $firstarrow. '/ Lastname '. $lastarrow .''; + } else { + $headerhtml .= ' '; + } + + foreach ($row as $element) { + $eid = $element['eid']; + $object = $element['object']; + $type = $element['type']; + + if (!empty($element['colspan'])) { + $colspan = 'colspan="'.$element['colspan'].'"'; + } else { + $colspan = ''; + } + + if (!empty($element['depth'])) { + $catlevel = ' catlevel'.$element['depth']; + } else { + $catlevel = ''; + } + + + if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') { + $headerhtml .= ' '; + } else if ($type == 'category') { + $headerhtml .= ''.$element['object']->get_name(); + + // Print icons + if ($USER->gradeediting) { + $headerhtml .= $this->get_icons($element); + } + + $headerhtml .= ''; + } else { + if ($element['object']->id == $this->sortitemid) { + if ($this->sortorder == 'ASC') { + $arrow = print_arrow('up', $strsortasc, true); + } else { + $arrow = print_arrow('down', $strsortdesc, true); + } + } else { + $arrow = ''; + } + + $dimmed = ''; + if ($element['object']->is_hidden()) { + $dimmed = ' dimmed_text '; + } + + if ($object->itemtype == 'mod') { + $icon = ''
+                              .get_string('modulename', $object->itemmodule).''; + } else if ($object->itemtype == 'manual') { + //TODO: add manual grading icon + $icon = ''.get_string('manualgrade', 'grades')
+                              .''; + } + + + $headerhtml .= ''. $element['object']->get_name() + . '' . $arrow; + + $headerhtml .= $this->get_icons($element) . ''; + + $this->items[$element['object']->sortorder] =& $element['object']; + } + + } + + $headerhtml .= ''; + } + return $headerhtml; + } + + /** + * Builds and return the HTML rows of the table (grades headed by student). + * @return string HTML + */ + function get_studentshtml() { + global $CFG, $USER; + $studentshtml = ''; + $strfeedback = get_string("feedback"); + + foreach ($this->users as $userid => $user) { + // Student name and link + $studentshtml .= '' . fullname($user) . ''; + foreach ($this->items as $item) { + + if (isset($this->finalgrades[$userid][$item->id])) { + $gradeval = $this->finalgrades[$userid][$item->id]->finalgrade; + $grade = new grade_grades($this->finalgrades[$userid][$item->id], false); + $grade->feedback = $this->finalgrades[$userid][$item->id]->feedback; + + } else { + $gradeval = null; + $grade = new grade_grades(array('userid' => $userid, 'itemid' => $item->id), false); + $grade->feedback = ''; + } + + if ($grade->is_overridden()) { + $studentshtml .= ''; + } else { + $studentshtml .= ''; + } + + // emulate grade element + $grade->courseid = $this->courseid; + $grade->grade_item = $item; // this may speedup is_hidden() and other grade_grades methods + $element = array ('eid'=>'g'.$grade->id, 'object'=>$grade, 'type'=>'grade'); + + // Do not show any icons if no grade (no record in DB to match) + if (!empty($grade->id)) { + $studentshtml .= $this->get_icons($element); + } + + // if in editting 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 ($USER->gradeediting) { + // We need to retrieve each grade_grade object from DB in order to + // know if they are hidden/locked + + if ($item->scaleid) { + if ($scale = get_record('scale', 'id', $item->scaleid)) { + $scales = explode(",", $scale->scale); + // reindex because scale is off 1 + $i = 0; + foreach ($scales as $scaleoption) { + $i++; + $scaleopt[$i] = $scaleoption; + } + + if ($this->quickgrading) { + $studentshtml .= choose_from_menu($scaleopt, 'grade_'.$userid.'_'.$item->id, + $gradeval, get_string('nograde'), '', -1, true); + } elseif ($scale = get_record('scale', 'id', $item->scaleid)) { + $scales = explode(",", $scale->scale); + + // invalid grade if gradeval < 1 + if ((int) $gradeval < 1) { + $studentshtml .= '-'; + } else { + $studentshtml .= $scales[$gradeval-1]; + } + } else { + // no such scale, throw error? + } + } + } else { + if ($this->quickgrading) { + $studentshtml .= ''; + } else { + $studentshtml .= get_grade_clean($gradeval); + } + } + + + // If quickfeedback is on, print an input element + if ($this->quickfeedback) { + if ($this->quickgrading) { + $studentshtml .= '
'; + } + $studentshtml .= ''; + } + + $studentshtml .= '
' . $this->get_icons($element, array('edit')) . '
'; + } else { + // If feedback present, surround grade with feedback tooltip + if (!empty($grade->feedback)) { + $studentshtml .= ''; + } + + // finalgrades[$userid][$itemid] could be null because of the outer join + // in this case it's different than a 0 + if ($item->scaleid) { + if ($scale = get_record('scale', 'id', $item->scaleid)) { + $scales = explode(",", $scale->scale); + + // invalid grade if gradeval < 1 + if ((int) $gradeval < 1) { + $studentshtml .= '-'; + } else { + $studentshtml .= $scales[$gradeval-1]; + } + } else { + // no such scale, throw error? + } + } else { + if (is_null($gradeval)) { + $studentshtml .= '-'; + } else { + $studentshtml .= get_grade_clean($gradeval); + } + } + if (!empty($grade->feedback)) { + $studentshtml .= ''; + } + } + + if (!empty($this->gradeserror[$item->id][$userid])) { + $studentshtml .= $this->gradeserror[$item->id][$userid]; + } + + $studentshtml .= '' . "\n"; + } + $studentshtml .= ''; + } + return $studentshtml; + } + + /** + * Builds and return the HTML rows of the table (grades headed by student). + * @return string HTML + */ + function get_groupsumhtml() { + global $CFG; + + $groupsumhtml = ''; + + if ($this->currentgroup && $this->showgroups) { + + /** SQL for finding group sum */ + $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum + FROM {$CFG->prefix}grade_items gi LEFT JOIN + {$CFG->prefix}grade_grades g ON gi.id = g.itemid RIGHT OUTER JOIN + {$CFG->prefix}user u ON u.id = g.userid LEFT JOIN + {$CFG->prefix}role_assignments ra ON u.id = ra.userid + $this->groupsql + WHERE gi.courseid = $this->courseid + $this->groupwheresql + AND ra.roleid in ($this->gradebookroles) + AND ra.contextid ".get_related_contexts_string($this->context)." + GROUP BY g.itemid"; + + $groupsum = array(); + $sums = get_records_sql($SQL); + foreach ($sums as $itemid => $csum) { + $groupsum[$itemid] = $csum; + } + + $groupsumhtml = 'Group total'; + foreach ($this->items as $item) { + if (!isset($groupsum[$item->id])) { + $groupsumhtml .= '-'; + } else { + $sum = $groupsum[$item->id]; + $groupsumhtml .= ''.get_grade_clean($sum->sum).''; + } + } + $groupsumhtml .= ''; + } + return $groupsumhtml; + } + + function get_gradesumhtml() { + global $CFG; + + $gradesumhtml = ''; + if ($this->showgrandtotals) { + + /** SQL for finding the SUM grades of all visible users ($CFG->gradebookroles) */ + + $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum + FROM {$CFG->prefix}grade_items gi LEFT JOIN + {$CFG->prefix}grade_grades g ON gi.id = g.itemid RIGHT OUTER JOIN + {$CFG->prefix}user u ON u.id = g.userid LEFT JOIN + {$CFG->prefix}role_assignments ra ON u.id = ra.userid + WHERE gi.courseid = $this->courseid + AND ra.roleid in ($this->gradebookroles) + AND ra.contextid ".get_related_contexts_string($this->context)." + GROUP BY g.itemid"; + + $classsum = array(); + $sums = get_records_sql($SQL); + foreach ($sums as $itemid => $csum) { + $classsum[$itemid] = $csum; + } + + $gradesumhtml = 'Total'; + foreach ($this->items as $item) { + if (!isset($classsum[$item->id])) { + $gradesumhtml .= '-'; + } else { + $sum = $classsum[$item->id]; + $gradesumhtml .= ''.get_grade_clean($sum->sum).''; + } + } + $gradesumhtml .= ''; + } + return $gradesumhtml; + } + + function get_scalehtml() { + $scalehtml = ''; + if ($this->showscales) { + $scalehtml = ''.get_string('range','grades').''; + foreach ($this->items as $item) { + $scalehtml .= ''. get_grade_clean($item->grademin).'-'. get_grade_clean($item->grademax).''; + } + $scalehtml .= ''; + } + return $scalehtml; + } + + /** + * Given a grade_category, grade_item or grade_grade, this function + * figures out the state of the object and builds then returns a div + * with the icons needed for the grader report. + * + * @param object $object + * @param array $icons An array of icon names that this function is explicitly requested to print, regardless of settings + * @param bool $limit If true, use the $icons array as the only icons that will be printed. If false, use it to exclude these icons. + * @return string HTML + */ + function get_icons($element, $icons=null, $limit=true) { + global $CFG; + global $USER; + + // Load language strings + $stredit = get_string("edit"); + $streditcalculation= get_string("editcalculation", 'grades'); + $strfeedback = get_string("feedback"); + $strmove = get_string("move"); + $strmoveup = get_string("moveup"); + $strmovedown = get_string("movedown"); + $strmovehere = get_string("movehere"); + $strcancel = get_string("cancel"); + $stredit = get_string("edit"); + $strdelete = get_string("delete"); + $strhide = get_string("hide"); + $strshow = get_string("show"); + $strlock = get_string("lock", 'grades'); + $strswitch_minus = get_string("contract", 'grades'); + $strswitch_plus = get_string("expand", 'grades'); + $strunlock = get_string("unlock", 'grades'); + + // Prepare container div + $html = '
'; + + // Prepare reference variables + $eid = $element['eid']; + $object = $element['object']; + $type = $element['type']; + + // Add mock attributes in case the object is not of the right type + if ($type != 'grade') { + $object->feedback = ''; + } + + // Load user preferences + $aggregationview = get_user_preferences('grade_report_aggregationview', $CFG->grade_report_aggregationview); + $showeyecons = get_user_preferences('grade_report_showeyecons', $CFG->grade_report_showeyecons); + $showlocks = get_user_preferences('grade_report_showlocks', $CFG->grade_report_showlocks); + $showcalculations = get_user_preferences('grade_report_showcalculations', $CFG->grade_report_showcalculations); + + // Prepare image strings + $edit_category_icon = '' + . ''
+                            . $stredit.''. "\n"; + + $edit_item_icon = '' + . ''
+                        . $stredit.''. "\n"; + $overlib = ''; + if (!empty($object->feedback)) { + $overlib = 'onmouseover="return overlib(\''.$object->feedback.'\', CAPTION, \'' + . $strfeedback.'\');" onmouseout="return nd();"'; + } + + $edit_grade_icon = '' + . '' . $stredit.''. "\n"; + + + $edit_calculation_icon = '' + . ''
+                               . $streditcalculation.''. "\n"; + + // Prepare Hide/Show icon state + $hide_show = 'hide'; + if ($object->is_hidden()) { + $hide_show = 'show'; + } + + $show_hide_icon = 'gtree->commonvars . "\">\n" + . ''
+                        . ${'str' . $hide_show}.''. "\n"; + + // Prepare lock/unlock string + $lock_unlock = 'lock'; + if ($object->is_locked()) { + $lock_unlock = 'unlock'; + } + + // Print lock/unlock icon + + $lock_unlock_icon = 'gtree->commonvars . "\">\n" + . ''
+                          . ${'str' . $lock_unlock}.''. "\n"; + + // Prepare expand/contract string + $expand_contract = 'switch_minus'; // Default: expanded + $state = get_user_preferences('grade_category_' . $object->id, GRADE_CATEGORY_EXPANDED); + if ($state == GRADE_CATEGORY_CONTRACTED) { + $expand_contract = 'switch_plus'; + } + + $contract_expand_icon = 'gtree->commonvars . "\">\n" + . ''
+                              . ${'str' . $expand_contract}.''. "\n"; + + // If an array of icon names is given, return only these in the order they are given + if (!empty($icons) && is_array($icons)) { + $new_html = ''; + + foreach ($icons as $icon_name) { + if ($icon_name == 'edit') { + $icon_name .= "_$type"; + } + if ($limit) { + $new_html .= ${$icon_name . '_icon'}; + } else { + ${'show_' . $icon_name} = false; + } + } + if ($limit) { + return $new_html; + } else { + $html .= $new_html; + } + } + + // Icons shown when edit mode is on + if ($USER->gradeediting) { + // Edit icon (except for grade_grades) + if ($type == 'category') { + $html .= $edit_category_icon; + + } else if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') { + $html .= $edit_item_icon; + } + + // Calculation icon for items and categories + if ($showcalculations && $type != 'grade') { + $html .= $edit_calculation_icon; + } + + if ($showeyecons) { + $html .= $show_hide_icon; + } + + if ($showlocks) { + $html .= $lock_unlock_icon; + } + + // If object is a category, display expand/contract icon + if (get_class($object) == 'grade_category' && $aggregationview == GRADER_REPORT_AGGREGATION_VIEW_COMPACT) { + $html .= $contract_expand_icon; + } + } else { // Editing mode is off + } + + return $html . '
'; + } +} +?> diff --git a/grade/report/grader/index.php b/grade/report/grader/index.php index 295405d11d6..cc773afd500 100644 --- a/grade/report/grader/index.php +++ b/grade/report/grader/index.php @@ -2,431 +2,53 @@ /// This creates and handles the whole grader report interface, sans header and footer -require_once($CFG->libdir.'/tablelib.php'); -require_once($CFG->libdir.'/gradelib.php'); -require_once($CFG->dirroot.'/grade/report/lib.php'); +require_once($CFG->dirroot.'/grade/report/grader/grader_report.php'); $gradeserror = array(); -/** -* Shortcut function for printing the grader report toggles. -* @param string $type The type of toggle -* @param string $baseurl The base of the URL the toggles will link to -* @param bool $return Whether to return the HTML string rather than printing it -* @return void -*/ -function grader_report_print_toggle($type, $baseurl, $return=false) { - global $CFG; - - $icons = array('eyecons' => 'hide', - 'calculations' => 'calc', - 'locks' => 'lock', - 'grandtotals' => 'sigma'); - - $pref_name = 'grade_report_show' . $type; - $show_pref = get_user_preferences($pref_name, $CFG->$pref_name); - - $strshow = get_string('show' . $type, 'grades'); - $strhide = get_string('hide' . $type, 'grades'); - - $show_hide = 'show'; - $toggle_action = 1; - - if ($show_pref) { - $show_hide = 'hide'; - $toggle_action = 0; - } - - if (array_key_exists($type, $icons)) { - $image_name = $icons[$type]; - } else { - $image_name = $type; - } - - $string = ${'str' . $show_hide}; - - $img = ''
-                  .$string.''. "\n"; - - $retval = '
' . $img . '" - . $string . '
'; - - if ($return) { - return $retval; - } else { - echo $retval; - } -} - - -/// processing posted grades & feedback here - -if ($data = data_submitted() and confirm_sesskey()) { - - // always initialize all arrays - $queue = array(); - - foreach ($data as $varname => $postedvalue) { - // this is a bit tricky - we have to first load all grades into memory, - // check if changed and only then start updating the final grades because - // columns might depend one on another - the result would be overriden calculated and category grades - - $needsupdate = false; - $note = false; // TODO implement note?? - - // skip, not a grade nor feedback - $data_type = ''; - if (strstr($varname, 'grade')) { - $data_type = 'grade'; - } elseif (strstr($varname, 'feedback')) { - $data_type = 'feedback'; - } else { - continue; - } - - $gradeinfo = explode("_", $varname); - - $userid = clean_param($gradeinfo[1], PARAM_INT); - $itemid = clean_param($gradeinfo[2], PARAM_INT); - - if (!$grade_item = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$course->id))) { // we must verify course id here! - error('Incorrect grade item id'); - } - - // Pre-process grade - if ($data_type == 'grade') { - - if ($grade_item->gradetype == GRADE_TYPE_SCALE) { - if ($postedvalue == -1) { // -1 means no grade - $finalgrade = null; - } else { - $finalgrade = (float)$postedvalue; - } - } else { - if ($postedvalue == '') { // empty string means no grade - $finalgrade = null; - } else { - $finalgrade = format_grade($postedvalue); - } - } - - if (!is_null($finalgrade) and ($finalgrade < $grade_item->grademin or $finalgrade > $grade_item->grademax)) { - $gradeserror[$grade_item->id][$userid] = 'outofrange'; //TODO: localize - // another possiblity is to use bounded number instead - continue; - } - } - - // Get the grade object to compare old value with new value - if ($grade = grade_grades::fetch(array('userid'=>$userid, 'itemid'=>$grade_item->id))) { - if ($data_type == 'feedback') { - $finalgrade = false; - $text = $grade->load_text(); - if ($text != s($postedvalue)) { - $feedback = s($postedvalue); - $feedbackformat = GRADER_REPORT_FEEDBACK_FORMAT_TEXT; - $needsupdate = true; - } - } elseif ($data_type == 'grade') { - $feedback = false; - $feedbackformat = false; - if (!is_null($grade->finalgrade)) { - $grade->finalgrade = (float)$grade->finalgrade; - } - if ($grade->finalgrade === $finalgrade) { - $needsupdate = true; - } - } - - } - - // we must not update all grades, only changed ones - we do not want to mark everything as overriden - if ($needsupdate) { - $gradedata = new object(); - $gradedata->grade_item = $grade_item; - $gradedata->userid = $userid; - $gradedata->note = $note; - $gradedata->finalgrade = $finalgrade; - $gradedata->feedback = $feedback; - $gradedata->feedbackformat = $feedbackformat; - - $queue[] = $gradedata; - } - } - - // now we update the new final grade for each changed grade - foreach ($queue as $gradedata) { - $gradedata->grade_item->update_final_grade($gradedata->userid, $gradedata->finalgrade, 'gradebook', - $gradedata->note, $gradedata->feedback, $gradedata->feedbackformat); - } -} - -// get the params -$courseid = required_param('id', PARAM_INT); -$context = get_context_instance(CONTEXT_COURSE, $courseid); +// get the params ($report, $courseid and $context are already set in grade/report.php) $page = optional_param('page', 0, PARAM_INT); $sortitemid = optional_param('sortitemid', 0, PARAM_ALPHANUM); // sort by which grade item -$report = optional_param('report', 0, PARAM_ALPHANUM); $action = optional_param('action', 0, PARAM_ALPHA); $move = optional_param('move', 0, PARAM_INT); $type = optional_param('type', 0, PARAM_ALPHA); $target = optional_param('target', 0, PARAM_ALPHANUM); $toggle = optional_param('toggle', NULL, PARAM_INT); $toggle_type = optional_param('toggle_type', 0, PARAM_ALPHANUM); - +$db->debug=true; // Handle toggle change request -// TODO print visual feedback if (!is_null($toggle) && !empty($toggle_type)) { set_user_preferences(array('grade_report_show' . $toggle_type => $toggle)); } -// Get the user preferences -$perpage = get_user_preferences('grade_report_studentsperpage', $CFG->grade_report_studentsperpage); // number of users on a page -$decimals = get_user_preferences('grade_report_decimalpoints', $CFG->grade_report_decimalpoints); // decimals in grades -$showgrandtotals = get_user_preferences('grade_report_showgrandtotals', $CFG->grade_report_showgrandtotals); -$showgroups = get_user_preferences('grade_report_showgroups', $CFG->grade_report_showgroups); -$aggregation_position = get_user_preferences('grade_report_aggregationposition', $CFG->grade_report_aggregationposition); -$showscales = get_user_preferences('grade_report_showscales', $CFG->grade_report_showscales); -$quickgrading = get_user_preferences('grade_report_quickgrading', $CFG->grade_report_quickgrading); -$quickfeedback = get_user_preferences('grade_report_quickfeedback', $CFG->grade_report_quickfeedback); +// Initialise the grader report object +$report = new grade_report_grader($courseid, $context, $page, $sortitemid); + +/// processing posted grades & feedback here +if ($data = data_submitted() and confirm_sesskey()) { + $report->process_data($data); +} // Override perpage if set in URL if ($perpageurl = optional_param('perpage', 0, PARAM_INT)) { - $perpage = $perpageurl; + $report->studentsperpage = $perpageurl; } -// Prepare language strings -$strsortasc = get_string('sortasc', 'grades'); -$strsortdesc = get_string('sortdesc', 'grades'); -$strfeedback = get_string("feedback"); - -// base url for sorting by first/last name -$baseurl = 'report.php?id='.$courseid.'&perpage='.$perpage.'&report=grader&page='.$page; -// base url for paging -$pbarurl = 'report.php?id='.$courseid.'&perpage='.$perpage.'&report=grader&'; - -/// setting up groups -$groupsql = ''; -$groupwheresql = ''; -$group_selector = null; -$currentgroup = null; - -if ($showgroups) { - /// find out current groups mode - $course = get_record('course', 'id', $courseid); - $groupmode = $course->groupmode; - ob_start(); - $currentgroup = setup_and_print_groups($course, $groupmode, $baseurl); - $group_selector = ob_get_clean(); - - // update paging after group - $baseurl .= 'group='.$currentgroup.'&'; - $pbarurl .= 'group='.$currentgroup.'&'; - - if ($currentgroup) { - $groupsql = " LEFT JOIN {$CFG->prefix}groups_members gm ON gm.userid = u.id "; - $groupwheresql = " AND gm.groupid = $currentgroup "; - } -} - -// Grab the grade_tree for this course -$gtree = new grade_tree($courseid, true, false, $aggregation_position); - -// setting the sort order, this depends on last state -// all this should be in the new table class that we might need to use -// for displaying grades - -// already in not requesting sort, i.e. normal paging - -if ($sortitemid) { - if (!isset($SESSION->gradeuserreport->sort)) { - $sortorder = $SESSION->gradeuserreport->sort = 'ASC'; - } else { - // this is the first sort, i.e. by last name - if (!isset($SESSION->gradeuserreport->sortitemid)) { - $sortorder = $SESSION->gradeuserreport->sort = 'ASC'; - } else if ($SESSION->gradeuserreport->sortitemid == $sortitemid) { - // same as last sort - if ($SESSION->gradeuserreport->sort == 'ASC') { - $sortorder = $SESSION->gradeuserreport->sort = 'DESC'; - } else { - $sortorder = $SESSION->gradeuserreport->sort = 'ASC'; - } - } else { - $sortorder = $SESSION->gradeuserreport->sort = 'ASC'; - } - } - $SESSION->gradeuserreport->sortitemid = $sortitemid; -} else { - // not requesting sort, use last setting (for paging) - - if (isset($SESSION->gradeuserreport->sortitemid)) { - $sortitemid = $SESSION->gradeuserreport->sortitemid; - } - if (isset($SESSION->gradeuserreport->sort)) { - $sortorder = $SESSION->gradeuserreport->sort; - } else { - $sortorder = 'ASC'; - } -} - -/// end of setting sort order code - // Perform actions on categories, items and grades if (!empty($target) && !empty($action) && confirm_sesskey()) { - - $element = $gtree->locate_element($target); - - switch ($action) { - case 'edit': - break; - case 'delete': - if ($confirm == 1) { // Perform the deletion - //TODO: add proper delete support for grade items and categories - //$element['object']->delete(); - // Print result message - - } else { // Print confirmation dialog - $eid = $element['eid']; - $strdeletecheckfull = get_string('deletecheck', '', $element['object']->get_name()); - $linkyes = "category.php?target=$eid&action=delete&confirm=1$gtree->commonvars"; - $linkno = "category.php?$gtree->commonvars"; - notice_yesno($strdeletecheckfull, $linkyes, $linkno); - } - break; - - case 'hide': - // TODO Implement calendar for selection of a date to hide element until - $element['object']->set_hidden(1); - $gtree = new grade_tree($courseid); - break; - case 'show': - $element['object']->set_hidden(0); - $gtree = new grade_tree($courseid); - break; - case 'lock': - // TODO Implement calendar for selection of a date to lock element after - if (!$element['object']->set_locked(1)) { - debugging("Could not update the element's locked state!"); - } - $gtree = new grade_tree($courseid); - break; - case 'unlock': - if (!$element['object']->set_locked(0)) { - debugging("Could not update the element's locked state!"); - } - $gtree = new grade_tree($courseid); - break; - default: - break; - } + $report->process_action($target, $action); } // first make sure we have all final grades // TODO: check that no grade_item has needsupdate set grade_regrade_final_grades($courseid); -// roles to be displaye in the gradebook -$gradebookroles = $CFG->gradebookroles; +$report->load_users(); +$numusers = $report->get_numusers(); +$report->load_final_grades(); -/* -* pulls out the userids of the users to be display, and sort them -* the right outer join is needed because potentially, it is possible not -* to have the corresponding entry in grade_grades table for some users -* this is check for user roles because there could be some users with grades -* but not supposed to be displayed -*/ -if (is_numeric($sortitemid)) { - $sql = "SELECT u.id, u.firstname, u.lastname - FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN - {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $sortitemid) - LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid - $groupsql - WHERE ra.roleid in ($gradebookroles) - $groupwheresql - AND ra.contextid ".get_related_contexts_string($context)." - ORDER BY g.finalgrade $sortorder"; - $users = get_records_sql($sql, $perpage * $page, $perpage); -} else { - // default sort - // get users sorted by lastname - $users = get_role_users(@implode(',', $CFG->gradebookroles), $context, false, 'u.id, u.firstname, u.lastname', 'u.'.$sortitemid .' '. $sortorder, false, $page * $perpage, $perpage, $currentgroup); - // need to cut users down by groups - -} - -/// count total records for paging - -$countsql = "SELECT COUNT(DISTINCT u.id) - FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN - {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $sortitemid) - LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid - $groupsql - WHERE ra.roleid in ($gradebookroles) - $groupwheresql - AND ra.contextid ".get_related_contexts_string($context); -$numusers = count_records_sql($countsql); - -// print_object($users); // debug - -if (empty($users)) { - $userselect = ''; - $users = array(); -} else { - $userselect = 'AND g.userid in ('.implode(',', array_keys($users)).')'; -} - - -// phase 2 sql, we supply the userids in this query, and get all the grades -// pulls out all the grades, this does not need to worry about paging -$sql = "SELECT g.id, g.itemid, g.userid, g.finalgrade, g.hidden, g.locked, g.locktime, g.overridden, gt.feedback - FROM {$CFG->prefix}grade_items gi, - {$CFG->prefix}grade_grades g - LEFT JOIN {$CFG->prefix}grade_grades_text gt ON g.id = gt.gradeid - WHERE g.itemid = gi.id - AND gi.courseid = $courseid $userselect"; - -///print_object($grades); //debug - -$finalgrades = array(); -// needs to be formatted into an array for easy retrival - -if ($grades = get_records_sql($sql)) { - foreach ($grades as $grade) { - $finalgrades[$grade->userid][$grade->itemid] = $grade; - } -} - -/// With the users in an sorted array and grades fetched, we can not print the main html table - -// 1. Fetch all top-level categories for this course, with all children preloaded, sorted by sortorder - - // Fetch array of students enroled in this course -if (!$context = get_context_instance(CONTEXT_COURSE, $gtree->courseid)) { +if (!$context = get_context_instance(CONTEXT_COURSE, $report->gtree->courseid)) { return false; } -//$users = get_role_users(@implode(',', $CFG->gradebookroles), $context); - -if ($sortitemid === 'lastname') { - if ($sortorder == 'ASC') { - $lastarrow = print_arrow('up', $strsortasc, true); - } else { - $lastarrow = print_arrow('down', $strsortdesc, true); - } -} else { - $lastarrow = ''; -} - -if ($sortitemid === 'firstname') { - if ($sortorder == 'ASC') { - $firstarrow = print_arrow('up', $strsortasc, true); - } else { - $firstarrow = print_arrow('down', $strsortdesc, true); - } -} else { - $firstarrow = ''; -} - -/********* BEGIN OUTPUT *********/ print_heading('Grader Report'); @@ -434,333 +56,18 @@ print_heading('Grader Report'); $currenttab = 'graderreport'; include('tabs.php'); -// Group selection drop-down -echo $group_selector; +echo $report->group_selector; +echo $report->get_toggles_html(); +print_paging_bar($numusers, $report->page, $report->studentsperpage, $report->pbarurl); +echo '
'; -// Show/hide toggles -echo '
'; -if ($USER->gradeediting) { - grader_report_print_toggle('eyecons', $baseurl); - grader_report_print_toggle('locks', $baseurl); - grader_report_print_toggle('calculations', $baseurl); -} - -grader_report_print_toggle('grandtotals', $baseurl); -grader_report_print_toggle('groups', $baseurl); -grader_report_print_toggle('scales', $baseurl); -echo '
'; - -// Paging bar -print_paging_bar($numusers, $page, $perpage, $pbarurl); - -$items = array(); - -// Prepare Table Headers -$headerhtml = ''; - -$numrows = count($gtree->levels); - -foreach ($gtree->levels as $key=>$row) { - if ($key == 0) { - // do not diplay course grade category - // continue; - } - - $headerhtml .= ''; - - if ($key == $numrows - 1) { - $headerhtml .= 'Firstname ' //TODO: localize - . $firstarrow. '/ Lastname '. $lastarrow .''; - } else { - $headerhtml .= ' '; - } - - foreach ($row as $element) { - $eid = $element['eid']; - $object = $element['object']; - $type = $element['type']; - - if (!empty($element['colspan'])) { - $colspan = 'colspan="'.$element['colspan'].'"'; - } else { - $colspan = ''; - } - - if (!empty($element['depth'])) { - $catlevel = ' catlevel'.$element['depth']; - } else { - $catlevel = ''; - } - - - if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') { - $headerhtml .= ' '; - } else if ($type == 'category') { - $headerhtml .= ''.$element['object']->get_name(); - - // Print icons - if ($USER->gradeediting) { - $headerhtml .= grade_get_icons($element, $gtree); - } - - $headerhtml .= ''; - } else { - if ($element['object']->id == $sortitemid) { - if ($sortorder == 'ASC') { - $arrow = print_arrow('up', $strsortasc, true); - } else { - $arrow = print_arrow('down', $strsortdesc, true); - } - } else { - $arrow = ''; - } - - $dimmed = ''; - if ($element['object']->is_hidden()) { - $dimmed = ' dimmed_text '; - } - - if ($object->itemtype == 'mod') { - $icon = ''
-                      .get_string('modulename', $object->itemmodule).''; - } else if ($object->itemtype == 'manual') { - //TODO: add manual grading icon - $icon = ''.get_string('manualgrade', 'grades')
-                      .''; - } - - - $headerhtml .= ''. $element['object']->get_name() - . '' . $arrow; - - $headerhtml .= grade_get_icons($element, $gtree) . ''; - - $items[$element['object']->sortorder] =& $element['object']; - } - - } - - $headerhtml .= ''; -} - -// Prepare Table Rows -$studentshtml = ''; - -foreach ($users as $userid => $user) { - // Student name and link - $studentshtml .= '' . fullname($user) . ''; - foreach ($items as $item) { - - if (isset($finalgrades[$userid][$item->id])) { - $gradeval = $finalgrades[$userid][$item->id]->finalgrade; - $grade = new grade_grades($finalgrades[$userid][$item->id], false); - $grade->feedback = $finalgrades[$userid][$item->id]->feedback; - - } else { - $gradeval = null; - $grade = new grade_grades(array('userid' => $userid, 'itemid' => $item->id), false); - $grade->feedback = ''; - } - - if ($grade->is_overridden()) { - $studentshtml .= ''; - } else { - $studentshtml .= ''; - } - - // Do not show any icons if no grade (no record in DB to match) - if (!empty($grade->id)) { - // emulate grade element - $grade->courseid = $course->id; - $grade->grade_item = $item; // this may speedup is_hidden() and other grade_grades methods - $element = array ('eid'=>'g'.$grade->id, 'object'=>$grade, 'type'=>'grade'); - $studentshtml .= grade_get_icons($element, $gtree); - } - - - // if in editting 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 ($USER->gradeediting) { - // We need to retrieve each grade_grade object from DB in order to - // know if they are hidden/locked - - if ($item->scaleid) { - if ($scale = get_record('scale', 'id', $item->scaleid)) { - $scales = explode(",", $scale->scale); - // reindex because scale is off 1 - $i = 0; - foreach ($scales as $scaleoption) { - $i++; - $scaleopt[$i] = $scaleoption; - } - - if ($quickgrading) { - $studentshtml .= choose_from_menu($scaleopt, 'grade_'.$userid.'_'.$item->id, - $gradeval, get_string('nograde'), '', -1, true); - } elseif ($scale = get_record('scale', 'id', $item->scaleid)) { - $scales = explode(",", $scale->scale); - - // invalid grade if gradeval < 1 - if ((int) $gradeval < 1) { - $studentshtml .= '-'; - } else { - $studentshtml .= $scales[$gradeval-1]; - } - } else { - // no such scale, throw error? - } - } - } else { - if ($quickgrading) { - $studentshtml .= ''; - } else { - $studentshtml .= get_grade_clean($gradeval); - } - } - - - // If quickfeedback is on, print an input element - if ($quickfeedback) { - $studentshtml .= ''; - } - - $studentshtml .= '
' . grade_get_icons($element, $gtree, array('edit')) . '
'; - } else { - // If feedback present, surround grade with feedback tooltip - if (!empty($grade->feedback)) { - $studentshtml .= ''; - } - - // finalgrades[$userid][$itemid] could be null because of the outer join - // in this case it's different than a 0 - if ($item->scaleid) { - if ($scale = get_record('scale', 'id', $item->scaleid)) { - $scales = explode(",", $scale->scale); - - // invalid grade if gradeval < 1 - if ((int) $gradeval < 1) { - $studentshtml .= '-'; - } else { - $studentshtml .= $scales[$gradeval-1]; - } - } else { - // no such scale, throw error? - } - } else { - if (is_null($gradeval)) { - $studentshtml .= '-'; - } else { - $studentshtml .= get_grade_clean($gradeval); - } - } - if (!empty($grade->feedback)) { - $studentshtml .= ''; - } - } - - if (!empty($gradeserror[$item->id][$userid])) { - $studentshtml .= $gradeserror[$item->id][$userid]; - } - - $studentshtml .= '' . "\n"; - } - $studentshtml .= ''; -} - -// if user preference to display group sum -$groupsumhtml = ''; - -if ($currentgroup && $showgroups) { - -/** SQL for finding group sum */ - $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum - FROM {$CFG->prefix}grade_items gi LEFT JOIN - {$CFG->prefix}grade_grades g ON gi.id = g.itemid RIGHT OUTER JOIN - {$CFG->prefix}user u ON u.id = g.userid LEFT JOIN - {$CFG->prefix}role_assignments ra ON u.id = ra.userid - $groupsql - WHERE gi.courseid = $courseid - $groupwheresql - AND ra.roleid in ($gradebookroles) - AND ra.contextid ".get_related_contexts_string($context)." - GROUP BY g.itemid"; - - $groupsum = array(); - $sums = get_records_sql($SQL); - foreach ($sums as $itemid => $csum) { - $groupsum[$itemid] = $csum; - } - - $groupsumhtml = 'Group total'; - foreach ($items as $item) { - if (!isset($groupsum[$item->id])) { - $groupsumhtml .= '-'; - } else { - $sum = $groupsum[$item->id]; - $groupsumhtml .= ''.get_grade_clean($sum->sum).''; - } - } - $groupsumhtml .= ''; -} - -// Grand totals -$gradesumhtml = ''; -if ($showgrandtotals) { - -/** SQL for finding the SUM grades of all visible users ($CFG->gradebookroles) */ - - $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum - FROM {$CFG->prefix}grade_items gi LEFT JOIN - {$CFG->prefix}grade_grades g ON gi.id = g.itemid RIGHT OUTER JOIN - {$CFG->prefix}user u ON u.id = g.userid LEFT JOIN - {$CFG->prefix}role_assignments ra ON u.id = ra.userid - WHERE gi.courseid = $courseid - AND ra.roleid in ($gradebookroles) - AND ra.contextid ".get_related_contexts_string($context)." - GROUP BY g.itemid"; - - $classsum = array(); - $sums = get_records_sql($SQL); - foreach ($sums as $itemid => $csum) { - $classsum[$itemid] = $csum; - } - - $gradesumhtml = 'Total'; - foreach ($items as $item) { - if (!isset($classsum[$item->id])) { - $gradesumhtml .= '-'; - } else { - $sum = $classsum[$item->id]; - $gradesumhtml .= ''.get_grade_clean($sum->sum).''; - } - } - $gradesumhtml .= ''; -} - -// finding the ranges of each gradeitem -$scalehtml = ''; -if ($showscales) { - $scalehtml = ''.get_string('range','grades').''; - foreach ($items as $item) { - $scalehtml .= ''. get_grade_clean($item->grademin).'-'. get_grade_clean($item->grademax).''; - } - $scalehtml .= ''; -} - -echo "
"; - -$reporthtml = "$headerhtml"; -$reporthtml .= $scalehtml; -$reporthtml .= $studentshtml; -$reporthtml .= $groupsumhtml; -$reporthtml .= $gradesumhtml; +$reporthtml = '
'; +$reporthtml .= $report->get_headerhtml(); +$reporthtml .= $report->get_scalehtml(); +$reporthtml .= $report->get_studentshtml(); +$reporthtml .= $report->get_groupsumhtml(); +$reporthtml .= $report->get_gradesumhtml(); $reporthtml .= "
"; - // print submit button if ($USER->gradeediting) { echo '
'; @@ -773,13 +80,13 @@ if ($USER->gradeediting) { echo $reporthtml; // print submit button -if ($USER->gradeediting && ($quickfeedback || $quickgrading)) { +if ($USER->gradeediting && ($report->quickfeedback || $report->quickgrading)) { echo '
'; echo '
'; } // prints paging bar at bottom for large pages -if ($perpage >= 20) { - print_paging_bar($numusers, $page, $perpage, $pbarurl); +if ($report->studentsperpage >= 20) { + print_paging_bar($numusers, $report->page, $report->studentsperpage, $report->pbarurl); } ?> diff --git a/lib/gradelib.php b/lib/gradelib.php index 7a2b18a0bef..da703aa0533 100644 --- a/lib/gradelib.php +++ b/lib/gradelib.php @@ -64,16 +64,6 @@ define('GRADE_HISTORY_INSERT', 1); define('GRADE_HISTORY_UPDATE', 2); define('GRADE_HISTORY_DELETE', 3); -// Set up constants for report preferences -define('GRADER_REPORT_AGGREGATION_POSITION_LEFT', 0); -define('GRADER_REPORT_AGGREGATION_POSITION_RIGHT', 1); -define('GRADER_REPORT_AGGREGATION_VIEW_FULL', 0); -define('GRADER_REPORT_AGGREGATION_VIEW_COMPACT', 1); -define('GRADER_REPORT_GRADE_DISPLAY_TYPE_RAW', 0); -define('GRADER_REPORT_GRADE_DISPLAY_TYPE_PERCENTAGE', 1); -define('GRADER_REPORT_FEEDBACK_FORMAT_TEXT', 0); -define('GRADER_REPORT_FEEDBACK_FORMAT_HTML', 1); - require_once($CFG->libdir . '/grade/grade_category.php'); require_once($CFG->libdir . '/grade/grade_item.php'); @@ -704,170 +694,4 @@ function grade_oldgradebook_upgrade($courseid) { } } -/** - * Given a grade_category, grade_item or grade_grade, this function - * figures out the state of the object and builds then returns a div - * with the icons needed for the grader report. - * - * @param object $object - * @param object $tree (A complete grade_tree object) - * @param array $icons An array of icon names that this function is explicitly requested to print, regardless of settings - * @param bool $limit If true, use the $icons array as the only icons that will be printed. If false, use it to exclude these icons. - * @return string HTML - */ -function grade_get_icons($element, $tree, $icons=null, $limit=true) { - global $CFG; - global $USER; - - - // Load language strings - $stredit = get_string("edit"); - $streditcalculation= get_string("editcalculation", 'grades'); - $strfeedback = get_string("feedback"); - $strmove = get_string("move"); - $strmoveup = get_string("moveup"); - $strmovedown = get_string("movedown"); - $strmovehere = get_string("movehere"); - $strcancel = get_string("cancel"); - $stredit = get_string("edit"); - $strdelete = get_string("delete"); - $strhide = get_string("hide"); - $strshow = get_string("show"); - $strlock = get_string("lock", 'grades'); - $strswitch_minus = get_string("contract", 'grades'); - $strswitch_plus = get_string("expand", 'grades'); - $strunlock = get_string("unlock", 'grades'); - - // Prepare container div - $html = '
'; - - // Prepare reference variables - $eid = $element['eid']; - $object = $element['object']; - $type = $element['type']; - - // Add mock attributes in case the object is not of the right type - if ($type != 'grade') { - $object->feedback = ''; - } - - // Load user preferences - $aggregationview = get_user_preferences('grade_report_aggregationview', $CFG->grade_report_aggregationview); - $showeyecons = get_user_preferences('grade_report_showeyecons', $CFG->grade_report_showeyecons); - $showlocks = get_user_preferences('grade_report_showlocks', $CFG->grade_report_showlocks); - $showcalculations = get_user_preferences('grade_report_showcalculations', $CFG->grade_report_showcalculations); - - // Prepare image strings - $edit_category_icon = '' - . ''
-                        . $stredit.''. "\n"; - - $edit_item_icon = '' - . ''
-                    . $stredit.''. "\n"; - $overlib = ''; - if (!empty($object->feedback)) { - $overlib = 'onmouseover="return overlib(\''.$object->feedback.'\', CAPTION, \'' - . $strfeedback.'\');" onmouseout="return nd();"'; - } - - $edit_grade_icon = '' - . '' . $stredit.''. "\n"; - - - $edit_calculation_icon = '' - . ''
-                           . $streditcalculation.''. "\n"; - - // Prepare Hide/Show icon state - $hide_show = 'hide'; - if ($object->is_hidden()) { - $hide_show = 'show'; - } - - $show_hide_icon = 'commonvars\">\n" - . ''
-                    . ${'str' . $hide_show}.''. "\n"; - - // Prepare lock/unlock string - $lock_unlock = 'lock'; - if ($object->is_locked()) { - $lock_unlock = 'unlock'; - } - - // Print lock/unlock icon - - $lock_unlock_icon = 'commonvars\">\n" - . ''
-                      . ${'str' . $lock_unlock}.''. "\n"; - - // Prepare expand/contract string - $expand_contract = 'switch_minus'; // Default: expanded - $state = get_user_preferences('grade_category_' . $object->id, GRADE_CATEGORY_EXPANDED); - if ($state == GRADE_CATEGORY_CONTRACTED) { - $expand_contract = 'switch_plus'; - } - - $contract_expand_icon = 'commonvars\">\n" - . ''
-                          . ${'str' . $expand_contract}.''. "\n"; - - // If an array of icon names is given, return only these in the order they are given - if (!empty($icons) && is_array($icons)) { - $new_html = ''; - - foreach ($icons as $icon_name) { - if ($icon_name == 'edit') { - $icon_name .= "_$type"; - } - if ($limit) { - $new_html .= ${$icon_name . '_icon'}; - } else { - ${'show_' . $icon_name} = false; - } - } - if ($limit) { - return $new_html; - } else { - $html .= $new_html; - } - } - - // Icons shown when edit mode is on - if ($USER->gradeediting) { - // Edit icon (except for grade_grades) - if ($type == 'category') { - $html .= $edit_category_icon; - - } else if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') { - $html .= $edit_item_icon; - } - - // Calculation icon for items and categories - if ($showcalculations && $type != 'grade') { - $html .= $edit_calculation_icon; - } - - if ($showeyecons) { - $html .= $show_hide_icon; - } - - if ($showlocks) { - $html .= $lock_unlock_icon; - } - - // If object is a category, display expand/contract icon - if (get_class($object) == 'grade_category' && $aggregationview == GRADER_REPORT_AGGREGATION_VIEW_COMPACT) { - $html .= $contract_expand_icon; - } - } else { // Editing mode is off - } - - return $html . '
'; -} - ?> diff --git a/lib/weblib.php b/lib/weblib.php index a21a35ee864..b4f1af7c137 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -3748,7 +3748,7 @@ has_capability('moodle/course:viewhiddenuserfields', $context)) { if (has_capability('moodle/notes:manage', $context) || has_capability('moodle/notes:view', $context)) { $output .= ''.get_string('notes','notes').'
'; } - + if (has_capability('moodle/site:viewreports', $context)) { $timemidnight = usergetmidnight(time()); $output .= ''. $string->activity .'
'; @@ -6291,7 +6291,7 @@ function print_location_comment($file, $line, $return = false) } -/** +/** * Returns an image of an up or down arrow, used for column sorting. To avoid unnecessary DB accesses, please * provide this function with the language strings for sortasc and sortdesc. * If no sort string is associated with the direction, an arrow with no alt text will be printed/returned. @@ -6299,17 +6299,17 @@ function print_location_comment($file, $line, $return = false) * @param string $strsort The language string used for the alt attribute of this image * @param bool $return Whether to print directly or return the html string * @return string HTML for the image - * + * * TODO See if this isn't already defined somewhere. If not, move this to weblib */ function print_arrow($direction='up', $strsort=null, $return=false) { global $CFG; - + if (!in_array($direction, array('up', 'down', 'right', 'left'))) { return null; } - - $return = null; + + $return = null; switch ($direction) { case 'up':