Note:

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

Assign feedback plugins

From MoodleDocs
Revision as of 16:29, 10 June 2022 by Dev Docs Bot (talk | contribs) (Update migration status and path)
Important:

This content of this page has been updated and migrated to the new Moodle Developer Resources. The information contained on the page should no longer be seen up-to-date.

Why not view this page on the new site and help us to migrate more content to the new site!

Introduction

This page gives an overview of assignment feedback plugins.

Overview of an assignment feedback plugin

An assignment feedback plugin can do many things including providing feedback to students about a submission. The grading interface for the assignment module provides many hooks that allow plugins to add their own entries and participate in the grading workflow.

History

Assignment feedback plugins were added with the assignment module rewrite for Moodle 2.3.

Template

A good example is the "file" feedback plugin included with core because it uses most of the features of feedback plugins.

File structure

The files for a custom feedback plugin sit under "mod/assign/feedback/<pluginname>". A plugin should not include any custom files outside of it's own plugin folder. Note: The plugin name should be no longer than 13 characters - this is because the database tables for a submission plugin must be prefixed with "assignfeedback_" + pluginname (15 chars + X) and the table names can be no longer than 28 chars (thanks oracle). If a plugin requires multiple database tables, the plugin name will need to be shorter to allow different table names to fit under the 28 character limit. All examples in this document exclude the required copyright and license information from source files for brevity.


version.php

To start with we need to tell Moodle the version information for our new plugin so that it can be installed and upgraded correctly. This information is added to version.php as with any other type of Moodle plugin. The component name must begin with "assignfeedback_" to identify this as a feedback plugin.

See version.php for more information.

defined('MOODLE_INTERNAL') || die();                                                                                                
                                                                                                                                    
$plugin->version   = 2012112900;                                                                                                    
$plugin->requires  = 2012112900;                                                                                                    
$plugin->component = 'assignfeedback_file';

settings.php

The settings file allows us to add custom settings to the system wide configuration page for our plugin.

All feedback settings should be named 'assignfeedback_pluginname/settingname' in order for the setting to be associated with the plugin.

All feedback plugins should include one setting named 'default' to indicate if the plugin should be enabled by default when creating a new assignment.

$settings->add(new admin_setting_configcheckbox('assignfeedback_file/default',                                                      
                   new lang_string('default', 'assignfeedback_file'),                                                               
                   new lang_string('default_help', 'assignfeedback_file'), 0));                                                     
                                                                                                                                    

lang/en/assignfeedback_pluginname.php

The language file for this plugin must have the same name as the component name (e.g. "assignfeedback_file.php"). It should at least define strings for "pluginname", "enabled" and "enabled_help". For example:

$string['pluginname'] = 'Awesome feedback';
$string['enabled'] = 'Awesome feedback';
$string['enabled_help'] = 'If enabled, the teacher will be able to provide awesome feedback '; 

db/access.php

This is where any additional capabilities are defined if required. This file can be omitted if there are no capabilities added by the plugin.

See Activity_modules#access.php for more information.

$capabilities = array(
    'assignfeedback/dungeon:master' => array(
        'riskbitmask' => RISK_XSS,
        'captype' => 'write',
        'contextlevel' => CONTEXT_COURSE,
        'archetypes' => array(
            'editingteacher' => CAP_ALLOW,
            'manager' => CAP_ALLOW
        ),
        'clonepermissionsfrom' => 'moodle/course:manageactivities'
    ),
);

db/upgrade.php

This is where any upgrade code is defined.

See Activity_modules#upgrade.php for more infomation.

function xmldb_feedback_file_upgrade($oldversion) {
    global $CFG, $DB, $OUTPUT;

    $dbman = $DB->get_manager();
    if ($oldversion < 2012091800) {
        // Put upgrade code here

        // Savepoint reached.
        upgrade_plugin_savepoint(true, 2012091800, 'assignfeedback', 'file');
    }

    return true;
}

