Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Gradebook reports

From MoodleDocs
(Redirected from 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
   $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,
               'manager' => 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! 7. Create an upgrade.php file in the db folder with the following content: grade/report/[newreport]/db/upgrade.php TODO

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 8

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.

I did not need to add setting.php file using Moodle 2.1.

Possible step 9

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

  1. Create a lib.php file (or copy it from other report).
   grade/report/[newreport]/lib.php
  1. 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