Note:

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

Moodle Activity Extensions Spec

From MoodleDocs
Revision as of 04:15, 14 January 2013 by James McLean (talk | contribs)

Moodle Activity Extensions

This spec should be considered a DRAFT

Goals

To provide a common point for creating and managing due date extensions to all Moodle Activity due dates.

Background

For Moodle 1.9, UniSA developed in house a number of advanced modifications to our Moodle instance. One of these modifications was our custom Extensions module. The 1.9 version only worked with mod_assignment and mod_quiz activities (Quiz was implemented after the initial release), however it has been very well received by our school staff and academics. In the year 2012 there was over 200,000 individual extension requests alone. Some 190,000 of these were approved. It's been very very stable with only a couple of minor issues discovered along the way.

We are transitioning to Moodle 2.X later this year, so starting in December 2012 myself and another developer here began the task of porting our existing modifications into 2.x. We believe the Extensions module is something that many people in the community would get benefits from, and as our porting effort continues, we would like to provide our code to the community. In addition to providing the code to the community, we are willing to be responsible for, and to perform the required modifications to the Moodle Core so that this code works seamlessly.

Features

Our current 1.9 implementation supports the following features, which will also be implemented in 2.x:

  • Individual extensions
    • Students may request an extension for activities that allow it via Moodle (Existing 1.9 functionality)
    • Students can supply up to 3 documents as proof of requirement for extension (doctors certificates etc) (Existing 1.9 functionality)
    • Students can submit only one pending request per activity at a time. (Existing 1.9 functionality)
    • Students can submit their pending request to a specific staff member, or to any staff member that has the correct capability (New in 2.x version, previous single staff member only)
      • Once that request is approved, they may request a further extension on that same activity (configurable) (Existing 1.9 functionality)
    • When a student has an approved request in the system, while they have no other pending requests they may request another extension (configurable) (Existing 1.9 functionality)
    • Staff may apply an approved extension on a student behalf, which allows for situations where a student cannot access Moodle to perform the request (configurable) (Existing 1.9 functionality)
    • Web Services will allow external systems to interrogate extensions for a student or activity (New in 2.x version)
    • History of extension request is visible on the request edit page (New 2.x version), in addition to the standard Moodle logs (Existing 1.9 functionality).
    • Extensions can have a range of status, Pending, Approved, Denied, Withdrawn, Revoked or More Info Required. (Existing 1.9 functionality)
    • Extensions integrates with Messaging 2.0 (New in 2.x version, simple email in 1.9 version).
    • Extensions can be 'quick approved' by a staff member selecting a checkbox along side each, and then selecting 'Approve' (Existing 1.9 functionality).
  • Global extensions
    • Staff may apply a global extension to an activity, optionally they can select groups to apply the extension to individually or to the entire Course (Existing 1.9 functionality).
    • Staff may apply seperate global extensions to seperate groups on the same Activity (Existing 1.9 functionality).
    • Currently no method for requesting a Global extension via Moodle for students.
  • Gradebook integration
    • Grades in Gradebook are flagged when an extension has been applied (Existing 1.9 functionality).