db/install.xml

This is where any database tables required to save this plugins data are defined. File submissions define a table that links to submission and contains a column to record the number of files.

<?xml version="1.0" encoding="UTF-8" ?>                                                                                             
<XMLDB PATH="mod/assign/feedback/file/db" VERSION="20120423" COMMENT="XMLDB file for Moodle mod/assign/feedback/file"               
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"                                                                           
    xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"                                                              
>                                                                                                                                   
  <TABLES>                                                                                                                          
    <TABLE NAME="assignfeedback_file" COMMENT="Stores info about the number of files submitted by a grader.">                      
      <FIELDS>                                                                                                                      
        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>                                                    
        <FIELD NAME="assignment" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>                               
        <FIELD NAME="grade" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>                                    
        <FIELD NAME="numfiles" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The number of files uploaded by a grader."/>
      </FIELDS>                                                                                                                     
      <KEYS>                                                                                                                        
        <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Unique id for this feedback value."/>                               
        <KEY NAME="assignment" TYPE="foreign" FIELDS="assignment" REFTABLE="assign" REFFIELDS="id" COMMENT="The assignment instance this feedback relates to."/>
        <KEY NAME="grade" TYPE="foreign" FIELDS="grade" REFTABLE="assign_grades" REFFIELDS="id" COMMENT="The grade instance this feedback relates to."/>
      </KEYS>                                                                                                                       
    </TABLE>                                                                                                                        
  </TABLES>                                                                                                                         
</XMLDB>                                                 

db/install.php

This file contains custom code to run on installation of the plugin. In this case it makes the files plugin the second of the feedback plugins installed by default.

                                                                                                                           
function xmldb_assignfeedback_file_install() {                                                                                      
    global $CFG;                                                                                                                    
                                                                                                                                    
    require_once($CFG->dirroot . '/mod/assign/adminlib.php');                                                                       
                                                                                                                                    
    // Set the correct initial order for the plugins.                                                                               
    $pluginmanager = new assign_plugin_manager('assignfeedback');                                                                   
    $pluginmanager->move_plugin('file', 'down');                                                                                    
                                                                                                                                    
    return true;                                                                                                                    
}                      

lib.php

This file is the entry point to many standard Moodle APIs for plugins. An example is that in order for a plugin to allow users to download files contained within a filearea belonging to the plugin, they must implement the componentname_pluginfile function in order to perform their own security checks.

See File_API for more information. Example:

function assignfeedback_file_pluginfile($course,                                                                                    
                                        $cm,                                                                                        
                                        context $context,                                                                           
                                        $filearea,                                                                                  
                                        $args,                                                                                      
                                        $forcedownload) {                                                                           
    global $USER, $DB;                                                                                                              
                                                                                                                                    
    if ($context->contextlevel != CONTEXT_MODULE) {                                                                                 
        return false;                                                                                                               
    }                                                                                                                               
                                                                                                                                    
    require_login($course, false, $cm);                                                                                             
    $itemid = (int)array_shift($args);                                                                                              
    $record = $DB->get_record('assign_grades', array('id'=>$itemid), 'userid,assignment', MUST_EXIST);                              
    $userid = $record->userid;                                                                                                      
                                                                                                                                    
    if (!$assign = $DB->get_record('assign', array('id'=>$cm->instance))) {                                                         
        return false;                                                                                                               
    }                                                                                                                               
                                                                                                                                    
    if ($assign->id != $record->assignment) {                                                                                       
        return false;                                                                                                               
    }                                                                                                                               
                                                                                                                                    
    // Check is users feedback or has grading permission.                                                                           
    if ($USER->id != $userid and !has_capability('mod/assign:grade', $context)) {                                                   
        return false;                                                                                                               
    }                                                                                                                               
                                                                                                                                    
    $relativepath = implode('/', $args);                                                                                            
                                                                                                                                    
    $fullpath = "/{$context->id}/assignfeedback_file/$filearea/$itemid/$relativepath";                                              
                                                                                                                                    
    $fs = get_file_storage();                                                                                                       
    if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {                                                 
        return false;                                                                                                               
    }                                                                                                                               
    // Download MUST be forced - security!                                                                                          
    send_stored_file($file, 0, 0, true);                                                                                            
}

