Parent Directory
|
Revision Log
CONTRIB-496 First version of report/stats plug-in as part of the animated grade statistics report GSoC project. Adds basic required functionality for a new text based grade report plug-in that displays several statistics such as mean, mode, highest grade, lowest grade, etc. Also added user options to control the look and how the statistics are calculated. TODO: # Add outcomes. # Improve the look and readability. # Add admin/teacher settings to override if a student can see a given statistic. # Add help text for the settings page. # More settings. # Printable version. # Add more documentation # Export ability # Clean up code a bit
<?php
/**
* File in which the grade_report_stats class is defined.
*/
require_once($CFG->dirroot . '/grade/report/lib.php');
require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->dirroot . '/grade/report/stats/statistics/load.php');
/**
* 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.
* TODO: Add a check for hidden items and grades.
* @var boolean $canviewhidden
*/
private $canviewhidden;
/**
* Categories to be displayed based on report setting.
* @var array $collapsed
*/
private $collapsed;
/**
* Grade objects of users in the course
* @var array $grades
*/
private $grades;
/**
* Array of users final grades affter filtered based on
* settings.
* @var array $finalgrades
*/
private $finalgrades;
/**
* The value returned from each statistic.
* @var array $reportedstats
*/
private $reportedstats;
/**
* 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;
/**
* 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 categories to be displayed.
if($this->get_pref('stats', 'aggregationview') == GRADE_REPORT_PREFERENCE_DEFAULT || $this->get_pref('stats', 'aggregationview') == GRADE_REPORT_AGGREGATION_VIEW_FULL) {
$this->collapsed = array('aggregatesonly' => array(), 'gradesonly' => array());
} elseif($this->get_pref('stats', 'aggregationview') == GRADE_REPORT_AGGREGATION_VIEW_AGGREGATES_ONLY) {
$this->collapsed = array('aggregatesonly' => array());
} elseif($this->get_pref('stats', 'aggregationview') == GRADE_REPORT_AGGREGATION_VIEW_GRADES_ONLY) {
$this->collapsed = array('gradesonly' => array());
} else {
$this->collapsed = array('aggregatesonly' => array(), 'gradesonly' => array());
}
/// 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'))) {
if(is_callable(array($this, 'setup_groups'))) {
$this->setup_groups();
} else {
$this->local_setup_groups();
}
}
}
public function process_data($data){}
public function process_action($target, $action){}
private function local_setup_groups() {
global $CFG;
/// find out current groups mode
$this->group_selector = groups_print_course_menu($this->course, $this->pbarurl, true);
$this->currentgroup = groups_get_course_group($this->course);
if ($this->currentgroup) {
$this->groupsql = " LEFT JOIN {groups_members} gm ON gm.userid = u.id ";
$this->groupwheresql = " AND gm.groupid = ? ";
$this->groupwheresql_params = array($this->currentgroup);
}
}
/**
* 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();
} 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, $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;
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->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.
* TODO: Do not report on hidden items if the user can not see them.
*/
public function report_data() {
global $stats;
$this->reportedstats = array();
foreach($stats as $name=>$stat) {
if($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() {
global $stats;
/// Set up table arrays
$tablecolumns = array('statistic');
$tableheaders = array($this->get_lang_string('statistic', 'gradereport_stats'));
/// Set up range column and number of grades column
$ranges = array('<strong>' . $this->get_lang_string('range', 'gradereport_stats') . '</strong>');
$numgrades = array('<strong>' . $this->get_lang_string('num_grades', 'gradereport_stats') . '</strong>');
/// Loop threw items and build arrays
foreach($this->finalgrades as $itemid=>$item) {
array_push($tablecolumns, $itemid);
array_push($tableheaders, $this->gtree->items[$itemid]->get_name());
array_push($ranges, '<strong>' . 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) . '<strong>' );
array_push($numgrades, count($item));
}
/// Set up flexible table
$this->table = new flexible_table('grade-report-stats-' . $this->courseid);
$this->table->define_columns($tablecolumns);
$this->table->define_headers($tableheaders);
$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 ($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.
foreach($this->reportedstats as $name=>$data) {
$row = array('<strong>' . $stats[$name]->name . '</strong>');
foreach($data as $itemid=>$stat) {
if(!is_array($stat)) {
array_push($row, grade_format_gradevalue($stat, $this->gtree->items[$itemid], true, $stats[$name]->displaytype, $stats[$name]->decimals));
} else {
$statstring = "";
for($i = 0; $i < 2; $i++) {
if($i >= count($stat)) {
break;
}
$statstring .= grade_format_gradevalue($stat[$i], $this->gtree->items[$itemid], true, $stats[$name]->displaytype, $stats[$name]->decimals) . ', ';
}
if($i < count($stat)) {
$statstring = "<a href=\"#\" onClick=\"javascript:window.open('arrayview.php?data={$this->encode_array($stat, $this->gtree->items[$itemid], $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');\">". $statstring . '....</a>';
} 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 ($this->get_pref('stats', 'shownumgrades')){
$this->table->add_separator();
$this->table->add_data($numgrades);
}
/// Build html
ob_start();
if($this->currentgroup == 0) {
echo '<strong>Group:</strong> All participants';
} else {
echo '<strong>Group:</strong> ' . groups_get_group_name($this->currentgroup);
}
$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 = '<div id="stats-report-toggles" style="vertical-align: text-top; text-align: center;">';
$html .= $this->print_toggle('numgrades', true);
$html .= $this->print_toggle('groups', true);
$html .= $this->print_toggle('ranges', true);
$html .= '</div>';
return $html;
}
/**
* Builds HTML for each individual toggle.
* Based on grader report print_toggle
* @param string $type The toggle type.
* @param bool $return Wheather ro return the HTML or print it.
*/
private function print_toggle($type, $return=false) {
global $CFG;
$icons = array('eyecons' => 't/hide.gif',
'numgrades' => 't/grades.gif',
'calculations' => 't/calc.gif',
'locks' => 't/lock.gif',
'averages' => 't/mean.gif',
'nooutcomes' => 't/outcomes.gif');
$pref_name = 'grade_report_statsshow' . $type;
if (array_key_exists($pref_name, $CFG)) {
$show_pref = get_user_preferences($pref_name, $CFG->$pref_name);
} else {
$show_pref = get_user_preferences($pref_name);
}
$strshow = $this->get_lang_string('show' . $type, 'gradereport_stats');
$strhide = $this->get_lang_string('hide' . $type, 'gradereport_stats');
$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 = "t/$type.gif";
}
$string = ${'str' . $show_hide};
$img = '<img src="'.$CFG->pixpath.'/'.$image_name.'" class="iconsmall" alt="'
.$string.'" title="'.$string.'" />'. "\n";
$retval = $img . '<a href="' . $this->baseurl . "&toggle=$toggle_action&toggle_type=$type\">"
. $string . '</a> ';
if ($return) {
return $retval;
} else {
echo $retval;
}
}
}
?>
| Moodle CVS Admin | ViewVC Help |
| Powered by ViewVC 1.0.7 |