(More detail available in forum thread: https://moodle.org/mod/forum/discuss.php?d=216784 )

Implementation

UI

We have begun the process of porting our existing work to 2.x. As this module will be used by The University of South Australia, it was initially designed and developed to suit our needs. As we are developing the module for 2.x, this does need to remain our primary focus, however we are definitely committed to ensuring the module will be usable for the community at large. We feel that where the module is at provides good functionality for many cases in the community and is a solid starting point for further development as needs evolve.

In the short term, we have been developing this module under /local/extensions/ however this causes some issues, such as creating links in the Course menu to cite one example.

I propose that the bulk of the extensions UI be located in a directory named 'extensions' in the 'course' directory of the Moodle source code tree (/course/extensions/). This will allow a consistent and simple path for accessing and maintaining extension requests for staff, and provide a place for students to view and request extensions for a specific course. There may exist the requirement for a 'global' view of pending requests for staff, the code is being developed with this in mind, and a suitable location for this view may need to be determined in the future if this is deemed a requirement by the community.

It is important to mention that at this moment, this extensions module is only considering due date extensions. Quiz attempt time extensions are not yet handled, but this is certainly something that can be implemented at a later date. Quiz module currently has the functionality required, so this will suffice in the short term.

Code

The implementation of the code for this module is expected to be relatively simple. Tim Hunt has provided me with some of his suggestions on how this should be implemented in the Core, which I have looked at how the Quiz module implements it's overrides for further ideas on how this may work for all activities in Moodle.

Each 'extension' weather that be individual or global, will be tied specifically to a Course Module ID, thus ensuring it remains closely integrated with Moodle and the specific activity it relates to.

I propose that the main code for the extensions logic is (in line with my other suggestion above) in /course/extensions/lib.php and as such this code will be included with a require_once() statement in /lib/setup.php. This will allow for configuration at the site level of whether Extensions is enabled or not before including the code.

Inline with Tim's suggestions, and how the Quiz overrides is implemented, I propose we implement a function in Extensions which is called by every activity to check if there is an extension for a specific course module id.

The ideal situation for this module is that each activity that implements some kind of submission deadline, can have that submission deadline extended for a student or groups of students as needed. This will require either modifying each and every activity which needs access to it. However, the function get_coursemodule_from_id() (/lib/datalib.php) is a core function that is called by all activities that support deadlines, and if this can be implemented in such a way that there is minimal overhead from the change, this could be the ideal location for injection of the modified dates.

Suggestion 1: Modifications are made to the get_coursemodule_from_id() function: function get_coursemodule_from_id($modulename, $cmid, $courseid=0, $sectionnum=false, $strictness=IGNORE_MISSING) {

   global $DB;
   // Modification
   if(extensions::exists($cmid)) {
       $extension = true;
       global $USER;
   }
   // End Modification
   $params = array('cmid'=>$cmid);
   if (!$modulename) {
       if (!$modulename = $DB->get_field_sql("SELECT md.name
                                                FROM {modules} md
                                                JOIN {course_modules} cm ON cm.module = md.id
                                               WHERE cm.id = :cmid", $params, $strictness)) {
           return false;
       }
   }
   $params['modulename'] = $modulename;
   $courseselect = "";
   $sectionfield = "";
   $sectionjoin  = "";
   if ($courseid) {
       $courseselect = "AND cm.course = :courseid";
       $params['courseid'] = $courseid;
   }
   if ($sectionnum) {
       $sectionfield = ", cw.section AS sectionnum";
       $sectionjoin  = "LEFT JOIN {course_sections} cw ON cw.id = cm.section";
   }
   $sql = "SELECT cm.*, m.name, md.name AS modname $sectionfield
             FROM {course_modules} cm
                  JOIN {modules} md ON md.id = cm.module
                  JOIN {".$modulename."} m ON m.id = cm.instance
                  $sectionjoin
            WHERE cm.id = :cmid AND md.name = :modulename
                  $courseselect";
   // Modification
   $cmdetail = $DB->get_record_sql($sql, $params, $strictness);
   if(isset($extension) && $extension == true) {
       $cmdetail = extension::update_duedate($cmid, $USER->id, $cmdetail);
   }
   return $cmdetail;
   // End Modification

}

Suggestion 2: Modifications are made to multiple places, in every module that supports deadlines (Excerpt from mod_assign/view.php shown below. Lines 30 - 36 only, before modification.) $id = required_param('id', PARAM_INT); // Course Module ID $url = new moodle_url('/mod/assign/view.php', array('id' => $id)); // Base URL

// get the request parameters $cm = get_coursemodule_from_id('assign', $id, 0, false, MUST_EXIST);

// Begin Modification if(extensions::exists($cm->id, $USER->id)) {

   $cm = extensions::get_duedate($cm->id, $USER->id, $cm, 'duedate');

} // End Modification

$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);

I can see there may be issues around implementing as outlined in Suggestion 1; and thus Suggestion 2 may be more desired even if it will take longer to implement, it will mean that modules can be implemented and tested over time rather than a 'big bang' approach.

Code for extensions::get_duedate() in Suggestion 2 would look something like (untested, error checking omitted): class extensions {

   public static function exists($cmid, $userid) {
       global $DB;
       return $DB->record_exists('extensions', array('cmid' => $cmid, 'student_id' => $userid)));
   }
   public static function get_duedate($cmid = null, $userid = null, $cmdetail = null, $update_field = null) {
       
       global $DB;
       // insert error checking here.
       if($ext = extensions::get_extension($cmid, $userid)) {
           $cmdetail->{$update_field} = $ext->due_date;
       }
       return $cmdetail;
   }

}