Development:Gradebook Report Tutorial
Introduction
One of the great features of the new gradebook implementation in Moodle 1.9 is the support for plug-in reports. A few reports are already included in the release, but it is fast and easy to create new reports of any kind.
This page is a small tutorial on how to create a new report for the Moodle gradebook. The first section highlights the basic setup steps, the bare minimum to get your plug-in detected and usable. The second section gives an example of a possible implementation, although you are free to develop outside of these suggestions.
The simplest example report to look at while following this guide is grade/report/overview.
Bare minimum
These steps all involve creating new files, but in all cases they can be copy-pasted from existing reports, to make your life easier.
1. Create a new folder under grade/report
grade/report/[newreport]
2. Create a /db sub-folder under the new report folder
grade/report/[newreport]/db
3. Create an access.php file in the db folder with the following content. You should change the capabilities as needed.
grade/report/[newreport]/db/access.php <?php $gradereport_[newreport]_capabilities = array( 'gradereport/[newreport]:view' => array( 'riskbitmask' => RISK_PERSONAL, 'captype' => 'read', 'contextlevel' => CONTEXT_COURSE, 'legacy' => array( 'student' => CAP_ALLOW, 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'admin' => CAP_ALLOW ) ), ); ?>
4. Create a version.php file with the current dates:
grade/report/[newreport]/version.php <?php $plugin->version = 2007081000; $plugin->requires = 2007081000; ?>
5.Create an index.php
grade/report/[newreport]/index.php
6.Create a language file for your report. Include at least the modulename, and optionally the strings for different capabilities.
grade/report/[newreport]/lang/en_utf8/gradereport_[newreport].php Example from user report here: $string['modulename'] = 'User report'; $string['user:view'] = 'View your own grade report';
You do not need to create a new link to your report, it will be automatically detected and added to the gradebook plugin drop-down menu, if you've followed all these steps!
This is all you need to do to create a new report! Now, of course, you'll want the code to actually DO something, usually fetch and display some data. The next section discusses one easy approach using flexitable.
Possible step 7
I also needed to include the settings.php file in my new grade\report\[directory name] directory. If the file was not present, i received this error "Section Error!". Once i added the file to the grade\report\[directory name] directory, it corrected. The settings.php file is empty. I am using Moodle 1.9.
Possible step 8
If the report is made available to multiple users you may need to update the tables by going to the admin section of your Moodle installation, just like when you are upgrading. Then go the the define roles and make sure that your permissions are set correct. I'm using Moodle 1.9
Extending the grade_report class
You are free to develop that class any way you want, but by using the existing methods set up in grade_report, you can avoid tedious repetition of code. We use flexitable for simple reports, as you can see in the user report (grade_report_user class). The rest of this tutorial will follow the path of flexitable.
New file
- Create a lib.php file (or copy it from other report).
grade/report/[newreport]/lib.php
- The lib.php contains an extension of the grade_report class, and should be named
grade_report_[newreport]
Grade_report class variables
The grade_report class makes the following variables available to your new class, if you use its constructor in your child class' constructor:
- courseid
- Required by constructor
- gpr
- Grade plugin return tracking object, required by constructor
- context
- Required by constructor
- gtree
- The grade_tree must be instantiated in child classes: not all reports need the whole tree
- prefs
- Array of user preferences related to this report. Methods are given to get and set these easily
- gradebookroles
- The roles for this report, pulled out of $CFG->gradebookroles
- baseurl
- Base url for sorting by first/last name (not needed by all reports)
- pbarurl
- Base url for paging
- page
- Current page (for paging). Must be given to constructor if paging is required.
- lang_strings
- Array of cached language strings (using get_string() all the time takes a long time!). A method is provided to replace get_string() and use this cache
- currentgroup
- The current group being displayed.
- group_selector
- A HTML select element used to select the current group.
- groupsql
- An SQL fragment used to add linking information to the group tables.
- groupwheresql
- An SQL constraint to append to the queries used by this object to build the report.
Grade_report class methods
The grade_report class has the following methods which you can use, provided the right steps have been taken to initialise the object first:
- get_pref()
- Given the name of a user preference (without grade_report_ prefix), locally saves then returns the value of that preference. If the preference has already been fetched before, the saved value is returned. If the preference is not set at the User level, the $CFG equivalent is given (site default).
- set_pref()
- Uses set_user_preferences() to update the value of a user preference. If 'default' is given as the value, the preference will be removed in favour of a higher-level preference ($CFG->$pref_name usually)
- process_data()
- Abstract method, needs to be implemented by child classes if they want to handle user form submissions on the report they want to handle user actions on the report
- process_action()
- Abstract method, needs to be implemented by child classes if they want to handle user actions on the report
- get_grade_clean()
- format grade using lang specific decimal point and thousand separator the result is suitable for printing on html page
- format_grade()
- Given a user input grade, format it to standard format i.e. no thousand separator, and . as decimal point
- get_lang_string()
- First checks the cached language strings, then returns match if found, or uses get_string(). Use this for any lang_strings in the grades.php file.
- grade_to_percentage()
- Computes then returns the percentage value of the grade value within the given range.
- get_grade_letters()
- Fetches and returns an array of grade letters indexed by their grade boundaries, as stored in preferences.
- get_numusers()
- Fetches and returns a count of all the users that will be shown on this page.
- setup_groups()
- Sets up this object's group variables, mainly to restrict the selection of users to display.
- get_sort_arrow()
- Returns an arrow icon inside an <a> tag, for the purpose of sorting a column.
- get_module_link()
- Builds and returns a HTML link to the grade or view page of the module given. If no itemmodule is given, only the name of the category/item is returned, no link.
Report child class
Assuming you are using flexitable, your child class needs the following variable and methods:
$table : The flexitable that will hold the data
- grade_report_[newreport]() : Constructor. You can set up anything here, but you must call the parent constructor with the 3 required params:
parent::grade_report($COURSE->id, $gpr, $context); The $gpr and $context variables are normally set up in grade/report/[newreport]/index.php
- setup_table() : Prepares the headers and attributes of the flexitable. Example used for the very simple overview report:
// setting up table headers $tablecolumns = array('coursename', 'grade', 'rank'); $tableheaders = array($this->get_lang_string('coursename', 'grades'), $this->get_lang_string('grade'), $this->get_lang_string('rank', 'grades')); $this->table = new flexible_table('grade-report-overview-'.$this->user->id); $this->table->define_columns($tablecolumns); $this->table->define_headers($tableheaders); $this->table->define_baseurl($this->baseurl); $this->table->set_attribute('cellspacing', '0'); $this->table->set_attribute('id', 'overview-grade'); $this->table->set_attribute('class', 'boxaligncenter generaltable'); $this->table->setup();
- fill_table() : After setup_table(), gathers and enters the data in the table. Again, from the overview report:
global $CFG; $numusers = $this->get_numusers(); if ($courses = get_courses('all', null, 'c.id, c.shortname')) { foreach ($courses as $course) { // Get course grade_item $grade_item_id = get_field('grade_items', 'id', 'itemtype', 'course', 'courseid', $course->id); // Get the grade $finalgrade = get_field('grade_grades', 'finalgrade', 'itemid', $grade_item_id, 'userid', $this->user->id); /// prints rank if ($finalgrade) { /// find the number of users with a higher grade $sql = "SELECT COUNT(DISTINCT(userid)) FROM {$CFG->prefix}grade_grades WHERE finalgrade > $finalgrade AND itemid = $grade_item_id"; $rank = count_records_sql($sql) + 1; $rankdata = "$rank/$numusers"; } else { // no grade, no rank $rankdata = "-"; } $this->table->add_data(array($course->shortname, $finalgrade, $rankdata)); } return true; } else { notify(get_string('nocourses', 'grades')); return false; }
- print_table() : Just does what its name says...
ob_start(); $this->table->print_html(); $html = ob_get_clean(); if ($return) { return $html; } else { echo $html; }
- process_data() and process_action() : You can implement these two methods if you need to handle data and actions.
Set up the index.php file
Here is the simple example from the overview report (grade/report/overview).
require_once '../../../config.php'; require_once $CFG->libdir.'/gradelib.php'; require_once $CFG->dirroot.'/grade/lib.php'; require_once $CFG->dirroot.'/grade/report/overview/lib.php'; $courseid = optional_param('id', $COURSE->id, PARAM_INT); $userid = optional_param('userid', $USER->id, PARAM_INT); /// basic access checks if (!$course = get_record('course', 'id', $courseid)) { print_error('nocourseid'); } require_login($course); if (!$user = get_complete_user_data('id', $userid)) { error("Incorrect userid"); } $context = get_context_instance(CONTEXT_COURSE, $course->id); $usercontext = get_context_instance(CONTEXT_USER, $user->id); require_capability('gradereport/overview:view', $context); $access = true; if (has_capability('moodle/grade:viewall', $context)) { //ok - can view all course grades } else if ($user->id == $USER->id and has_capability('moodle/grade:view', $context) and $course->showgrades) { //ok - can view own grades } else if (has_capability('moodle/grade:viewall', $usercontext) and $course->showgrades) { // ok - can view grades of this user- parent most probably } else { $acces = false; } /// return tracking object $gpr = new grade_plugin_return(array('type'=>'report', 'plugin'=>'overview', 'courseid'=>$course->id, 'userid'=>$userid)); /// last selected report session tracking if (!isset($USER->grade_last_report)) { $USER->grade_last_report = array(); } $USER->grade_last_report[$course->id] = 'overview'; /// Build navigation $strgrades = get_string('grades'); $reportname = get_string('modulename', 'gradereport_overview'); $navigation = grade_build_nav(__FILE__, $reportname, $course->id); /// Print header print_header_simple($strgrades.': '.$reportname, ': '.$strgrades, $navigation, , , true, , navmenu($course)); /// Print the plugin selector at the top print_grade_plugin_selector($course->id, 'report', 'overview'); if ($access) { //first make sure we have proper final grades - this must be done before constructing of the grade tree grade_regrade_final_grades($course->id); // Create a report instance $report = new grade_report_overview($userid, $gpr, $context); $gradetotal = 0; $gradesum = 0; // print the page print_heading(get_string('modulename', 'gradereport_overview'). ' - '.fullname($report->user)); if ($report->fill_table()) { echo $report->print_table(true); } } else { // no access to grades! echo "Can not view grades."; //TODO: localize } print_footer($course);
Conclusion
This short tutorial doesn't explain how to actually create a useful report. That part is essentially up to you, and there are no hard rules about how to do it. Instead, this tutorial explains how to setup a "stub" report, a basic framework with a set of tools and variables you can use to create a fully functional and essential report for your Moodle needs. Please share your report-building experiences and tribulations with the Moodle community through the forum.
See also
- Gradebook Development ideas forum discussion
- Using Moodle New gradebook for Moodle forum discussion
- Development:Grades