dirroot . '/grade/report/lib.php'); require_once($CFG->libdir.'/tablelib.php'); foreach (glob($CFG->dirroot . '/grade/report/stats/statistics/stat_*.php') as $filename) { require_once($filename); } /** * Class providing the API for the stats report, including harvesters, * reports, and adaptor methods for turing grades in to statistics. * @uses grade_report * @package gradebook */ class grade_report_stats extends grade_report { /** * Capability to view hidden items. * @var bool $canviewhidden */ private $canviewhidden; /** * Grade objects of users in the course * @var array $grades */ private $grades = array(); /** * Array of users final grades affter filtered based on * settings. * @var array $finalgrades */ private $finalgrades = array(); /** * The value returned from each statistic. * @var array $reportedstats */ private $reportedstats = array(); /** * The html of the report to output. * @var string $html */ public $html; /** * The table class used to make the html of the report. * @var object $table */ private $table; /** * Array of clases that extend stats witch have the logic to * generate the statstics. * @var array $stats */ private static $stats = array(); /** * Constructor. Initialises grade_tree, sets up group, baseurl * and pbarurl. * @param int $courseid the coures id for the report * @param object $gpr grade plugin tracking object * @context string $context */ public function __construct($courseid, $gpr, $context) { global $CFG; parent::__construct($courseid, $gpr, $context, null); $this->canviewhidden = has_capability('moodle/grade:viewhidden', get_context_instance(CONTEXT_COURSE, $this->course->id)); /// Set up urls $this->baseurl = 'index.php?id=' . $this->courseid; $this->pbarurl = 'index.php?id=' . $this->courseid; /// Set the position of the aggregation categorie based on pref $switch = $this->get_pref('stats', 'aggregationposition'); if ($switch == '' && isset($CFG->grade_aggregationposition)) { $switch = grade_get_setting($this->courseid, 'aggregationposition', $CFG->grade_aggregationposition); } /// Build grade tree $this->gtree = new grade_tree($this->courseid, false, $switch); /// Set up Groups if ($this->get_pref('stats', 'showgroups') || is_null($this->get_pref('stats', 'showgroups'))) { $this->setup_groups(); } /// Load stats classes from ./statistics $this->load_stats(); } /** * Load all the stats classes into $stats. * Looks in /statistics and trys to make an instence of any * class that is in a file that starts with stats_ and extends * stats directly. * @param bool $return if true return stats array, else store in $this->stats * @returns array array of clases that extend stats */ private function load_stats($return=false) { global $CFG; $stats = array(); foreach (glob($CFG->dirroot . '/grade/report/stats/statistics/stat_*.php') as $path) { $filename = substr(basename($path, '.php'), 5); if(class_exists($filename) && get_parent_class($class = new $filename) == 'stats' ) { $stats[$filename] = $class; } } if($return) { return $stats; } else { grade_report_stats::$stats = $stats; } } /** * Returns the current stats being used in the report or if no stats are * set, it returns the stats as whould be loaded by load_stats. * @returns array array of classes that extend stats */ public function get_stats() { if(!isset(grade_report_stats::$stats) || is_null(grade_report_stats::$stats) || empty(grade_report_stats::$stats)) { return grade_report_stats::load_stats(true); } else { return grade_report_stats::$stats; } } /// Added to keep grade_report happy public function process_data($data){} public function process_action($target, $action){} /** * Based on load user function from grader report. * Pulls out the userids of the users to be used in the stats. * @return array array of user ids to use in stats */ public function load_users() { global $CFG, $DB; if(isset($DB) && !is_null($DB)) { $params = array(); list($usql, $gbr_params) = $DB->get_in_or_equal(explode(',', $this->gradebookroles)); $sql = "SELECT u.id FROM {user} u JOIN {role_assignments} ra ON u.id = ra.userid $this->groupsql WHERE ra.roleid $usql $this->groupwheresql AND ra.contextid ".get_related_contexts_string($this->context); $params = array_merge($gbr_params, $this->groupwheresql_params); $this->users = $DB->get_records_sql($sql, $params); } else { $sql = "SELECT u.id, u.firstname, u.lastname, u.imagealt, u.picture, u.idnumber FROM {$CFG->prefix}user u 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); $this->users = get_records_sql($sql); } if (empty($this->users)) { $this->userselect = ''; $this->users = array(); $this->userselect_params = array(); } else { if(isset($DB) && !is_null($DB)) { list($usql, $params) = $DB->get_in_or_equal(array_keys($this->users)); $this->userselect = "AND g.userid $usql"; $this->userselect_params = $params; }else{ $this->userselect = 'AND g.userid in ('.implode(',', array_keys($this->users)).')'; } } return $this->users; } /** * Encode an array of stat's data in to a stirng so it can * be put in the get part of a url. * @param arrray $array array of data to encode * @param object $item grade_item to use to fromat stats * @param object $stat stats object to use to format stats * @return string encoded string */ private function encode_array(array $array, $item, $stat) { $string = ''; /// Encode each elment by puting a delimiter and base64 encoding it. foreach($array as $id=>$data) { $string .= addslashes(grade_format_gradevalue($data, $item, true, $stat->displaytype, $stat->decimals)) . '"'; } return strtr(base64_encode($string), '+/=', '-_,'); } /** * Harvest the grades from the data base and build the finalgrades array. * Filters out hidden, locked and null grades based on users settings. * Partly based on grader reports load_final_grades function. */ public function harvest_data() { global $CFG, $DB; $params = array(); if(isset($DB) && !is_null($DB)) { $params = array_merge(array($this->courseid), $this->userselect_params); /// please note that we must fetch all grade_grades fields if we want to contruct grade_grade object from it! $sql = "SELECT g.* FROM {grade_items} gi, {grade_grades} g WHERE g.itemid = gi.id AND gi.courseid = ? {$this->userselect}"; $grades = $DB->get_records_sql($sql, $params); } else { /// please note that we must fetch all grade_grades fields if we want to contruct grade_grade object from it! $sql = "SELECT g.* FROM {$CFG->prefix}grade_items gi, {$CFG->prefix}grade_grades g WHERE g.itemid = gi.id AND gi.courseid = {$this->courseid} {$this->userselect}"; $grades = get_records_sql($sql); } $userids = array_keys($this->users); if ($grades) { foreach ($grades as $graderec) { if (in_array($graderec->userid, $userids) and array_key_exists($graderec->itemid, $this->gtree->items)) { // some items may not be present!! $this->grades[$graderec->itemid][$graderec->userid] = new grade_grade($graderec, false); $this->grades[$graderec->itemid][$graderec->userid]->grade_item =& $this->gtree->items[$graderec->itemid]; // db caching } } } /// prefil grades that do not exist yet foreach ($userids as $userid) { foreach ($this->gtree->items as $itemid=>$unused) { if (!isset($this->grades[$itemid][$userid])) { $this->grades[$itemid][$userid] = new grade_grade(); $this->grades[$itemid][$userid]->itemid = $itemid; $this->grades[$itemid][$userid]->userid = $userid; $this->grades[$itemid][$userid]->grade_item =& $this->gtree->items[$itemid]; // db caching } } } $this->finalgrades = array(); /// Build finalgrades array and filliter out unwanted grades. foreach ($this->gtree->items as $id=>$item) { if(($item->gradetype == GRADE_TYPE_SCALE && ($this->get_pref('stats', 'showscaleitems') || is_null($this->get_pref('stats', 'showscaleitems')))) || ($item->gradetype == GRADE_TYPE_VALUE && ($this->get_pref('stats', 'showvalueitems') || is_null($this->get_pref('stats', 'showvalueitems'))))) { $this->finalgrades[$id] = array(); $i = 0; if(isset($this->grades[$id]) && !is_null($this->grades[$id])) { foreach ($this->grades[$id] as $grade) { if( (($grade->is_hidden() && $this->canviewhidden && ($this->get_pref('stats', 'usehidden') || is_null($this->get_pref('stats', 'usehidden')))) || !$grade->is_hidden()) && (($grade->is_locked() && ($this->get_pref('stats', 'uselocked') || is_null($this->get_pref('stats', 'uselocked')))) || !$grade->is_locked())) { if($this->get_pref('stats', 'incompleasmin') && is_null($grade->finalgrade)) { $this->finalgrades[$id][$i] = $item->grademin; $i++; } elseif(!is_null($grade->finalgrade)) { $this->finalgrades[$id][$i] = $grade->finalgrade; $i++; } } } } } } } /** * Runs grades for each item threw the report functions * of each stats class in $stats and stores the values in * reportedstats. */ public function report_data() { $this->reportedstats = array(); foreach(grade_report_stats::$stats as $name=>$stat) { if(($stat->capability == null || has_capability($stat->capability, $this->context)) && ($this->get_pref('stats', $name) || is_null($this->get_pref('stats', $name)))) { $this->reportedstats[$name] = array(); foreach($this->finalgrades as $itemid=>$item) { sort($item); if(count($item) > 0) { $this->reportedstats[$name][$itemid] = $stat->report_data($item, $this->gtree->items[$itemid]); } else { $this->reportedstats[$name][$itemid] = null; } } } } } /** * Take the reported data and adapt it in to HTML to output. * HTML is stored in html. * TODO: Deal with tables growing to wide. * TODO: Make it look nice. */ public function adapt_data($printerversion = false) { global $CFG; $inverted = $this->get_pref('stats', 'showinverted'); /// Set up table arrays $tablecolumns = array('statistic'); $tableheaders = array($this->get_lang_string('statistic', 'gradereport_stats')); /// Loop threw items and build arrays if ($inverted) { if($this->get_pref('stats', 'showranges')) { array_push($tablecolumns, 'range'); array_push($tableheaders, $this->get_lang_string('range', 'gradereport_stats')); } foreach($this->reportedstats as $name=>$data) { array_push($tablecolumns, $name); array_push($tableheaders, grade_report_stats::$stats[$name]->name); } if($this->get_pref('stats', 'shownumgrades')) { array_push($tablecolumns, 'num_grades'); array_push($tableheaders, $this->get_lang_string('num_grades', 'gradereport_stats')); } } else { /// Set up range column and number of grades column $ranges = array(format_text('' . $this->get_lang_string('range', 'gradereport_stats') . '', FORMAT_HTML)); $numgrades = array(format_text('' . $this->get_lang_string('num_grades', 'gradereport_stats') . '', FORMAT_HTML)); foreach($this->finalgrades as $itemid=>$grades) { array_push($tablecolumns, $itemid); array_push($tableheaders, format_text($this->gtree->items[$itemid]->get_name(), FORMAT_HTML)); array_push($ranges, format_text('' . grade_format_gradevalue($this->gtree->items[$itemid]->grademin, $this->gtree->items[$itemid], true) . '-' . grade_format_gradevalue($this->gtree->items[$itemid]->grademax, $this->gtree->items[$itemid], true) . '' , FORMAT_HTML)); array_push($numgrades, format_text(count($grades), FORMAT_HTML)); } } /// Set up flexible table $this->table = new flexible_table('grade-report-stats-' . $this->courseid); $this->table->define_columns($tablecolumns); $this->table->define_headers($tableheaders); if ($printerversion) { $this->table->collapsible(false); $this->table->set_attribute('cellspacing', '1'); $this->table->set_attribute('border', '1'); } else { $this->table->define_baseurl($this->baseurl); $this->table->collapsible(true); $this->table->set_attribute('cellspacing', '1'); $this->table->set_attribute('id', 'stats-grade'); $this->table->set_attribute('class', 'grade-report-stats gradestable flexible'); } $this->table->setup(); /// If ranges are being shown add them to the table if(!$inverted){ if ($this->get_pref('stats', 'showranges')){ $this->table->add_data($ranges); $this->table->add_separator(); } } /// Loop threw all the reported data and format it in to cells /// If stat retured an array of values display the elements or /// make a link to a popup with the data in it. if($inverted) { foreach($this->finalgrades as $itemid=>$grades) { $item = $this->gtree->items[$itemid]; $row = array(format_text('' . $item->get_name() . '' , FORMAT_HTML)); if($this->get_pref('stats', 'showranges')) { array_push($row, format_text('' . grade_format_gradevalue($item->grademin, $item, true) . '-' . grade_format_gradevalue($item->grademax, $item, true) . '' , FORMAT_HTML)); } foreach($this->reportedstats as $name=>$data) { $stat = $data[$itemid]; if(!is_array($stat)) { array_push($row, format_text(grade_format_gradevalue($stat, $item, true, grade_report_stats::$stats[$name]->displaytype, grade_report_stats::$stats[$name]->decimals), FORMAT_HTML)); } else { $statstring = ""; for($i = 0; $i < 2; $i++) { if($i >= count($stat)) { break; } $statstring .= grade_format_gradevalue($stat[$i], $item, true, grade_report_stats::$stats[$name]->displaytype, grade_report_stats::$stats[$name]->decimals) . ', '; } if($i < count($stat)) { if(!$printerversion) { $statstring = "wwwroot}/grade/report/stats/arrayview.php?id={$this->courseid}&data={$this->encode_array($stat, $item, grade_report_stats::$stats[$name])}','{$this->get_lang_string('moredata', 'gradereport_stats')}','width=300,height=500,menubar=no,status=no,location=no,directories=no,toolbar=no,scrollbars=yes');\">". format_text($statstring, FORMAT_HTML) . '....'; } else { $statstring .= '...'; } } else { $statstring = substr($statstring, 0, strlen($statstring) - 2); } array_push($row, $statstring); } } if($this->get_pref('stats', 'shownumgrades')) { array_push($row, format_text(count($grades), FORMAT_HTML)); } $this->table->add_data($row); } } else { foreach($this->reportedstats as $name=>$data) { $row = array(format_text('' . grade_report_stats::$stats[$name]->name . '', FORMAT_HTML)); foreach($data as $itemid=>$stat) { if(!is_array($stat)) { array_push($row, format_text(grade_format_gradevalue($stat, $this->gtree->items[$itemid], true, grade_report_stats::$stats[$name]->displaytype, grade_report_stats::$stats[$name]->decimals), FORMAT_HTML)); } else { $statstring = ""; for($i = 0; $i < 2; $i++) { if($i >= count($stat)) { break; } $statstring .= grade_format_gradevalue($stat[$i], $this->gtree->items[$itemid], true, grade_report_stats::$stats[$name]->displaytype, grade_report_stats::$stats[$name]->decimals) . ', '; } if($i < count($stat)) { if(!$printerversion) { $statstring = "wwwroot}/grade/report/stats/arrayview.php?id={$this->courseid}&data={$this->encode_array($stat, $this->gtree->items[$itemid], grade_report_stats::$stats[$name])}','{$this->get_lang_string('moredata', 'gradereport_stats')}','width=300,height=500,menubar=no,status=no,location=no,directories=no,toolbar=no,scrollbars=yes');\">". format_text($statstring, FORMAT_HTML) . '....'; } else { $statstring .= '...'; } } else { $statstring = substr($statstring, 0, strlen($statstring) - 2); } array_push($row, $statstring); } } $this->table->add_data($row); } } /// If the number of grades is being shown add it to the table. if(!$inverted) { if ($this->get_pref('stats', 'shownumgrades')){ $this->table->add_separator(); $this->table->add_data($numgrades); } } /// Build html ob_start(); if($this->currentgroup == 0) { echo format_text('Group: All participants', FORMAT_HTML); } else { echo format_text('Group: ' . groups_get_group_name($this->currentgroup), FORMAT_HTML); } $this->table->print_html(); $this->html = ob_get_clean(); } /** * Builds HTML for toggles on top of report. * Based on grader report get_toggles_html * @return string html code for toggles. */ public function get_toggles_html() { global $CFG, $USER; $html = '