locallib.php

This is where all the functionality for this plugin is defined. We will step through this file and describe each part as we go.


class assign_feedback_file extends assign_feedback_plugin {

All feedback plugins MUST define a class with the component name of the plugin that extends assign_feedback_plugin.


    public function get_name() {
        return get_string('file', 'assignfeedback_file');
    }

Get name is abstract in feedback_plugin and must be defined in your new plugin. Use the language strings to make your plugin translatable.

    public function get_settings(MoodleQuickForm $mform) {
         $mform->addElement('assignfeedback_file_fileextensions', get_string('allowedfileextensions', 'assignfeedback_file'));
         $mform->setType('assignfeedback_file_fileextensions', PARAM_FILE);
    }

The "get_settings" function is called when building the settings page for the assignment. It allows this plugin to add a list of settings to the form. Notice that the settings should be prefixed by the plugin name which is good practice to avoid conflicts with other plugins. (None of the core feedback plugins have any instance settings, so this example is fictional).

    public function save_settings(stdClass $data) {                                                                                 
        $this->set_config('allowedfileextensions', $data->allowedfileextensions);                                             
        return true;                                                                                                                
    }   

The "save_settings" function is called when the assignment settings page is submitted, either for a new assignment or when editing an existing one. For settings specific to a single instance of the assignment you can use the assign_plugin::set_config function shown here to save key/value pairs against this assignment instance for this plugin.

    public function get_form_elements_for_user($grade, MoodleQuickForm $mform, stdClass $data, $userid)                                       
                                                                                                                                    
        $fileoptions = $this->get_file_options();                                                                                   
        $gradeid = $grade ? $grade->id : 0;                                                                                         
        $elementname = 'files_' . $userid;                                                                                          
                                                                                                                                    
        $data = file_prepare_standard_filemanager($data,                                                                            
                                                  $elementname,                                                                     
                                                  $fileoptions,                                                                     
                                                  $this->assignment->get_context(),                                                 
                                                  'assignfeedback_file',                                                            
                                                  ASSIGNFEEDBACK_FILE_FILEAREA,                                                     
                                                  $gradeid);                                                                        
        $mform->addElement('filemanager', $elementname . '_filemanager', html_writer::tag('span', $this->get_name(),                
            array('class' => 'accesshide')), null, $fileoptions);                                                                   
                                                                                                                                    
        return true;                                                                                                                
    }                                              

The "get_form_elements_for_user" function is called when building the feedback form. It functions identically to the get_settings function except that the grade object is available (if there is a grade) to associate the settings with a single grade attempt. This example also shows how to use a filemanager within a feedback plugin. The function must return true if it has modified the form otherwise the assignment will not include a header for this plugin. Notice there is an older version of this function "get_form_elements" which does not accept a userid as a parameter - this version is less useful - not recommended.

    public function is_feedback_modified(stdClass $grade, stdClass $data) {
        $commenttext = '';
        if ($grade) {
            $feedbackcomments = $this->get_feedback_comments($grade->id);
            if ($feedbackcomments) {
                $commenttext = $feedbackcomments->commenttext;
            }
        }

        if ($commenttext == $data->assignfeedbackcomments_editor['text']) {
            return false;
        } else {
            return true;
        }
    }

The is_feedback_modified function is called before feedback is saved. If feedback has not been modified then the save() method is not called. This function takes the grade object and submitted data from the grading form. In this example we are comparing the existing text comments made with the new ones. This function must return a boolean; True if the feedback has been modified; False if there has been no modification made. If this method is not overwritten then it will default to returning True.

   public function save(stdClass $grade, stdClass $data) {                                                                         
        global $DB;                                                                                                                 
                                                                                                                                    
        $fileoptions = $this->get_file_options();                                                                                   
                                                                                                                                    
        $userid = $grade->userid;                                                                                                   
        $elementname = 'files_' . $userid;                                                                                          
                                                                                                                                    
        $data = file_postupdate_standard_filemanager($data,                                                                         
                                                     $elementname,                                                                  
                                                     $fileoptions,                                                                  
                                                     $this->assignment->get_context(),                                              
                                                     'assignfeedback_file',                                                         
                                                     ASSIGNFEEDBACK_FILE_FILEAREA,                                                  
                                                     $grade->id);                                                                   
                                                                                                                                    
        $filefeedback = $this->get_file_feedback($grade->id);                                                                       
        if ($filefeedback) {                                                                                                        
            $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);                                 
            return $DB->update_record('assignfeedback_file', $filefeedback);                                                        
        } else {                                                                                                                    
            $filefeedback = new stdClass();                                                                                         
            $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);                                 
            $filefeedback->grade = $grade->id;                                                                                      
            $filefeedback->assignment = $this->assignment->get_instance()->id;                                                      
            return $DB->insert_record('assignfeedback_file', $filefeedback) > 0;                                                    
        }                                                                                                                           
    }                                         

The "save" function is called to save a graders feedback. The parameters are the grade object and the data from the feedback form. This example calls file_postupdate_standard_filemanager to copy the files from the draft file area to the filearea for this feedback. It then records the number of files in the plugin specific "assignfeedback_file" table.

    public function view_summary(stdClass $grade, & $showviewlink) {                                                                
        $count = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);                                                      
        // show a view all link if the number of files is over this limit                                                           
        $showviewlink = $count > ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES;                                                               
                                                                                                                                    
        if ($count <= ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES) {                                                                        
            return $this->assignment->render_area_files('assignfeedback_file', ASSIGNFEEDBACK_FILE_FILEAREA, $grade->id);           
        } else {                                                                                                                    
            return get_string('countfiles', 'assignfeedback_file', $count);                                                         
        }                                                                                                                           
    }                          

The view_summary function is called to display a summary of the feedback to both markers and students. It counts the number of files and if it is more that a set number, it only displays a count of how many files are in the feedback - otherwise it uses a helper function to write the entire list of files. This is because we want to keep the summaries really short so they can be displayed in a table. There will be a link to view the full feedback on the submission status page.

    public function view(stdClass $grade) {                                                                                         
        return $this->assignment->render_area_files('assignfeedback_file', ASSIGNFEEDBACK_FILE_FILEAREA, $grade->id);               
    }    

The view function is called to display the entire feedback to both markers and students. In this case it uses the helper function in the assignment class to write the list of files.

    public function can_upgrade($type, $version) {                                                                                  
                                                                                                                                    
        if (($type == 'upload' || $type == 'uploadsingle') && $version >= 2011112900) {                                             
            return true;                                                                                                            
        }                                                                                                                           
        return false;                                                                                                               
    }       

The can_upgrade function is used to identify old "Assignment 2.2" subtypes that can be upgraded by this plugin. This plugin supports upgrades from the old "upload" and "uploadsingle" assignment subtypes.

    
    public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {                                        
        // first upgrade settings (nothing to do)                                                                                   
        return true;                                                                                                                
    }                  

This function is called once per assignment instance to upgrade the settings from the old assignment to the new mod_assign. In this case it returns true as there are no settings to upgrade.


    public function upgrade(context $oldcontext, stdClass $oldassignment, stdClass $oldsubmission, stdClass $grade, & $log) {       
        global $DB;                                                                                                                 
                                                                                                                                    
        // now copy the area files                                                                                                  
        $this->assignment->copy_area_files_for_upgrade($oldcontext->id,                                                             
                                                        'mod_assignment',                                                           
                                                        'response',                                                                 
                                                        $oldsubmission->id,                                                         
                                                        // New file area                                                            
                                                        $this->assignment->get_context()->id,                                       
                                                        'assignfeedback_file',                                                      
                                                        ASSIGNFEEDBACK_FILE_FILEAREA,                                               
                                                        $grade->id);                                                                
                                                                                                                                    
        // now count them!                                                                                                          
        $filefeedback = new stdClass();                                                                                             
        $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);                                     
        $filefeedback->grade = $grade->id;                                                                                          
        $filefeedback->assignment = $this->assignment->get_instance()->id;                                                          
        if (!$DB->insert_record('assignfeedback_file', $filefeedback) > 0) {                                                        
            $log .= get_string('couldnotconvertgrade', 'mod_assign', $grade->userid);                                               
            return false;                                                                                                           
        }                                                                                                                           
        return true;                                                                                                                
    }                

The "upgrade" function upgrades a single submission from the old assignment type to the new one. In this case it involves copying all the files from the old filearea to the new one. There is a helper function available in the assignment class for this (Note: the copy will be fast as it is just adding rows to the files table). If this function returns false, the upgrade will be aborted and rolled back.

    public function is_empty(stdClass $submission) {                                                                                
        return $this->count_files($submission->id, ASSIGNSUBMISSION_FILE_FILEAREA) == 0;                                            
    }   

If a plugin has no data to show - it can return true from the is_empty function. This prevents a table row being added to the feedback summary for this plugin. It is also used to check if a grader has tried to save feedback with no data.

    public function get_file_areas() {                                                                                              
        return array(ASSIGNFEEDBACK_FILE_FILEAREA=>$this->get_name());                                                              
    }      

A plugin should implement get_file_areas if it supports saving of any files to moodle - this allows the file areas to be browsed by the moodle file manager.

    public function delete_instance() {                                                                                             
        global $DB;                                                                                                                 
        // will throw exception on failure                                                                                          
        $DB->delete_records('assignfeedback_file', array('assignment'=>$this->assignment->get_instance()->id));                     
                                                                                                                                    
        return true;                                                                                                                
    }         

The delete_instance function is called when a plugin is deleted. Note only database records need to be cleaned up - files belonging to fileareas for this assignment will be automatically cleaned up.

    public function format_for_gradebook(stdClass $grade) {                                                                         
        return FORMAT_MOODLE;                                                                                                       
    }

    public function text_for_gradebook(stdClass $grade) {                                                                           
        return '';                                                                                                                  
    }

Only one feedback plugin can push comments to the gradebook. Usually this is the feedback_comments plugin - but it can be configured to be any feedback plugin. If the current plugin is the plugin chosen to generate comments for the gradebook, the comment text and format will be taken from these two functions.

   /**                                                                                                                             
     * Override to indicate a plugin supports quickgrading                                                                          
     *                                                                                                                              
     * @return boolean - True if the plugin supports quickgrading                                                                   
     */                                                                                                                             
    public function supports_quickgrading() {                                                                                       
        return false;                                                                                                               
    }                                                                                                                               
                                                                                                                                    
    /**                                                                                                                             
     * Get quickgrading form elements as html                                                                                       
     *                                                                                                                              
     * @param int $userid The user id in the table this quickgrading element relates to                                             
     * @param mixed $grade grade or null - The grade data. May be null if there are no grades for this user (yet)                   
     * @return mixed - A html string containing the html form elements required for quickgrading or false to indicate this plugin does not support quickgrading
     */                                                                                                                             
    public function get_quickgrading_html($userid, $grade) {                                                                        
        return false;                                                                                                               
    }                                                                                                                               
                                                                                                                                    
    /**                                                                                                                             
     * Has the plugin quickgrading form element been modified in the current form submission?                                       
     *                                                                                                                              
     * @param int $userid The user id in the table this quickgrading element relates to                                             
     * @param stdClass $grade The grade                                                                                             
     * @return boolean - true if the quickgrading form element has been modified                                                    
     */                                                                                                                             
    public function is_quickgrading_modified($userid, $grade) {                                                                     
        return false;                                                                                                               
    }                                                                                                                               
                                                                                                                                    
    /**                                                                                                                             
     * Save quickgrading changes                                                                                                    
     *                                                                                                                              
     * @param int $userid The user id in the table this quickgrading element relates to                                             
     * @param stdClass $grade The grade                                                                                             
     * @return boolean - true if the grade changes were saved correctly                                                             
     */                                                                                                                             
    public function save_quickgrading_changes($userid, $grade) {                                                                    
        return false;                                                                                                               
    }                                

These 4 functions can be implemented to allow a plugin to support quickgrading. The feedback comments plugin is the only example of this in core.

    /**                                                                                                                             
     * Run cron for this plugin                                                                                                     
     */                                                                                                                             
    public static function cron() {                                                                                                 
    }

A plugin can run code when cron runs by implementing this method.

   /**                                                                                                                             
     * Return a list of the grading actions supported by this plugin.                                                               
     *                                                                                                                              
     * A grading action is a page that is not specific to a user but to the whole assignment.                                       
     * @return array - An array of action and description strings.                                                                  
     *                 The action will be passed to grading_action.                                                                 
     */                                                                                                                             
    public function get_grading_actions() {                                                                                         
        return array();                                                                                                             
    }                  

   /**                                                                                                                             
     * Show a grading action form                                                                                                   
     *                                                                                                                              
     * @param string $gradingaction The action chosen from the grading actions menu                                                 
     * @return string The page containing the form                                                                                  
     */                                                                                                                             
    public function grading_action($gradingaction) {                                                                                
        return '';                                                                                                                  
    }              

Grading actions appear in the select menu above the grading table and apply to the whole assignment. An example is "Upload grading worksheet". When a grading action is selected, the grading_action will be called with the action that was chosen (so plugins can have multiple entries in the list).

   /**                                                                                                                             
     * Return a list of the batch grading operations supported by this plugin.                                                      
     *                                                                                                                              
     * @return array - An array of action and description strings.                                                                  
     *                 The action will be passed to grading_batch_operation.                                                        
     */                                                                                                                             
    public function get_grading_batch_operations() {                                                                                
        return array();                                                                                                             
    }     

    /**                                                                                                                             
     * Show a batch operations form                                                                                                 
     *                                                                                                                              
     * @param string $action The action chosen from the batch operations menu                                                       
     * @param array $users The list of selected userids                                                                             
     * @return string The page containing the form                                                                                  
     */                                                                                                                             
    public function grading_batch_operation($action, $users) {                                                                      
        return '';                                                                                                                  
    }              

These two callbacks allow adding entries to the batch grading operations list (where you select multiple users in the table and choose e.g. "Lock submissions" for every user). The action is passed to "grading_batch_operation" so that multiple entries can be supported by a plugin.

Other features

Add calendar events

Moodle 3.1


From Moodle 3.1 onwards, feedback plugins can add events to the Moodle calendar without side effects. These will be hidden and deleted in line with the assignment module. For example:

        // Add release date to calendar
        $calendarevent = new stdClass();
        $calendarevent->name         = get_string('calendareventname', 'assignsubmission_something');
        $calendarevent->description  = get_string('calendareventdesc', 'assignsubmission_something');
        $calendarevent->courseid     = $courseid;
        $calendarevent->groupid      = 0;
        $calendarevent->userid       = $userid;
        $calendarevent->modulename   = 'assign';
        $calendarevent->instance     = $instanceid;
        $calendarevent->eventtype    = 'something_release'; // For activity module's events, this can be used to set the alternative text of the event icon. Set it to 'pluginname' unless you have a better string.
        $calendarevent->timestart    = $releasedate;
        $calendarevent->visible      = true;
        $calendarevent->timeduration = 0;

        calendar_event::create($calendarevent);