# This patch file was generated by NetBeans IDE
# This patch can be applied using context Tools: Apply Diff Patch action on respective folder.
# It uses platform neutral UTF-8 encoding.
# Above lines and this line are ignored by the patching process.
Index: moodle/admin/cron.php
--- moodle/admin/cron.php Base (1.166)
+++ moodle/admin/cron.php Locally Modified (Based On 1.166)
@@ -245,6 +245,16 @@
     events_cron();
     mtrace('done.');
 
+
+    if ($CFG->enablecompletion) {
+        // Completion cron
+        mtrace('Starting the completion cron...');
+        require_once($CFG->libdir . '/completion/cron.php');
+        completion_cron();
+        mtrace('done');
+    }
+
+
     if ($CFG->enableportfolios) {
         // Portfolio cron
         mtrace('Starting the portfolio cron...');
Index: moodle/backup/backuplib.php
--- moodle/backup/backuplib.php Base (1.243)
+++ moodle/backup/backuplib.php Locally Modified (Based On 1.243)
@@ -715,6 +715,7 @@
             fwrite ($bf,full_tag("ENROLENDDATE",3,false,$course->enrolenddate));
             fwrite ($bf,full_tag("ENROLPERIOD",3,false,$course->enrolperiod));
             fwrite ($bf,full_tag("ENABLECOMPLETION",3,false,$course->enablecompletion));
+            fwrite ($bf,full_tag("COMPLETIONNOTIFY",3,false,$course->completionnotify));
 
             // Write role assigns, overrides, etc.
             write_per_context_data($bf, $preferences, $context, 3);
@@ -1249,7 +1250,7 @@
                    backup_userdata_selected($preferences,$moduletype,$course_module->instance)) {
                    fwrite ($bf,start_tag("COMPLETIONDATA",6,true));
 
-                   // Get all completion records for this module and loop
+                   // Get all activity completion records for this module and loop
                    $data=$DB->get_records('course_modules_completion',array('coursemoduleid'=>$course_module->id));
                    $data=$data ? $data : array();
                    foreach($data as $completion) {
@@ -1563,6 +1564,180 @@
         return $status;
     }
 
+    //Backup course completion info
+    function backup_course_completion_info($bf, $preferences) {
+        global $CFG, $DB;
+        require_once($CFG->libdir.'/completionlib.php');
+
+        $status = true;
+
+        //Completion header
+        fwrite ($bf,start_tag("COMPLETION",2,true));
+
+        $status = backup_course_completion_aggregation_methods($bf, $preferences);
+        $status = backup_course_completion_criteria($bf, $preferences);
+        $status = backup_course_completion_notify($bf, $preferences);
+        $status = backup_course_completion_criteria_completions($bf, $preferences);
+        $status = backup_course_completion_completions($bf, $preferences);
+
+        //Completion footer
+        $status = fwrite ($bf,end_tag("COMPLETION",2,true));
+        return $status;
+    }
+
+    // Backup course completion aggregation methods
+    function backup_course_completion_aggregation_methods($bf, $preferences) {
+        global $DB;
+        $status = true;
+
+        $methods = $DB->get_records('course_completion_aggr_methd', array('course' => $preferences->backup_course));
+
+        if ($methods) {
+            //Begin completion_aggregation_methods tag
+            fwrite ($bf,start_tag("COMPLETION_AGGREGATION_METHODS",3,true));
+
+            //Iterate for each aggregation method
+            foreach ($methods as $method) {
+
+                //Begin completion_aggregation_method
+                fwrite ($bf,start_tag("COMPLETION_AGGREGATION_METHOD",4,true));
+
+                //Output individual fields
+                fwrite ($bf,full_tag("ID",5,false,$method->id));
+                fwrite ($bf,full_tag("COURSE",5,false,$method->course));
+                fwrite ($bf,full_tag("CRITERIATYPE",5,false,$method->criteriatype));
+                fwrite ($bf,full_tag("METHOD",5,false,$method->method));
+                fwrite ($bf,full_tag("VALUE",5,false,$method->value));
+
+                //End completion_aggregation_method
+                fwrite ($bf,end_tag("COMPLETION_AGGREGATION_METHOD",4,true));
+            }
+
+            //End completion_aggregation_methods tag
+            $status = fwrite ($bf,end_tag("COMPLETION_AGGREGATION_METHODS",3,true));
+        } 
+
+        return $status;
+    }
+
+    // Backup course completion criteria
+    function backup_course_completion_criteria($bf, $preferences) {
+        global $DB;
+        $status = true;
+
+        $criteria = $DB->get_records('course_completion_criteria', array('course' => $preferences->backup_course));
+
+        if ($criteria) {
+            //Begin completion_criteria tag
+            fwrite ($bf,start_tag("COMPLETION_CRITERIA",3,true));
+
+            //Iterate for each criteria
+            foreach ($criteria as $criterion) {
+
+                //Begin completion_criterion
+                fwrite ($bf,start_tag("COMPLETION_CRITERIA",4,true));
+
+                //Output individual fields
+                fwrite ($bf,full_tag("ID",5,false,$criterion->id));
+                fwrite ($bf,full_tag("COURSE",5,false,$criterion->course));
+                fwrite ($bf,full_tag("ITEMNAME",5,false,$criterion->itemname));
+                fwrite ($bf,full_tag("CRITERIATYPE",5,false,$criterion->criteriatype));
+                fwrite ($bf,full_tag("MODULE",5,false,$criterion->module));
+                fwrite ($bf,full_tag("MODULEINSTANCE",5,false,$criterion->moduleinstance));
+                fwrite ($bf,full_tag("ENROLPERIOD",5,false,$criterion->enrolperiod));
+                fwrite ($bf,full_tag("DATE",5,false,$criterion->date));
+                fwrite ($bf,full_tag("GRADEPASS",5,false,$criterion->gradepass));
+                fwrite ($bf,full_tag("ROLE",5,false,$criterion->role));
+                fwrite ($bf,full_tag("LOCK",5,false,$criterion->lock));
+
+                //End completion_criterion
+                fwrite ($bf,end_tag("COMPLETION_CRITERIA",4,true));
+            }
+
+            //End completion_criteria tag
+            $status = fwrite ($bf,end_tag("COMPLETION_CRITERIA",3,true));
+        } 
+
+        return $status;
+    }
+
+    // Backup course completion notify
+    function backup_course_completion_notify($bf, $preferences) {
+        global $DB;
+        $status = true;
+
+        $notifications = $DB->get_records('course_completion_notify', array('course' => $preferences->backup_course));
+
+        if ($notifications) {
+            //Begin completion_notify tag
+            fwrite ($bf,start_tag("COMPLETION_NOTIFY",3,true));
+
+            //Iterate for each notification
+            foreach ($notifications as $notification) {
+
+                //Begin completion_notification
+                fwrite ($bf,start_tag("COMPLETION_NOTIFICATION",4,true));
+
+                //Output individual fields
+                fwrite ($bf,full_tag("ID",5,false,$notification->id));
+                fwrite ($bf,full_tag("COURSE",5,false,$notification->course));
+                fwrite ($bf,full_tag("ROLE",5,false,$notification->role));
+                fwrite ($bf,full_tag("MESSAGE",5,false,$notification->message));
+                fwrite ($bf,full_tag("TIMESENT",5,false,$notification->timesent));
+
+                //End completion_notification
+                fwrite ($bf,end_tag("COMPLETION_NOTIFICATION",4,true));
+            }
+
+            //End completion_notify tag
+            $status = fwrite ($bf,end_tag("COMPLETION_NOTIFY",3,true));
+        } 
+
+        return $status;
+    }
+
+    //Backup course completion criteria completions
+    function backup_course_completion_criteria_completions($bf, $preferences) {
+        global $CFG, $DB;
+
+        $status = true;
+
+        // Grade all criteria completions in this course
+        if ($completions = $DB->get_records('course_completion_crit_compl', array('course' => $preferences->backup_course))) {
+
+            fwrite ($bf,start_tag("COMPLETION_CRITERIA_COMPLETIONS",5,true));
+            foreach ($grades as $grade) {
+            /// Grades are only sent to backup if the user is one target user
+                if (backup_getid($preferences->backup_unique_code, 'user', $grade->userid)) {
+                    fwrite ($bf,start_tag("GRADE",6,true));
+                    fwrite ($bf,full_tag("ID",7,false,$grade->id));
+                    fwrite ($bf,full_tag("USERID",7,false,$grade->userid));
+                    fwrite ($bf,full_tag("RAWGRADE",7,false,$grade->rawgrade));
+                    fwrite ($bf,full_tag("RAWGRADEMAX",7,false,$grade->rawgrademax));
+                    fwrite ($bf,full_tag("RAWGRADEMIN",7,false,$grade->rawgrademin));
+                    fwrite ($bf,full_tag("RAWSCALEID",7,false,$grade->rawscaleid));
+                    fwrite ($bf,full_tag("USERMODIFIED",7,false,$grade->usermodified));
+                    fwrite ($bf,full_tag("FINALGRADE",7,false,$grade->finalgrade));
+                    fwrite ($bf,full_tag("HIDDEN",7,false,$grade->hidden));
+                    fwrite ($bf,full_tag("LOCKED",7,false,$grade->locked));
+                    fwrite ($bf,full_tag("LOCKTIME",7,false,$grade->locktime));
+                    fwrite ($bf,full_tag("EXPORTED",7,false,$grade->exported));
+                    fwrite ($bf,full_tag("OVERRIDDEN",7,false,$grade->overridden));
+                    fwrite ($bf,full_tag("EXCLUDED",7,false,$grade->excluded));
+                    fwrite ($bf,full_tag("FEEDBACK",7,false,$grade->feedback));
+                    fwrite ($bf,full_tag("FEEDBACKFORMAT",7,false,$grade->feedbackformat));
+                    fwrite ($bf,full_tag("INFORMATION",7,false,$grade->information));
+                    fwrite ($bf,full_tag("INFORMATIONFORMAT",7,false,$grade->informationformat));
+                    fwrite ($bf,full_tag("TIMECREATED",7,false,$grade->timecreated));
+                    fwrite ($bf,full_tag("TIMEMODIFIED",7,false,$grade->timemodified));
+                    fwrite ($bf,end_tag("GRADE",6,true));
+                }
+            }
+            $status = fwrite ($bf,end_tag("GRADE_GRADES",5,true));
+        }
+        return $status;
+    }
+
     //Backup gradebook info
     function backup_gradebook_info($bf, $preferences) {
         global $CFG, $DB;
Index: moodle/blocks/completionstatus/block_completionstatus.php
--- moodle/blocks/completionstatus/block_completionstatus.php No Base Revision
+++ moodle/blocks/completionstatus/block_completionstatus.php Locally New
@@ -0,0 +1,163 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Block for displayed logged in user's course completion status
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once($CFG->libdir.'/completionlib.php');
+
+/**
+ * Course completion status
+ * Displays overall, and individual criteria status for logged in user
+ */
+class block_completionstatus extends block_base {
+
+    public function init() {
+        $this->title   = get_string('completionstatus', 'block_completionstatus');
+        $this->version = 2009072800;
+    }
+
+    public function get_content() {
+        global $USER, $CFG;
+
+        // If content is cached
+        if ($this->content !== NULL) {
+            return $this->content;
+        }
+
+        // Create empty content
+        $this->content = new stdClass;
+
+        // Don't display if completion isn't enabled!
+        if (!$this->page->course->enablecompletion) {
+            return $this->content;
+        }
+
+        // Load criteria to display
+        $info = new completion_info($this->page->course);
+        $completions = $info->get_completions($USER->id);
+
+        // Check if this course has any criteria
+        if (empty($completions)) {
+            return $this->content;
+        }
+
+        // Check this user is enroled
+        $users = $info->internal_get_tracked_users(true);
+        if (!in_array($USER->id, array_keys($users))) {
+            $this->content->text = get_string('notenroled', 'completion');
+            return $this->content;
+        }
+
+        // Generate markup for criteria statuses
+        $shtml = '<tr><td><b>'.get_string('requiredcriteria', 'completion').'</b></td><td style="text-align: right"><b>'.get_string('status').'</b></td></tr>';
+
+        // For aggregating activity completion
+        $activities = array();
+        $activities_complete = 0;
+
+        // Flag to set if current completion data is inconsistent with
+        // what is stored in the database
+        $pending_update = false;
+
+        // Loop through course criteria
+        foreach ($completions as $completion) {
+
+            $criteria = $completion->get_criteria();
+            $complete = $completion->is_complete();
+
+            if (!$pending_update && $criteria->is_pending($completion)) {
+                $pending_update = true;
+            }
+
+            // Activities are a special case, so cache them and leave them till last
+            if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
+                $activities[$criteria->moduleinstance] = $complete;
+
+                if ($complete) {
+                    $activities_complete++;
+                }
+
+                continue;
+            }
+
+            $shtml .= '<tr><td>';
+            $shtml .= $criteria->get_title();
+            $shtml .= '</td><td style="text-align: right">';
+            $shtml .= $completion->get_status();
+            $shtml .= '</td></tr>';
+        }
+
+        // Aggregate activities
+        if (!empty($activities)) {
+
+            $shtml .= '<tr><td>';
+            $shtml .= 'Activities complete';
+            $shtml .= '</td><td style="text-align: right">';
+            $shtml .= $activities_complete.' of '.count($activities);
+            $shtml .= '</td></tr>';
+        }
+
+        // Display completion status
+        $this->content->text  = '<table width="100%" style="font-size: 90%;"><tbody>';
+        $this->content->text .= '<tr><td colspan="2"><b>'.get_string('status').':</b> ';
+
+        // Is course complete?
+        $coursecomplete = $info->is_course_complete($USER->id);
+
+        // Has this user completed any criteria?
+        $criteriacomplete = $info->count_course_user_data($USER->id);
+
+        if ($pending_update) {
+            $this->content->text .= '<i>'.get_string('pending', 'completion').'</i>';
+        } else if ($coursecomplete) {
+            $this->content->text .= get_string('complete');
+        } else if (!$criteriacomplete) {
+            $this->content->text .= '<i>'.get_string('notyetstarted', 'completion').'</i>';
+        } else {
+            $this->content->text .= '<i>'.get_string('inprogress','completion').'</i>';
+        }
+
+        $this->content->text .= '</td></tr>';
+        $this->content->text .= '<tr><td colspan="2">';
+
+        // Get overall aggregation method
+        $overall = $info->get_aggregation_method();
+
+        if ($overall == COMPLETION_AGGREGATION_ALL) {
+            $this->content->text .= get_string('criteriarequiredall', 'completion');
+        } else {
+            $this->content->text .= get_string('criteriarequiredany', 'completion');
+        }
+
+        $this->content->text .= ':</td></tr>'.$shtml.'</tbody></table>';
+
+        // Display link to report
+        $context = get_context_instance(CONTEXT_COURSE, $this->page->course->id);
+        if (has_capability('coursereport/progress:view',$context)) {
+            $this->content->footer = '<br><a href="'.$CFG->wwwroot.'/course/report/progress/?course='.$this->page->course->id.'">More info</a>';
+        }
+
+        return $this->content;
+    }
+}
Index: moodle/blocks/selfcompletion/block_selfcompletion.php
--- moodle/blocks/selfcompletion/block_selfcompletion.php No Base Revision
+++ moodle/blocks/selfcompletion/block_selfcompletion.php Locally New
@@ -0,0 +1,93 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+require_once($CFG->libdir.'/completionlib.php');
+
+/**
+ * Self course completion marking
+ * Let's a user manually complete a course
+ *
+ * Will only display if the course has completion enabled,
+ * there is a self completion criteria, and the logged in user is yet
+ * to complete the course.
+ */
+class block_selfcompletion extends block_base {
+
+    public function init() {
+        $this->title   = get_string('selfcompletion', 'block_selfcompletion');
+        $this->version = 2009072800;
+    }
+
+    public function get_content() {
+        global $USER;
+
+        // If content is cached
+        if ($this->content !== NULL) {
+          return $this->content;
+        }
+
+        global $CFG;
+
+        // Create empty content
+        $this->content = new stdClass;
+
+        // Don't display if completion isn't enabled!
+        if (!$this->page->course->enablecompletion) {
+            return $this->content;
+        }
+
+        // Get course completion data
+        $info = new completion_info($this->page->course);
+        $completion = $info->get_completion($USER->id, COMPLETION_CRITERIA_TYPE_SELF);
+
+        // Is course complete?
+        if ($info->is_course_complete($USER->id)) {
+            return $this->content;
+        }
+
+        // Check if self completion is one of this course's criteria
+        if (empty($completion)) {
+            return $this->content;
+        }
+
+        // Check this user is enroled
+        $users = $info->internal_get_tracked_users(true);
+        if (!in_array($USER->id, array_keys($users))) {
+            $this->content->text = get_string('notenroled', 'completion');
+            return $this->content;
+        }
+
+        // Check if the user has already marked themselves as complete
+        if ($completion->is_complete()) {
+            return $this->content;
+        } else {
+            $this->content->text = '';
+            $this->content->footer = '<br /><a href="'.$CFG->wwwroot.'/course/togglecompletion.php?course='.$this->page->course->id.'">'.
+                                       get_string('completecourse', 'block_selfcompletion').'</a>...';
+        }
+
+        return $this->content;
+    }
+}
Index: moodle/course/completion.php
--- moodle/course/completion.php No Base Revision
+++ moodle/course/completion.php Locally New
@@ -0,0 +1,139 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+// Edit course completion settings
+
+require_once('../config.php');
+require_once('lib.php');
+require_once($CFG->libdir.'/completionlib.php');
+require_once($CFG->libdir.'/completion/completion_criteria_self.php');
+require_once($CFG->libdir.'/completion/completion_criteria_date.php');
+require_once($CFG->libdir.'/completion/completion_criteria_unenrol.php');
+require_once($CFG->libdir.'/completion/completion_criteria_activity.php');
+require_once($CFG->libdir.'/completion/completion_criteria_duration.php');
+require_once($CFG->libdir.'/completion/completion_criteria_grade.php');
+require_once($CFG->libdir.'/completion/completion_criteria_role.php');
+require_once('completion_form.php');
+
+$id = required_param('id', PARAM_INT);       // course id
+
+/// basic access control checks
+if ($id) { // editing course
+
+    if($id == SITEID){
+        // don't allow editing of  'site course' using this from
+        print_error('cannoteditsiteform');
+    }
+
+    if (!$course = $DB->get_record('course', array('id'=>$id))) {
+        print_error('invalidcourseid');
+    }
+    require_login($course->id);
+    require_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $course->id));
+
+} else {
+    require_login();
+    print_error('needcourseid');
+}
+
+/// Set up the page
+$streditcompletionsettings = get_string("editcoursecompletionsettings", 'completion');
+$PAGE->set_course($course);
+$PAGE->set_url('course/completion.php', array('id' => $course->id));
+$PAGE->navbar->add($streditcompletionsettings);
+$PAGE->set_title($course->shortname);
+$PAGE->set_heading($course->fullname);
+
+/// first create the form
+$form = new course_completion_form('completion.php?id='.$id, compact('course'));
+
+// now override defaults if course already exists
+if ($form->is_cancelled()){
+    redirect($CFG->wwwroot.'/course/view.php?id='.$course->id);
+
+} else if ($data = $form->get_data()) {
+
+    $completion = new completion_info($course);
+
+/// process criteria unlocking if requested
+    if (!empty($data->settingsunlock)) {
+
+        $completion->delete_course_completion_data();
+
+        // Return to form (now unlocked)
+        redirect($CFG->wwwroot."/course/completion.php?id=$course->id");
+    }
+
+/// process data if submitted
+    // Delete old criteria
+    $completion->clear_criteria();
+
+    // Loop through each criteria type and run update_config
+    global $COMPLETION_CRITERIA_TYPES;
+    foreach ($COMPLETION_CRITERIA_TYPES as $type) {
+        $class = 'completion_criteria_'.$type;
+        $criterion = new $class();
+        $criterion->update_config($data);
+    }
+
+    // Handle aggregation methods
+    // Overall aggregation
+    $aggregation = new completion_aggregation();
+    $aggregation->course = $data->id;
+    $aggregation->criteriatype = null;
+    $aggregation->setMethod($data->overall_aggregation);
+    $aggregation->insert();
+
+    // Activity aggregation
+    if (empty($data->activity_aggregation)) {
+        $data->activity_aggregation = 0;
+    }
+
+    $aggregation = new completion_aggregation();
+    $aggregation->course = $data->id;
+    $aggregation->criteriatype = COMPLETION_CRITERIA_TYPE_ACTIVITY;
+    $aggregation->setMethod($data->activity_aggregation);
+    $aggregation->insert();
+
+    // Role aggregation
+    $aggregation = new completion_aggregation();
+    $aggregation->course = $data->id;
+    $aggregation->criteriatype = COMPLETION_CRITERIA_TYPE_ROLE;
+    $aggregation->setMethod($data->role_aggregation);
+    $aggregation->insert();
+
+    redirect($CFG->wwwroot."/course/view.php?id=$course->id");
+}
+
+
+/// Print the form
+
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($streditcompletionsettings);
+
+$form->display();
+
+echo $OUTPUT->footer();
Index: moodle/course/completion_form.php
--- moodle/course/completion_form.php No Base Revision
+++ moodle/course/completion_form.php Locally New
@@ -0,0 +1,157 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+require_once($CFG->libdir.'/formslib.php');
+
+class course_completion_form extends moodleform {
+
+    function definition() {
+        global $USER, $CFG, $DB;
+
+        $courseconfig = get_config('moodlecourse');
+        $mform    =& $this->_form;
+
+        $course   = $this->_customdata['course'];
+        $completion = new completion_info($course);
+
+        $params = array(
+            'course'  => $course->id
+        );
+
+
+/// form definition
+//--------------------------------------------------------------------------------
+
+        // Check if there is existing criteria completions
+        if ($completion->is_course_locked()) {
+            $mform->addElement('header', '', get_string('completionsettingslocked', 'completion'));
+            $mform->addElement('static', '', '', get_string('err_settingslocked', 'completion'));
+            $mform->addElement('submit', 'settingsunlock', get_string('unlockcompletiondelete', 'completion'));
+        }
+
+        // Get array of all available aggregation methods
+        $aggregation_methods = $completion->get_aggregation_methods();
+
+        // Overall criteria aggregation
+        $mform->addElement('header', 'overallcriteria', get_string('overallcriteriaaggregation', 'completion'));
+        $mform->addElement('select', 'overall_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+        $mform->setDefault('overall_aggregation', $completion->get_aggregation_method());
+
+        // Manual self completion
+        $mform->addElement('header', 'manualselfcompletion', get_string('manualselfcompletion', 'completion'));
+        $criteria = new completion_criteria_self($params);
+        $criteria->config_form_display($mform);
+
+        // Role completion criteria
+        $mform->addElement('header', 'roles', get_string('manualcompletionby', 'completion'));
+
+        $roles = get_roles_with_capability('moodle/course:markcomplete', CAP_ALLOW, get_context_instance(CONTEXT_COURSE, $course->id));
+
+        if (!empty($roles)) {
+            $mform->addElement('select', 'role_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+            $mform->setDefault('role_aggregation', $completion->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE));
+
+            foreach ($roles as $role) {
+                $params_a = array('role' => $role->id);
+                $criteria = new completion_criteria_role(array_merge($params, $params_a));
+                $criteria->config_form_display($mform, $role);
+            }
+        } else {
+            $mform->addElement('static', 'noroles', '', get_string('err_noroles', 'completion'));
+        }
+
+        // Activity completion criteria
+        $mform->addElement('header', 'activitiescompleted', get_string('activitiescompleted', 'completion'));
+
+        $activities = $completion->get_activities();
+        if (!empty($activities)) {
+            if (count($activities) > 1) {
+                $mform->addElement('select', 'activity_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+                $mform->setDefault('activity_aggregation', $completion->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY));
+            }
+
+            foreach ($activities as $activity) {
+                $params_a = array('moduleinstance' => $activity->id);
+                $criteria = new completion_criteria_activity(array_merge($params, $params_a));
+                $criteria->config_form_display($mform, $activity);
+            }
+        } else {
+            $mform->addElement('static', 'noactivities', '', get_string('err_noactivities', 'completion'));
+        }
+
+        // Completion on date
+        $mform->addElement('header', 'date', get_string('date'));
+        $criteria = new completion_criteria_date($params);
+        $criteria->config_form_display($mform);
+
+        // Completion after enrolment duration
+        $mform->addElement('header', 'duration', get_string('durationafterenrolment', 'completion'));
+        $criteria = new completion_criteria_duration($params);
+        $criteria->config_form_display($mform);
+
+        // Completion on course grade
+        $mform->addElement('header', 'grade', get_string('grade'));
+
+        $course_grade = $DB->get_field('grade_items', 'gradepass', array('courseid' => $course->id, 'itemtype' => 'course'));
+        $criteria = new completion_criteria_grade($params);
+
+        // Only display criteria enable if the course has a pass grade, or criteria already is setup
+        if ($course_grade > 0 || $criteria->id) {
+            $criteria->config_form_display($mform, $course_grade);
+        } else {
+            $mform->addElement('static', 'nograde', '', get_string('err_nograde', 'completion'));
+        }
+
+        // Completion on unenrolment
+        $mform->addElement('header', 'unenrolment', get_string('unenrolment', 'completion'));
+        $criteria = new completion_criteria_unenrol($params);
+        $criteria->config_form_display($mform);
+
+
+//--------------------------------------------------------------------------------
+        $this->add_action_buttons();
+//--------------------------------------------------------------------------------
+        $mform->addElement('hidden', 'id', $course->id);
+        $mform->setType('id', PARAM_INT);
+
+        // If the criteria are locked, freeze values and submit button
+        if ($completion->is_course_locked()) {
+            $except = array('settingsunlock');
+            $mform->hardFreezeAllVisibleExcept($except);
+            $mform->addElement('cancel');
+        }
+    }
+
+
+/// perform some extra moodle validation
+    function validation($data, $files) {
+        global $DB, $CFG;
+
+        $errors = parent::validation($data, $files);
+
+        return $errors;
+    }
+}
+?>
Index: moodle/course/report/progress/index.php
--- moodle/course/report/progress/index.php Base (1.28)
+++ moodle/course/report/progress/index.php Locally Modified (Based On 1.28)
@@ -63,17 +63,21 @@
     require_capability('moodle/site:accessallgroups',$context);
 }
 
-// Get data on activities and progress of all users, and give error if we've
-// nothing to display (no users or no activities)
+// Get data on activities, criteria and progress of all users, and give error if we've
+// nothing to display (no users, no activities or no criteria)
 $reportsurl=$CFG->wwwroot.'/course/report.php?id='.$course->id;
 $completion=new completion_info($course);
 $activities=$completion->get_activities();
-if(count($activities)==0) {
+$criteria   = $completion->has_criteria() ? $completion->get_criteria() : false;
+
+if(empty($activities) && empty($criteria)) {
     print_error('err_noactivities','completion',$reportsurl);
 }
 
-$progress=$completion->get_progress_all($firstnamesort,$group,
-    $csv ? 0 :COMPLETION_REPORT_PAGE,$csv ? 0 : $start);
+$progress = $completion->get_progress_all(
+    $firstnamesort, $group,
+    $csv ? 0 : COMPLETION_REPORT_PAGE,
+    $csv ? 0 : $start);
 
 if($csv) {
     header('Content-Disposition: attachment; filename=progress.'.
@@ -141,6 +145,32 @@
     $pagingbar='';
 }
 
+
+// Can we mark users as complete?
+// (if the logged in user has a role defined in the role criteria)
+$allow_marking = false;
+$allow_marking_criteria = null;
+
+if ($criteria) {
+    // Get role criteria
+    $rcriteria = $completion->get_criteria(COMPLETION_CRITERIA_TYPE_ROLE);
+
+    if (!empty($rcriteria)) {
+
+        foreach ($rcriteria as $rcriterion) {
+            $users = get_role_users($rcriterion->role, $context, true);
+
+            // If logged in user has this role, allow marking complete
+            if (in_array($USER->id, array_keys($users))) {
+                $allow_marking = true;
+                $allow_marking_criteria = $rcriterion->id;
+                break;
+            }
+        }
+    }
+}
+
+
 // Okay, let's draw the table of progress info,
 
 // Start of table
@@ -198,7 +228,7 @@
         print '<th scope="col" class="'.$activity->datepassedclass.'">'.
             '<a href="'.$CFG->wwwroot.'/mod/'.$activity->modname.
             '/view.php?id='.$activity->id.'">'.
-            '<img src="'.$OUTPUT->mod_icon_url('icon', $activity->modname).'/icon.gif" alt="'.
+            '<img src="'.$OUTPUT->mod_icon_url('icon', $activity->modname).'" alt="'.
             get_string('modulename',$activity->modname).'" /> <span class="completion-activityname">'.
             format_string($activity->name).'</span></a>';
         if($activity->completionexpected) {
@@ -208,6 +238,37 @@
     }
 }
 
+// Course completion criteria
+if ($criteria) {
+
+    foreach ($criteria as $criterion) {
+
+        // Ignore activity completion criteria (use existing activity cols instead)
+        if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
+            continue;
+        }
+
+        if ($csv) {
+            print $sep.csv_quote(strip_tags($criterion->get_title()));
+        } else {
+            print '<th scope="col">'.format_text($criterion->get_title()).'</th>';
+        }
+    }
+
+    // Overall course completion status
+    if ($csv) {
+        print $sep.csv_quote(strip_tags(get_string('coursecomplete', 'completion')));
+    } else {
+        print '<th scope="col">'.get_string('coursecomplete', 'completion').'</th>';
+    }
+
+    // Mark complete?
+    if (!$csv && $allow_marking) {
+        print '<th scope="col">'.get_string('markcomplete', 'completion').'</th>';
+    }
+}
+
+
 if($csv) {
     print $line;
 } else {
@@ -263,15 +324,101 @@
         $a->activity=strip_tags($activity->name);
         $fulldescribe=get_string('progress-title','completion',$a);
 
+        // Check if this activity has a dependant couse completion criteria
+        $is_criteria = false;
+        if ($criteria) {
+            foreach ($criteria as $criterion) {
+                if ($criterion->criteriatype != COMPLETION_CRITERIA_TYPE_ACTIVITY) {
+                    continue;
+                }
+
+                if ($criterion->moduleinstance === $activity->id) {
+                    $is_criteria = true;
+                    break;
+                }
+            }
+        }
+
         if($csv) {
             print $sep.csv_quote($describe).$sep.csv_quote($date);
         } else {
-            print '<td class="completion-progresscell '.$activity->datepassedclass.'">'.
+            print '<td class="completion-progresscell '.$activity->datepassedclass.'"'.
+                ($is_criteria ? '' : ' style="background-color: #ccc;"').'>'.
                 '<img src="'.$OUTPUT->old_icon_url('i/'.$completionicon).
                 '" alt="'.$describe.'" title="'.$fulldescribe.'" /></td>';
         }
     }
 
+    // Progress for each course completion criteria
+    if ($criteria) {
+        foreach ($criteria as $criterion) {
+
+            // Ignore activity completion criteria
+            if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
+                continue;
+            }
+
+            $criteria_completion = $completion->get_user_completion($user->id, $criterion);
+            $is_complete = $criteria_completion->is_complete();
+
+            $completiontype = $is_complete ? 'y' : 'n';
+            $describe = get_string('completion-alt-auto-'.$completiontype, 'completion');
+
+            $a = new StdClass;
+            $a->state    = $describe;
+            $a->date     = $is_complete ? userdate($criteria_completion->timecompleted) : '';
+            $a->user     = fullname($user);
+            $a->activity = strip_tags($criterion->get_title());
+            $fulldescribe = get_string('progress-title', 'completion', $a);
+
+            if ($csv) {
+                print $sep.csv_quote($describe);
+            } else {
+                print '<td class="completion-progresscell">'.
+                    '<img src="'.$OUTPUT->old_icon_url('i/completion-auto-'.$completiontype).
+                    '" alt="'.$describe.'" title="'.$fulldescribe.'" /></td>';
+            }
+        }
+
+        // Course completion
+        $completiontype =  $completion->is_course_complete($user->id) ? 'y' : 'n';
+        $describe = get_string('completion-alt-auto-'.$completiontype, 'completion');
+
+        $a = new StdClass;
+        $a->state    = $describe;
+        $a->date     = '';
+        $a->user     = fullname($user);
+        $a->activity = strip_tags(get_string('coursecomplete', 'completion'));
+        $fulldescribe = get_string('progress-title', 'completion', $a);
+
+        if ($csv) {
+            print $sep.csv_quote($describe);
+        } else {
+            print '<td class="completion-progresscell">'.
+                '<img src="'.$OUTPUT->old_icon_url('i/completion-auto-'.$completiontype).
+                '" alt="'.$describe.'" title="'.$fulldescribe.'" /></td>';
+        }
+
+        if ($allow_marking) {
+
+            foreach ($criteria as $criterion) {
+                if ($criterion->id == $allow_marking_criteria) {
+
+                    $criteria_completion = $completion->get_user_completion($user->id, $criterion);
+                    $marked_complete = $criteria_completion->is_complete() ? 'y' : 'n';
+                    break;
+                }
+            }
+
+            $describe = get_string('completion-alt-auto-'.$marked_complete.'completion');
+
+            print '<td class="completion-progresscell">'.
+                '<a href="'.$CFG->wwwroot.'/course/togglecompletion.php?user='.$user->id.'&course='.$course->id.'&rolec='.$allow_marking_criteria.'">'.
+                '<img src="'.$OUTPUT->old_icon_url('i/completion-manual-'.$marked_complete).
+                '" alt="'.$describe.'" title="Mark as complete" /></a></td>';
+        }
+    }
+
     if($csv) {
         print $line;
     } else {
Index: moodle/course/togglecompletion.php
--- moodle/course/togglecompletion.php Base (1.3)
+++ moodle/course/togglecompletion.php Locally Modified (Based On 1.3)
@@ -1,12 +1,81 @@
 <?php
-// Toggles the manual completion flag for a particular activity and the current
+// Toggles the manual completion flag for a particular activity or course completion and the current
 // user.
 
 require_once('../config.php');
 require_once($CFG->libdir.'/completionlib.php');
 
 // Parameters
-$cmid=required_param('id',PARAM_INT);
+$cmid = optional_param('id', 0, PARAM_INT);
+$courseid = optional_param('course', 0, PARAM_INT);
+$confirm = optional_param('confirm', 0, PARAM_BOOL);
+
+if (!$cmid && !$courseid) {
+    print_error('invalidarguments');
+}
+
+// Process self completion
+if ($courseid) {
+
+    // Check user is logged in
+    $course = $DB->get_record('course', array('id' => $courseid));
+    require_login($course);
+
+    $completion = new completion_info($course);
+
+    // Check if we are marking a user complete via the completion report
+    $user = optional_param('user', 0, PARAM_INT);
+    $rolec = optional_param('rolec', 0, PARAM_INT);
+
+    if ($user && $rolec) {
+
+        $criteria = completion_criteria::factory((object) array('id'=>$rolec, 'criteriatype'=>COMPLETION_CRITERIA_TYPE_ROLE));
+        $criteria_completions = $completion->get_completions($user, COMPLETION_CRITERIA_TYPE_ROLE);
+
+        foreach ($criteria_completions as $criteria_completion) {
+            if ($criteria_completion->criteriaid == $rolec) {
+                $criteria->complete($criteria_completion);
+                break;
+            }
+        }
+
+        // Return to previous page
+        if (!empty($_SERVER['HTTP_REFERER'])) {
+            redirect($_SERVER['HTTP_REFERER']);
+        } else {
+            redirect('view.php?id='.$course->id);
+        }
+
+    } else {
+
+        // Confirm with user
+        if ($confirm) {
+            $completion = $completion->get_completion($USER->id, COMPLETION_CRITERIA_TYPE_SELF);
+
+            if (!$completion) {
+                print_error('noselfcompletioncriteria', 'completion');
+            }
+
+            // Check if the user has already marked themselves as complete
+            if ($completion->is_complete()) {
+                print_error('useralreadymarkedcomplete', 'completion');
+            }
+
+            $completion->mark_complete();
+
+            redirect($CFG->wwwroot.'/course/view.php?id='.$courseid);
+            return;
+        }
+
+        $strconfirm = get_string('confirmselfcompletion', 'completion');
+        print_header_simple($strconfirm, '', build_navigation(array(array('name' => $strconfirm, 'link' => '', 'type' => 'misc'))));
+        notice_yesno($strconfirm, $CFG->wwwroot.'/course/togglecompletion.php?course='.$courseid.'&confirm=1', $CFG->wwwroot.'/course/view.php?id='.$courseid); 
+        print_simple_box_end();
+        print_footer($course);
+        exit;
+    }
+}
+
 $targetstate=required_param('completionstate',PARAM_INT);
 switch($targetstate) {
     case COMPLETION_COMPLETE:
Index: moodle/lang/en_utf8/block_completionstatus.php
--- moodle/lang/en_utf8/block_completionstatus.php No Base Revision
+++ moodle/lang/en_utf8/block_completionstatus.php Locally New
@@ -0,0 +1,3 @@
+<?php
+
+$string['completionstatus'] = 'Course Completion Status';
Index: moodle/lang/en_utf8/block_selfcompletion.php
--- moodle/lang/en_utf8/block_selfcompletion.php No Base Revision
+++ moodle/lang/en_utf8/block_selfcompletion.php Locally New
@@ -0,0 +1,4 @@
+<?php
+
+$string['selfcompletion'] = 'Self Completion';
+$string['completecourse'] = 'Complete Course';
Index: moodle/lang/en_utf8/completion.php
--- moodle/lang/en_utf8/completion.php Base (1.8)
+++ moodle/lang/en_utf8/completion.php Locally Modified (Based On 1.8)
@@ -1,5 +1,9 @@
 <?php
+$string['activitiescompleted']='Activities completed';
 $string['activitycompletion']='Activity completion';
+$string['afterspecifieddate']='After specified date';
+$string['aggregationmethod']='Aggregation method';
+$string['any']='Any';
 $string['badautocompletion']='When you select automatic completion, you must also enable at least one requirement (below).';
 $string['completedunlocked']='Completion options unlocked';
 $string['completedunlockedtext']='When you save changes, completion state for all users will be erased. If you change your mind about this, do not save the form.';
@@ -19,33 +23,62 @@
 $string['completion_automatic']='Show activity as complete when conditions are met';
 $string['completion_manual']='Users can manually mark the activity as completed';
 $string['completion_none']='Do not indicate activity completion';
-$string['completionenabled']='Enabled, control via activity settings';
+$string['completionenabled']='Enabled, control via completion and activity settings';
 $string['completionexpected']='Expect completed on';
 $string['completiondisabled']='Disabled, not shown in activity settings';
+$string['completionmenuitem']='Completion';
 $string['completionreport']='Completion progress report';
+$string['completionsettingslocked']='Completion settings locked';
 $string['completionusegrade']='Require grade';
 $string['completionusegrade_text']='User must receive a grade';
 $string['completionview']='Require view';
 $string['completionview_text']='User must view activity';
 $string['configenablecompletion'] = 'When enabled, this lets you turn on completion tracking (progress) features at course level.';
 $string['configprogresstrackedroles'] = 'Roles that are displayed in the progress-tracking screen. (Usually includes just students and equivalent roles.)';
+$string['confirmselfcompletion']='Confirm self completion';
+$string['coursecomplete']='Course Complete';
+$string['criteria']='Criteria';
+$string['criteriarequiredall']='All criteria below are required';
+$string['criteriarequiredany']='Any criteria below are required';
 $string['csvdownload']='Download in spreadsheet format (UTF-8 .csv)';
+$string['editcoursecompletionsettings']='Edit Course Completion Settings';
 $string['excelcsvdownload']='Download in Excel-compatible format (.csv)';
 $string['enablecompletion'] = 'Enable completion tracking';
+$string['enrolmentduration']='Days left';
 $string['err_noactivities']='Completion information is not enabled for any activity, so none can be displayed. You can enable completion information by editing the settings for an activity.';
+$string['err_nograde']='A course pass grade has not been set for this course. To enable this criteria type you must create a pass grade for this course.';
+$string['err_noroles']='There are no roles with the capability \'moodle/course:markcomplete\' in this course. You can enable this criteria type by adding this capability to role(s).';
+$string['err_settingslocked']='One or more users have already completed a criteria so the settings have been locked. Unlocking the completion criteria settings will delete any existing user data and may cause confusion.';
 $string['err_nousers']='There are no users on this course or group for whom completion information is displayed. (By default, completion information is displayed only for students, so if there are no students, you will see this error. Administrators can alter this option via the admin screens.)';
 $string['err_system']='An internal error occurred in the completion system. (System administrators can enable debugging information to see more detail.)';
+$string['daysafterenrolment']='Days after enrolment';
+$string['durationafterenrolment']='Duration after enrolment';
+$string['fraction']='Fraction';
 $string['help_completion']='completion tracking';
 $string['help_completionexpected']='the date completion is expected'; 
 $string['help_completionlocked']='locked completion options';
 $string['help_completionview']='requiring view to complete';
+$string['inprogress']='In progress';
+$string['manualcompletionby']='Manual completion by';
+$string['manualselfcompletion']='Manual self completion';
+$string['markcomplete']='Mark complete';
+$string['notenroled']='You are not enroled as a student in this course';
+$string['notyetstarted']='Not yet started';
+$string['overallcriteriaaggregation']='Overall critieria type aggregation';
+$string['passinggrade']='Passing grade';
+$string['pending']='Pending';
 $string['progress']='Student progress';
 $string['progress-title']='$a->user, $a->activity: $a->state $a->date';
 $string['progresstrackedroles'] = 'Progress-tracked roles';
 $string['reportpage']='Showing users {$a->from} to {$a->to} of {$a->total}.';
+$string['requiredcriteria']='Required Criteria';
 $string['restoringcompletiondata']='Writing completion data';
 $string['saved']='Saved';
+$string['selfcompletion']='Self completion';
+$string['unit']='Unit';
+$string['unenrolment']='Unenrolment';
 $string['unlockcompletion']='Unlock completion options';
+$string['unlockcompletiondelete']='Unlock completion options and delete user completion data';
 $string['writingcompletiondata']='Writing completion data';
 $string['completionicons']='progress tick boxes';
 $string['yourprogress']='Your progress';
Index: moodle/lang/en_utf8/role.php
--- moodle/lang/en_utf8/role.php Base (1.93)
+++ moodle/lang/en_utf8/role.php Locally Modified (Based On 1.93)
@@ -64,6 +64,7 @@
 $string['course:managegroups'] = 'Manage groups';
 $string['course:managemetacourse'] = 'Manage metacourse';
 $string['course:managescales'] = 'Manage scales';
+$string['course:markcomplete'] = 'Mark user\'s complete';
 $string['course:request'] = 'Request new courses';
 $string['course:reset'] = 'Reset course';
 $string['course:sectionvisibility'] = 'Control section visibility';
Index: moodle/lib/completion/completion_aggregation.php
--- moodle/lib/completion/completion_aggregation.php No Base Revision
+++ moodle/lib/completion/completion_aggregation.php Locally New
@@ -0,0 +1,114 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Course completion critieria aggregation
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once($CFG->libdir.'/data_object.php');
+
+/**
+ * Course completion critieria aggregation
+ */
+class completion_aggregation extends data_object {
+
+    /**
+     * DB Table
+     * @var string $table
+     */
+    public $table = 'course_completion_aggr_methd';
+
+    /**
+     * Array of required table fields, must start with 'id'.
+     * @var array $required_fields
+     */
+    public $required_fields = array('id', 'course', 'criteriatype', 'method', 'value');
+
+    /**
+     * Course id
+     * @access  public
+     * @var     int
+     */
+    public $course;
+
+    /**
+     * Criteria type this aggregation method applies to, or NULL for overall course aggregation
+     * @access  public
+     * @var     int
+     */
+    public $criteriatype;
+
+    /**
+     * Aggregation method (COMPLETION_AGGREGATION_* constant)
+     * @access  public
+     * @var     int
+     */
+    public $method;
+
+    /**
+     * Method value
+     * @access  public
+     * @var     mixed
+     */
+    public $value;
+
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        return self::fetch_helper('course_completion_aggr_methd', __CLASS__, $params);
+    }
+
+
+    /**
+     * Finds and returns all data_object instances based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return array array of data_object insatnces or false if none found.
+     */
+    public static function fetch_all($params) {}
+
+    /**
+     * Set the aggregation method
+     * @access  public
+     * @param   $method     int
+     * @return  void
+     */
+    public function setMethod($method) {
+        $methods = array(
+            COMPLETION_AGGREGATION_ALL,
+            COMPLETION_AGGREGATION_ANY,
+        );
+
+        if (in_array($method, $methods)) {
+            $this->method = $method;
+        } else {
+            $this->method = COMPLETION_AGGREGATION_ALL;
+        }
+    }
+}
Index: moodle/lib/completion/completion_completion.php
--- moodle/lib/completion/completion_completion.php No Base Revision
+++ moodle/lib/completion/completion_completion.php Locally New
@@ -0,0 +1,136 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Course completion status for a particular user/course
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once($CFG->libdir.'/data_object.php');
+
+
+/**
+ * Course completion status for a particular user/course
+ */
+class completion_completion extends data_object {
+
+    /**
+     * DB Table
+     * @var string $table
+     */
+    public $table = 'course_completions';
+
+    /**
+     * Array of required table fields, must start with 'id'.
+     * @var array $required_fields
+     */
+    public $required_fields = array('id', 'userid', 'course', 'deleted', 'timenotified', 'timeenroled', 'timecompleted');
+
+    /**
+     * User ID
+     * @access  public
+     * @var     int
+     */
+    public $userid;
+
+    /**
+     * Course ID
+     * @access  public
+     * @var     int
+     */
+    public $course;
+
+    /**
+     * Set to 1 if this record has been deleted
+     * @access  public
+     * @var     int
+     */
+    public $deleted;
+
+    /**
+     * Timestamp the interested parties were notified
+     * of this user's completion
+     * @access  public
+     * @var     int
+     */
+    public $timenotified;
+
+    /**
+     * Time of course enrolment
+     * @access  public
+     * @var     int
+     */
+    public $timeenroled;
+
+    /**
+     * Timestamp of course completion
+     * @see     completion_completion::mark_complete()
+     * @access  public
+     * @var     int
+     */
+    public $timecompleted;
+
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['deleted'] = null;
+        return self::fetch_helper('course_completions', __CLASS__, $params);
+    }
+
+    /**
+     * Return status of this completion
+     * @access  public
+     * @return  boolean
+     */
+    public function is_complete() {
+        return (bool) $this->timecompleted;
+    }
+
+    /**
+     * Mark this user complete in this course
+     * 
+     * This generally happens when the required completion criteria
+     * in the course are complete.
+     *
+     * This method creates a course_completions record
+     * @access  public
+     * @return  void
+     */
+    public function mark_complete() {
+
+        $this->timecompleted = time();
+
+        // Get users timenroled
+        // Can't find a more efficient way of doing this without alter get_users_by_capability()
+        global $DB;
+        $context = get_context_instance(CONTEXT_COURSE, $this->course);
+        $this->timeenroled = $DB->get_field('role_assignments', 'timestart', array('contextid' => $context->id, 'userid' => $this->userid));
+
+        // Create record
+        $this->insert();
+    }
+}
Index: moodle/lib/completion/completion_criteria.php
--- moodle/lib/completion/completion_criteria.php No Base Revision
+++ moodle/lib/completion/completion_criteria.php Locally New
@@ -0,0 +1,207 @@
+<?php
+
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Course completion criteria
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once($CFG->libdir.'/data_object.php');
+require_once($CFG->libdir.'/completion/completion_criteria_completion.php');
+
+
+/**
+ * Criteria type constants
+ * Primarily for storing criteria type in the database
+ */
+define('COMPLETION_CRITERIA_TYPE_SELF',         1);
+define('COMPLETION_CRITERIA_TYPE_DATE',         2);
+define('COMPLETION_CRITERIA_TYPE_UNENROL',      3);
+define('COMPLETION_CRITERIA_TYPE_ACTIVITY',     4);
+define('COMPLETION_CRITERIA_TYPE_DURATION',     5);
+define('COMPLETION_CRITERIA_TYPE_GRADE',        6);
+define('COMPLETION_CRITERIA_TYPE_ROLE',         7);
+
+/**
+ * Criteria type constant to class name mapping
+ */
+global $COMPLETION_CRITERIA_TYPES;
+$COMPLETION_CRITERIA_TYPES = array(
+    COMPLETION_CRITERIA_TYPE_SELF       => 'self',
+    COMPLETION_CRITERIA_TYPE_DATE       => 'date',
+    COMPLETION_CRITERIA_TYPE_UNENROL    => 'unenrol',
+    COMPLETION_CRITERIA_TYPE_ACTIVITY   => 'activity',
+    COMPLETION_CRITERIA_TYPE_DURATION   => 'duration',
+    COMPLETION_CRITERIA_TYPE_GRADE      => 'grade',
+    COMPLETION_CRITERIA_TYPE_ROLE       => 'role',
+);
+
+
+/**
+ * Completion criteria abstract definition
+ */
+abstract class completion_criteria extends data_object {
+    /**
+     * DB Table
+     * @var string $table
+     */
+    public $table = 'course_completion_criteria';
+
+    /**
+     * Array of required table fields, must start with 'id'.
+     * @var array $required_fields
+     */
+    public $required_fields = array('id', 'course', 'criteriatype', 'module', 'moduleinstance', 'enrolperiod', 'date', 'gradepass', 'role');
+
+    /**
+     * Course id
+     * @var     int
+     */
+    public $course;
+
+    /**
+     * Criteria type
+     * One of the COMPLETION_CRITERIA_TYPE_* constants
+     * @var     int
+     */
+    public $criteriatype;
+
+    /**
+     * Module type this criteria relates to (for activity criteria)
+     * @var     string
+     */
+    public $module;
+
+    /**
+     * Course module instance id this criteria relates to (for activity criteria)
+     * @var     int
+     */
+    public $moduleinstance;
+
+    /**
+     * Period after enrolment completion will be triggered (for period criteria)
+     * @var     int     (days)
+     */
+    public $enrolperiod;
+
+    /**
+     * Date of course completion (for date criteria)
+     * @var     int     (timestamp)
+     */
+    public $date;
+
+    /**
+     * Passing grade required to complete course (for grade completion)
+     * @var     float
+     */
+    public $gradepass;
+
+    /**
+     * Role ID that has the ability to mark a user as complete (for role completion)
+     * @var     int
+     */
+    public $role;
+
+    /**
+     * Finds and returns all data_object instances based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return array array of data_object insatnces or false if none found.
+     */
+    public static function fetch_all($params) {}
+
+    /**
+     * Factory method for creating correct class object
+     * @static
+     * @param   array
+     * @return  object
+     */
+    public static function factory($params) {
+        global $CFG, $COMPLETION_CRITERIA_TYPES;
+
+        if (!isset($COMPLETION_CRITERIA_TYPES[$params->criteriatype])) {
+            throw new moodle_exception('invalidcriteriatype', 'completion');
+        }
+
+        $class = 'completion_criteria_'.$COMPLETION_CRITERIA_TYPES[$params->criteriatype];
+        require_once($CFG->libdir.'/completion/'.$class.'.php');
+
+        return new $class($params, false);
+    }
+
+    /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    abstract public function config_form_display(&$mform, $data = null);
+
+    /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    abstract public function update_config(&$data);
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    abstract public function review($completion, $mark = true);
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    abstract public function get_title();
+
+    /**
+     * Return criteria status text for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  string
+     */
+    public function get_status($completion) {
+        return $completion->is_complete() ? get_string('yes') : get_string('no');
+    }
+
+    /**
+     * Return true if the criteria's current status is different to what is sorted
+     * in the database, e.g. pending an update
+     *
+     * @param object $completion The user's criteria completion record
+     * @return bool
+     */
+    public function is_pending($completion) {
+        $review = $this->review($completion, false);
+
+        return $review !== $completion->is_complete();
+    }
+}
Index: moodle/lib/completion/completion_criteria_activity.php
--- moodle/lib/completion/completion_criteria_activity.php No Base Revision
+++ moodle/lib/completion/completion_criteria_activity.php Locally New
@@ -0,0 +1,196 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course completion critieria - completion on activity completion
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class completion_criteria_activity extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_ACTIVITY;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['criteriatype'] = COMPLETION_CRITERIA_TYPE_ACTIVITY;
+        return self::fetch_helper('course_completion_criteria', __CLASS__, $params);
+    }
+
+    /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    public function config_form_display(&$mform, $data = null) {
+        $mform->addElement('checkbox', 'criteria_activity['.$data->id.']', ucfirst(self::get_mod_name($data->module)).' - '.$data->name);
+
+        if ($this->id) {
+            $mform->setDefault('criteria_activity['.$data->id.']', 1);
+        }
+    }
+
+    /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    public function update_config(&$data) {
+        global $DB;
+
+        if (!empty($data->criteria_activity) && is_array($data->criteria_activity)) {
+
+            $this->course = $data->id;
+
+            foreach (array_keys($data->criteria_activity) as $activity) {
+
+                $module = $DB->get_record('course_modules', array('id' => $activity));
+                $this->module = self::get_mod_name($module->module);
+                $this->moduleinstance = $activity;
+                $this->id = NULL;
+                $this->insert();
+            }
+        }
+    }
+
+    /**
+     * Get module instance module type
+     * @static
+     * @access  public
+     * @param   int     $type   Module type id
+     * @return  string
+     */
+    public static function get_mod_name($type) {
+        static $types;
+
+        if (!is_array($types)) {
+            global $DB;
+            $types = $DB->get_records('modules');
+        }
+
+        return $types[$type]->name;
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    public function review($completion, $mark = true) {
+        global $DB;
+
+        $course = $DB->get_record('course', array('id' => $completion->course));
+        $cm = $DB->get_record('course_modules', array('id' => $this->moduleinstance));
+        $info = new completion_info($course);
+
+        $data = $info->get_data($cm, false, $completion->userid);
+
+        // If the activity is complete
+        if (in_array($data->completionstate, array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS))) {
+            if ($mark) {
+                $completion->mark_complete();
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title() {
+        return get_string('activitiescompleted', 'completion');
+    }
+
+    /**
+     * Find user's who have completed this criteria
+     * @access  public
+     * @return  void
+     */
+    public function cron() {
+        global $DB;
+
+        // Get all users who meet this criteria
+        $sql = '
+            SELECT DISTINCT
+                c.id AS course,
+                cr.date AS date,
+                cr.id AS criteriaid,
+                ra.userid AS userid
+            FROM
+                {course_completion_criteria} cr
+            INNER JOIN
+                {course} c
+             ON cr.course = c.id
+            INNER JOIN
+                {context} con
+             ON con.instanceid = c.id
+            INNER JOIN
+                {role_assignments} ra
+              ON ra.contextid = con.id
+            INNER JOIN
+                {course_modules_completion} mc
+             ON mc.coursemoduleid = cr.moduleinstance
+            AND mc.userid = ra.userid
+            LEFT JOIN
+                {course_completion_crit_compl} cc
+             ON cc.criteriaid = cr.id
+            AND cc.userid = ra.userid
+            WHERE
+                cr.criteriatype = '.COMPLETION_CRITERIA_TYPE_ACTIVITY.'
+            AND con.contextlevel = '.CONTEXT_COURSE.'
+            AND c.enablecompletion = 1
+            AND cc.id IS NULL
+            AND (
+                mc.completionstate = '.COMPLETION_COMPLETE.'
+             OR mc.completionstate = '.COMPLETION_COMPLETE_PASS.'
+                )
+        ';
+
+        // Loop through completions, and mark as complete
+        if ($rs = $DB->get_recordset_sql($sql)) {
+            foreach ($rs as $record) {
+
+                $completion = new completion_criteria_completion((array)$record);
+                $completion->mark_complete();
+            }
+
+            $rs->close();
+        }
+    }
+}
Index: moodle/lib/completion/completion_criteria_completion.php
--- moodle/lib/completion/completion_criteria_completion.php No Base Revision
+++ moodle/lib/completion/completion_criteria_completion.php Locally New
@@ -0,0 +1,189 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Completion data for a specific user, course and critieria
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once($CFG->libdir.'/data_object.php');
+
+
+/**
+ * Completion data for a specific user, course and critieria
+ */
+class completion_criteria_completion extends data_object {
+
+    /**
+     * DB Table
+     * @var string $table
+     */
+    public $table = 'course_completion_crit_compl';
+
+    /**
+     * Array of required table fields, must start with 'id'.
+     * @var array $required_fields
+     */
+    public $required_fields = array('id', 'userid', 'course', 'criteriaid', 'gradefinal', 'deleted', 'unenroled', 'timecompleted');
+
+    /**
+     * User ID
+     * @access  public
+     * @var     int
+     */
+    public $userid;
+
+    /**
+     * Course ID
+     * @access  public
+     * @var     int
+     */
+    public $course;
+
+    /**
+     * The id of the course completion criteria this completion references
+     * @access  public
+     * @var     int
+     */
+    public $criteriaid;
+
+    /**
+     * The final grade for the user in the course (if completing a grade criteria)
+     * @access  public
+     * @var     float
+     */
+    public $gradefinal;
+
+    /**
+     * Course deleted flag
+     * @access  public
+     * @var     boolean
+     */
+    public $deleted;
+
+    /**
+     * Timestamp of user unenrolment (if completing a unenrol criteria)
+     * @access  public
+     * @var     int     (timestamp)
+     */
+    public $unenroled;
+
+    /**
+     * Timestamp of course criteria completion
+     * @see     completion_criteria_completion::mark_complete()
+     * @access  public
+     * @var     int     (timestamp)
+     */
+    public $timecompleted;
+
+    /**
+     * Associated criteria object
+     * @access  private
+     * @var object  completion_criteria
+     */
+    private $_criteria;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['deleted'] = null;
+        return self::fetch_helper('course_completion_crit_compl', __CLASS__, $params);
+    }
+
+    /**
+     * Finds and returns all data_object instances based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return array array of data_object insatnces or false if none found.
+     */
+    public static function fetch_all($params) {}
+
+    /**
+     * Return status of this criteria completion
+     * @access  public
+     * @return  boolean
+     */
+    public function is_complete() {
+        return (bool) $this->timecompleted;
+    }
+
+    /**
+     * Mark this criteria complete for the associated user
+     *
+     * This method creates a course_completion_crit_compl record
+     * @access  public
+     * @return  void
+     */
+    public function mark_complete() {
+        // Create record
+        $this->timecompleted = time();
+        $this->insert();
+    }
+
+    /**
+     * Attach a preloaded criteria object to this object
+     * @access  public
+     * @param   $criteria   object  completion_criteria
+     * @return  void
+     */
+    public function attach_criteria(completion_criteria $criteria) {
+        $this->_criteria = $criteria;
+    }
+
+    /**
+     * Return the associated criteria with this completion
+     * If nothing attached, load from the db
+     * @access  public
+     * @return  object completion_criteria
+     */
+    public function get_criteria() {
+
+        if (!$this->_criteria)
+        {
+            global $DB;
+
+            $params = array(
+                'id'    => $this->criteriaid
+            );
+
+            $record = $DB->get_record('course_completion_criteria', $params);
+
+            $this->attach_criteria(completion_criteria::factory($record));
+        }
+
+        return $this->_criteria;
+    }
+
+    /**
+     * Return criteria status text for display in reports
+     * @see     completion_criteria::get_status()
+     * @access  public
+     * @return  string
+     */
+    public function get_status() {
+        return $this->_criteria->get_status($this);
+    }
+}
Index: moodle/lib/completion/completion_criteria_date.php
--- moodle/lib/completion/completion_criteria_date.php No Base Revision
+++ moodle/lib/completion/completion_criteria_date.php Locally New
@@ -0,0 +1,173 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course completion critieria - completion on specified date
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class completion_criteria_date extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_DATE;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['criteriatype'] = COMPLETION_CRITERIA_TYPE_DATE;
+        return self::fetch_helper('course_completion_criteria', __CLASS__, $params);
+    }
+
+    /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    public function config_form_display(&$mform, $data = null)
+    {
+        $mform->addElement('checkbox', 'criteria_date', get_string('enable'));
+        $mform->addElement('date', 'criteria_date_value', get_string('afterspecifieddate', 'completion'));
+
+        // If instance of criteria exists
+        if ($this->id) {
+            $mform->setDefault('criteria_date', 1);
+            $mform->setDefault('criteria_date_value', $this->date);
+        } else {
+            $mform->setDefault('criteria_date_value', time() + 3600 * 24);
+        }
+    }
+
+    /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    public function update_config(&$data) {
+
+        if (!empty($data->criteria_date))
+        {
+            $this->course = $data->id;
+            $date = $data->criteria_date_value;
+            $this->date = strtotime($date['Y'].'-'.$date['M'].'-'.$date['d']);
+            $this->insert();
+        }
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    public function review($completion, $mark = true)
+    {
+        // If date is past date
+        if ($this->date && $this->date < time()) {
+            if ($mark) {
+                $completion->mark_complete();
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title() {
+        return get_string('date');
+    }
+
+    /**
+     * Return criteria status text for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  string
+     */
+    public function get_status($completion) {
+        return userdate($this->date, '%d-%h-%y');
+    }
+
+    /**
+     * Find user's who have completed this criteria
+     * @access  public
+     * @return  void
+     */
+    public function cron() {
+        global $DB;
+
+        // Get all users who match meet this criteria
+        $sql = '
+            SELECT DISTINCT
+                c.id AS course,
+                cr.date AS date,
+                cr.id AS criteriaid,
+                ra.userid AS userid
+            FROM
+                {course_completion_criteria} cr
+            INNER JOIN
+                {course} c
+             ON cr.course = c.id
+            INNER JOIN
+                {context} con
+             ON con.instanceid = c.id
+            INNER JOIN
+                {role_assignments} ra
+             ON ra.contextid = con.id
+            LEFT JOIN
+                {course_completion_crit_compl} cc
+             ON cc.criteriaid = cr.id
+            AND cc.userid = ra.userid
+            WHERE
+                cr.criteriatype = '.COMPLETION_CRITERIA_TYPE_DATE.'
+            AND con.contextlevel = '.CONTEXT_COURSE.'
+            AND c.enablecompletion = 1
+            AND cc.id IS NULL
+            AND cr.date < ?
+        ';
+
+        // Loop through completions, and mark as complete
+        if ($rs = $DB->get_recordset_sql($sql, array(time()))) {
+            foreach ($rs as $record) {
+
+                $completion = new completion_criteria_completion((array)$record);
+                $completion->mark_complete();
+            }
+
+            $rs->close();
+        }
+    }
+}
Index: moodle/lib/completion/completion_criteria_duration.php
--- moodle/lib/completion/completion_criteria_duration.php No Base Revision
+++ moodle/lib/completion/completion_criteria_duration.php Locally New
@@ -0,0 +1,197 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course completion critieria - completion after specific duration from course enrolment
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class completion_criteria_duration extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_DURATION;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['criteriatype'] = COMPLETION_CRITERIA_TYPE_DURATION;
+        return self::fetch_helper('course_completion_criteria', __CLASS__, $params);
+    }
+
+    /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    public function config_form_display(&$mform, $data = null) {
+
+        $mform->addElement('checkbox', 'criteria_duration', get_string('enable'));
+
+        $thresholdmenu=array();
+        for ($i=1; $i<=30; $i++) {
+            $seconds = $i * 86400;
+            $thresholdmenu[$seconds] = get_string('numdays', '', $i);
+        }
+        $mform->addElement('select', 'criteria_duration_days', get_string('daysafterenrolment', 'completion'), $thresholdmenu);
+
+        if ($this->id) {
+            $mform->setDefault('criteria_duration', 1);
+            $mform->setDefault('criteria_duration_days', $this->enrolperiod);
+        }
+    }
+
+    /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    public function update_config(&$data) {
+
+        if (!empty($data->criteria_duration)) {
+            $this->course = $data->id;
+            $this->enrolperiod = $data->criteria_duration_days;
+            $this->insert();
+        }
+    }
+
+    /**
+     * Get the time this user was enroled
+     * @param   object  $completion
+     * @return  int
+     */
+    private function get_timeenroled($completion) {
+        global $DB;
+
+        $context = get_context_instance(CONTEXT_COURSE, $this->course);
+        return $DB->get_field('role_assignments', 'timestart', array('contextid' => $context->id, 'userid' => $completion->userid));
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    public function review($completion, $mark = true) {
+        $timeenroled = $this->get_timeenroled($completion);
+
+        // If duration since enrolment has passed
+        if (!$this->enrolperiod || !$timeenroled) {
+            return false;
+        }
+
+        if (time() > ($timeenroled + $this->enrolperiod)) {
+            if ($mark) {
+                $completion->mark_complete();
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title() {
+        return get_string('enrolmentduration', 'completion');
+    }
+
+    /**
+     * Return criteria status text for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  string
+     */
+    public function get_status($completion) {
+        $timeenroled = $this->get_timeenroled($completion);
+        $timeleft = $timeenroled + $this->enrolperiod - time();
+        $enrolperiod = ceil($this->enrolperiod / (60 * 60 * 24));
+
+        $daysleft = ceil($timeleft / (60 * 60 * 24));
+
+        return ($daysleft > 0 ? $daysleft : 0).' of '.$enrolperiod;
+    }
+
+    /**
+     * Find user's who have completed this criteria
+     * @access  public
+     * @return  void
+     */
+    public function cron() {
+        global $DB;
+
+        // Get all users who match meet this criteria
+        $sql = '
+            SELECT DISTINCT
+                c.id AS course,
+                cr.date AS date,
+                cr.id AS criteriaid,
+                ra.userid AS userid
+            FROM
+                {course_completion_criteria} cr
+            INNER JOIN
+                {course} c
+             ON cr.course = c.id
+            INNER JOIN
+                {context} con
+             ON con.instanceid = c.id
+            INNER JOIN
+                {role_assignments} ra
+             ON ra.contextid = con.id
+            LEFT JOIN
+                {course_completion_crit_compl} cc
+             ON cc.criteriaid = cr.id
+            AND cc.userid = ra.userid
+            WHERE
+                cr.criteriatype = '.COMPLETION_CRITERIA_TYPE_DURATION.'
+            AND con.contextlevel = '.CONTEXT_COURSE.'
+            AND c.enablecompletion = 1
+            AND cc.id IS NULL
+            AND ra.timestart + cr.enrolperiod < ?
+        ';
+
+        // Loop through completions, and mark as complete
+        if ($rs = $DB->get_recordset_sql($sql, array(time()))) {
+            foreach ($rs as $record) {
+
+                $completion = new completion_criteria_completion((array)$record);
+                $completion->mark_complete();
+            }
+
+            $rs->close();
+        }
+    }
+}
Index: moodle/lib/completion/completion_criteria_grade.php
--- moodle/lib/completion/completion_criteria_grade.php No Base Revision
+++ moodle/lib/completion/completion_criteria_grade.php Locally New
@@ -0,0 +1,210 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course completion critieria - completion on achieving course grade
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once $CFG->dirroot.'/grade/lib.php';
+require_once $CFG->dirroot.'/grade/querylib.php';
+
+/**
+ * Course completion critieria - completion on achieving course grade
+ */
+class completion_criteria_grade extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_GRADE;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['criteriatype'] = COMPLETION_CRITERIA_TYPE_GRADE;
+        return self::fetch_helper('course_completion_criteria', __CLASS__, $params);
+    }
+
+    /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    public function config_form_display(&$mform, $data = null) {
+        $mform->addElement('checkbox', 'criteria_grade', get_string('enable'));
+        $mform->addElement('text', 'criteria_grade_value', get_string('passinggrade', 'completion'));
+        $mform->setDefault('criteria_grade_value', $data);
+
+        if ($this->id) {
+            $mform->setDefault('criteria_grade', 1);
+            $mform->setDefault('criteria_grade_value', $this->gradepass);
+        }
+
+        $mform->hardFreeze('criteria_grade_value');
+    }
+
+    /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    public function update_config(&$data) {
+
+        // TODO validation
+        if (!empty($data->criteria_grade) && is_numeric($data->criteria_grade_value))
+        {
+            $this->course = $data->id;
+            $this->gradepass = $data->criteria_grade_value;
+            $this->insert();
+        }
+    }
+
+    /**
+     * Get user's course grade in this course
+     * @static
+     * @access  private
+     * @param   object  $completion
+     * @return  float
+     */
+    private function get_grade($completion) {
+        $grade = grade_get_course_grade($completion->userid, $this->course);
+        return $grade->grade;
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    public function review($completion, $mark = true) {
+        // Get user's course grade
+        $grade = $this->get_grade($completion);
+
+        // If user's current course grade is higher than the required pass grade
+        if ($this->gradepass && $this->gradepass <= $grade) {
+            if ($mark) {
+                $completion->gradefinal = $grade;
+                $completion->mark_complete();
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title() {
+        return get_string('grade');
+    }
+
+    /**
+     * Return criteria status text for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  string
+     */
+    public function get_status($completion) {
+        // Cast as floats to get rid of excess decimal places
+        $grade = (float) $this->get_grade($completion);
+        $gradepass = (float) $this->gradepass;
+
+        if ($grade) {
+            return $grade.'% ('.$gradepass.'% to pass)';
+        } else {
+            return $gradepass.'% to pass';
+        }
+    }
+
+    /**
+     * Find user's who have completed this criteria
+     * @access  public
+     * @return  void
+     */
+    public function cron() {
+        global $DB;
+
+        // Get all users who meet this criteria
+        $sql = '
+            SELECT DISTINCT
+                c.id AS course,
+                cr.date AS date,
+                cr.id AS criteriaid,
+                ra.userid AS userid,
+                gg.finalgrade AS gradefinal
+            FROM
+                {course_completion_criteria} cr
+            INNER JOIN
+                {course} c
+             ON cr.course = c.id
+            INNER JOIN
+                {context} con
+             ON con.instanceid = c.id
+            INNER JOIN
+                {role_assignments} ra
+              ON ra.contextid = con.id
+            INNER JOIN
+                {grade_items} gi
+             ON gi.courseid = c.id
+            AND gi.itemtype = \'course\'
+            INNER JOIN
+                {grade_grades} gg
+             ON gg.itemid = gi.id
+            AND gg.userid = ra.userid
+            LEFT JOIN
+                {course_completion_crit_compl} cc
+             ON cc.criteriaid = cr.id
+            AND cc.userid = ra.userid
+            WHERE
+                cr.criteriatype = '.COMPLETION_CRITERIA_TYPE_GRADE.'
+            AND con.contextlevel = '.CONTEXT_COURSE.'
+            AND c.enablecompletion = 1
+            AND cc.id IS NULL
+            AND gg.finalgrade >= cr.gradepass
+        ';
+
+        // Loop through completions, and mark as complete
+        if ($rs = $DB->get_recordset_sql($sql)) {
+            foreach ($rs as $record) {
+
+                $completion = new completion_criteria_completion((array)$record);
+                $completion->mark_complete();
+            }
+
+            $rs->close();
+        }
+    }
+}
Index: moodle/lib/completion/completion_criteria_role.php
--- moodle/lib/completion/completion_criteria_role.php No Base Revision
+++ moodle/lib/completion/completion_criteria_role.php Locally New
@@ -0,0 +1,123 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course completion critieria - marked by role
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class completion_criteria_role extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_ROLE;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['criteriatype'] = COMPLETION_CRITERIA_TYPE_ROLE;
+        return self::fetch_helper('course_completion_criteria', __CLASS__, $params);
+    }
+
+   /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    public function config_form_display(&$mform, $data = null) {
+
+        $mform->addElement('checkbox', 'criteria_role['.$data->id.']', $data->name);
+
+        if ($this->id) {
+            $mform->setDefault('criteria_role['.$data->id.']', 1);
+        }
+    }
+
+     /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    public function update_config(&$data) {
+
+        if (!empty($data->criteria_role) && is_array($data->criteria_role)) {
+
+            $this->course = $data->id;
+
+            foreach (array_keys($data->criteria_role) as $role) {
+
+                $this->role = $role;
+                $this->id = NULL;
+                $this->insert();
+            }
+        }
+    }
+
+    /**
+     * Mark this criteria as complete
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  void
+     */
+    public function complete($completion) {
+        $this->review($completion, true, true);
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    public function review($completion, $mark = true, $is_complete = false)  {
+        // If we are marking this as complete
+        if ($is_complete && $mark)
+        {
+            $completion->completedself = 1;
+            $completion->mark_complete();
+
+            return true;
+        }
+
+        return $completion->is_complete();
+    }
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title() {
+        global $DB;
+        $role = $DB->get_field('role', 'name', array('id' => $this->role));
+        return $role;
+    }
+}
Index: moodle/lib/completion/completion_criteria_self.php
--- moodle/lib/completion/completion_criteria_self.php No Base Revision
+++ moodle/lib/completion/completion_criteria_self.php Locally New
@@ -0,0 +1,112 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course completion critieria - student self marked
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class completion_criteria_self extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_SELF;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['criteriatype'] = COMPLETION_CRITERIA_TYPE_SELF;
+        return self::fetch_helper('course_completion_criteria', __CLASS__, $params);
+    }
+
+    /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    public function config_form_display(&$mform, $data = null) {
+        $mform->addElement('checkbox', 'criteria_self', get_string('enable'));
+
+        if ($this->id ) {
+           $mform->setDefault('criteria_self', 1);
+        }
+    }
+
+    /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    public function update_config(&$data) {
+        if (!empty($data->criteria_self)) {
+            $this->course = $data->id;
+            $this->insert();
+        }
+    }
+
+    /**
+     * Mark this criteria as complete
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  void
+     */
+    public function complete($completion) {
+        $this->review($completion, true, true);
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    public function review($completion, $mark = true, $is_complete = false) {
+        // If we are marking this as complete
+        if ($is_complete && $mark)
+        {
+            $completion->completedself = 1;
+            $completion->mark_complete();
+
+            return true;
+        }
+
+        return $completion->is_complete();
+    }
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title() {
+        return get_string('selfcompletion', 'completion');
+    }
+}
Index: moodle/lib/completion/completion_criteria_unenrol.php
--- moodle/lib/completion/completion_criteria_unenrol.php No Base Revision
+++ moodle/lib/completion/completion_criteria_unenrol.php Locally New
@@ -0,0 +1,94 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course completion critieria - completion on unenrolment
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class completion_criteria_unenrol extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_UNENROL;
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static function fetch($params) {
+        $params['criteriatype'] = COMPLETION_CRITERIA_TYPE_UNENROL;
+        return self::fetch_helper('course_completion_criteria', __CLASS__, $params);
+    }
+
+    /**
+     * Add appropriate form elements to the critieria form
+     * @access  public
+     * @param   object  $mform  Moodle forms object
+     * @param   mixed   $data   optional
+     * @return  void
+     */
+    public function config_form_display(&$mform, $data = null) {
+        $mform->addElement('checkbox', 'criteria_unenrol', 'Completion on unenrolment');
+
+        if ($this->id) {
+            $mform->setDefault('criteria_unenrol', 1);
+        }
+    }
+
+    /**
+     * Update the criteria information stored in the database
+     * @access  public
+     * @param   array   $data   Form data
+     * @return  void
+     */
+    public function update_config(&$data) {
+        if (!empty($data->criteria_unenrol)) {
+            $this->course = $data->id;
+            $this->insert();
+        }
+    }
+
+    /**
+     * Review this criteria and decide if the user has completed
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @param   boolean $mark           Optionally set false to not save changes to database
+     * @return  boolean
+     */
+    public function review($completion, $mark = true) {
+        // Check enrolment
+        return false;
+    }
+
+    /**
+     * Return criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title() {
+        return get_string('unenrol');
+    }
+}
Index: moodle/lib/completion/cron.php
--- moodle/lib/completion/cron.php No Base Revision
+++ moodle/lib/completion/cron.php Locally New
@@ -0,0 +1,236 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Cron job for reviewing and aggregating course completion criteria
+ *
+ * @package   moodlecore
+ * @copyright 2009 Catalyst IT Ltd
+ * @author    Aaron Barnes <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once $CFG->libdir.'/completionlib.php';
+
+
+/**
+ * Update user's course completion statuses
+ *
+ * First update all criteria completions, then
+ * aggregate all criteria completions and update
+ * overall course completions
+ *
+ * @return  void
+ */
+function completion_cron() {
+
+    completion_cron_criteria();
+
+    completion_cron_completions();
+}
+
+/**
+ * Run installed criteria's data aggregation methods
+ *
+ * Loop through each installed criteria and run the
+ * cron() method if it exists
+ *
+ * @return  void
+ */
+function completion_cron_criteria() {
+
+    // Process each criteria type
+    global $CFG, $COMPLETION_CRITERIA_TYPES;
+
+    foreach ($COMPLETION_CRITERIA_TYPES as $type) {
+
+        $object = 'completion_criteria_'.$type;
+        require_once $CFG->libdir.'/completion/'.$object.'.php';
+
+        $class = new $object();
+
+        // Run the criteria type's cron method, if it has one
+        if (method_exists($class, 'cron')) {
+
+            if (debugging()) {
+                mtrace('Running '.$object.'->cron()');
+            }
+            $class->cron();
+        }
+    }
+}
+
+/**
+ * Aggregate each user's criteria completions
+ *
+ * @return  void
+ */
+function completion_cron_completions() {
+    global $DB;
+
+    if (debugging()) {
+        mtrace('Aggregating completions');
+    }
+
+    // Grab all criteria and their associated criteria completions
+    $sql = '
+        SELECT DISTINCT
+            c.id AS course,
+            cr.id AS criteriaid,
+            ra.userid AS userid,
+            cr.criteriatype AS criteriatype,
+            cc.timecompleted AS timecompleted
+        FROM
+            {course_completion_criteria} cr
+        INNER JOIN
+            {course} c
+         ON cr.course = c.id
+        INNER JOIN
+            {context} con
+         ON con.instanceid = c.id
+        INNER JOIN
+            {role_assignments} ra
+         ON ra.contextid = con.id
+        LEFT JOIN
+            {course_completion_crit_compl} cc
+         ON cc.criteriaid = cr.id
+        AND cc.userid = ra.userid
+        LEFT JOIN
+            {course_completions} crc
+         ON crc.course = c.id
+        AND crc.userid = ra.userid
+        WHERE
+            con.contextlevel = '.CONTEXT_COURSE.'
+        AND c.enablecompletion = 1
+        AND crc.id IS NULL
+        ORDER BY
+            course,
+            userid
+    ';
+
+    // Check if result is empty
+    if (!$rs = $DB->get_recordset_sql($sql)) {
+        return;
+    }
+
+    $current_user = null;
+    $current_course = null;
+    $completions = array();
+
+    while (1) {
+
+        // Grab records for current user/course
+        foreach ($rs as $record) {
+            // If we are still grabbing the same users completions
+            if ($record->userid === $current_user && $record->course === $current_course) {
+                $completions[$record->criteriaid] = $record;
+            } else {
+                break;
+            }
+        }
+
+        // Aggregate
+        if (!empty($completions)) {
+
+            if (debugging()) {
+                mtrace('Aggregating completions for user '.$current_user.' in course '.$current_course);
+            }
+
+            // Get course info object
+            $info = new completion_info((object)array('id' => $current_course));
+
+            // Setup aggregation
+            $overall = $info->get_aggregation_method();
+            $activity = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY);
+            $role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE);
+
+            $overall_status = null;
+            $activity_status = null;
+            $role_status = null;
+
+            // Check each of the criteria
+            foreach ($completions as $params) {
+                $completion = new completion_criteria_completion($params, false);
+
+                // Handle aggregation special cases
+                if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
+                    completion_cron_aggregate($activity, $completion->is_complete(), $activity_status);
+                } else if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ROLE) {
+                    completion_cron_aggregate($role, $completion->is_complete(), $role_status);
+                } else {
+                    completion_cron_aggregate($overall, $completion->is_complete(), $overall_status);
+                }
+            }
+
+            // Include role criteria aggregation in overall aggregation
+            if ($role_status !== null) {
+                completion_cron_aggregate($overall, $role_status, $overall_status);
+            }
+
+            // Include activity criteria aggregation in overall aggregation
+            if ($activity_status !== null) {
+                completion_cron_aggregate($overall, $activity_status, $overall_status);
+            }
+
+            // If aggregation status is true, mark course complete for user
+            if ($overall_status) {
+                if (debugging()) {
+                    mtrace('Marking complete');
+                }
+
+                $ccompletion = new completion_completion(array('course' => $params->course, 'userid' => $params->userid));
+                $ccompletion->mark_complete();
+            }
+        }
+
+        // If this is the end of the recordset, break the loop
+        if (!$rs->valid()) {
+            $rs->close();
+            break;
+        }
+
+        // New/next user, update user details, reset completions
+        $current_user = $record->userid;
+        $current_course = $record->course;
+        $completions = array();
+        $completions[$record->criteriaid] = $record;
+    }
+}
+
+/**
+ * Aggregate criteria status's as per configured aggregation method
+ *
+ * @param int $method COMPLETION_AGGREGATION_* constant
+ * @param bool $data Criteria completion status
+ * @param bool|null $state Aggregation state
+ * @return void
+ */
+function completion_cron_aggregate($method, $data, &$state) {
+    if ($method == COMPLETION_AGGREGATION_ALL) {
+        if ($data && $state !== false) {
+            $state = true;
+        } else {
+            $state = false;
+        }
+    } elseif ($method == COMPLETION_AGGREGATION_ANY) {
+        if ($data) {
+            $state = true;
+        } else if (!$data && $state === null) {
+            $state = false;
+        }
+    }
+}
Index: moodle/lib/completionlib.php
--- moodle/lib/completionlib.php Base (1.19)
+++ moodle/lib/completionlib.php Locally Modified (Based On 1.19)
@@ -15,6 +15,12 @@
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+require_once $CFG->libdir.'/completion/completion_aggregation.php';
+require_once $CFG->libdir.'/completion/completion_criteria.php';
+require_once $CFG->libdir.'/completion/completion_completion.php';
+require_once $CFG->libdir.'/completion/completion_criteria_completion.php';
+
+
 /**
  * Contains a class used for tracking whether activities have been completed
  * by students ('completion')
@@ -81,7 +87,7 @@
 define('COMPLETION_CACHE_EXPIRY', 10*60);
 
 // Combining completion condition. This is also the value you should return
-// if you don't have any applicable conditions.
+// if you don't have any applicable conditions. Used for activity completion.
 /** Completion details should be ORed together and you should return false if
  none apply */
 define('COMPLETION_OR', false);
@@ -89,6 +95,11 @@
  none apply */
 define('COMPLETION_AND', true);
 
+// Course completion criteria aggregation methods
+define('COMPLETION_AGGREGATION_ALL',        1);
+define('COMPLETION_AGGREGATION_ANY',        2);
+
+
 /**
  * Class represents completion information for a course.
  *
@@ -99,16 +110,48 @@
  * @package moodlecore
  */
 class completion_info {
-    /** @var object Passed during construction */
+    /** 
+     * Course object passed during construction
+     * @access  private
+     * @var     object
+     */
     private $course;
 
     /**
+     * Course id
+     * @access  public
+     * @var     int
+     */
+    public $course_id;
+
+    /**
+     * Completion criteria
+     * @access  private
+     * @var     array
+     * @see     completion_info->get_criteria()
+     */
+    private $criteria;
+
+    /**
+     * Return array of aggregation methods
+     * @access  public
+     * @return  array
+     */
+    public static function get_aggregation_methods() {
+        return array(
+            COMPLETION_AGGREGATION_ALL       => get_string('all'),
+            COMPLETION_AGGREGATION_ANY       => get_string('any', 'completion'),
+        );
+    }
+
+    /**
      * Constructs with course details.
      *
      * @param object $course Moodle course object. Must have at least ->id, ->enablecompletion
      */
     public function __construct($course) {
         $this->course = $course;
+        $this->course_id = $course->id;
     }
 
     /**
@@ -173,8 +216,196 @@
             echo '</span>';
         }
     }
-    // OU specific end
+
     /**
+     * Get a course completion for a user
+     * @access  public
+     * @param   $user_id        int     User id
+     * @param   $criteriatype   int     Specific criteria type to return
+     * @return  false|completion_criteria_completion
+     */
+    public function get_completion($user_id, $criteriatype) {
+        $completions = $this->get_completions($user_id, $criteriatype);
+
+        // Check if self completion is one of this course's criteria
+        if (empty($completions)) {
+            return false;
+        } elseif (count($completions) > 1) {
+            print_error('multipleselfcompletioncriteria', 'completion');
+        }
+
+        return $completions[0];
+    }
+
+    /**
+     * Get all course criteria's completion objects for a user
+     * @access  public
+     * @param   $user_id        int     User id
+     * @param   $criteriatype   int     optional    Specific criteria type to return
+     * @return  array
+     */
+    public function get_completions($user_id, $criteriatype = null) {
+        $criterion = $this->get_criteria($criteriatype);
+
+        $completions = array();
+
+        foreach ($criterion as $criteria) {
+            $params = array(
+                'course'        => $this->course_id,
+                'userid'        => $user_id,
+                'criteriaid'    => $criteria->id
+            );
+
+            $completion = new completion_criteria_completion($params);
+            $completion->attach_criteria($criteria);
+
+            $completions[] = $completion;
+        }
+
+        return $completions;
+    }
+
+    /**
+     * Get completion object for a user and a criteria
+     * @access  public
+     * @param   $user_id        int     User id
+     * @param   $criteria       completion_criteria     Criteria object
+     * @return  completion_criteria_completion
+     */
+    public function get_user_completion($user_id, $criteria) {
+        $params = array(
+            'criteriaid'    => $criteria->id,
+            'userid'        => $user_id
+        );
+
+        $completion = new completion_criteria_completion($params);
+        return $completion;
+    }
+
+    /**
+     * Check if course has completion criteria set
+     *
+     * @access  public
+     * @return  bool
+     */
+    public function has_criteria() {
+        $criteria = $this->get_criteria();
+
+        return (bool) count($criteria);
+    }
+
+
+    /**
+     * Get course completion criteria
+     * @access  public
+     * @param   $criteriatype   int     optional    Specific criteria type to return
+     * @return  void
+     */
+    public function get_criteria($criteriatype = null) {
+
+        // Fill cache if empty
+        if (!is_array($this->criteria)) {
+            global $DB;
+
+            $params = array(
+                'course'    => $this->course->id
+            );
+
+            // Load criteria from database
+            $records = (array)$DB->get_records('course_completion_criteria', $params);
+
+            // Build array of criteria objects
+            $this->criteria = array();
+            foreach ($records as $record) {
+                $this->criteria[$record->id] = completion_criteria::factory($record);
+            }
+        }
+
+        // If after all criteria
+        if ($criteriatype === null) {
+            return $this->criteria;
+        }
+
+        // If we are only after a specific criteria type
+        $criteria = array();
+        foreach ($this->criteria as $criterion) {
+
+            if ($criterion->criteriatype != $criteriatype) {
+                continue;
+            }
+
+            $criteria[$criterion->id] = $criterion;
+        }
+
+        return $criteria;
+    }
+
+    /**
+     * Get aggregation method
+     * @access  public
+     * @param   $criteriatype   int     optional    If none supplied, get overall aggregation method
+     * @return  int
+     */
+    public function get_aggregation_method($criteriatype = null) {
+        $params = array(
+            'course'        => $this->course_id,
+            'criteriatype'  => $criteriatype
+        );
+
+        $aggregation = new completion_aggregation($params);
+
+        if (!$aggregation->id) {
+            $aggregation->method = COMPLETION_AGGREGATION_ALL;
+        }
+
+        return $aggregation->method;
+    }
+
+    /**
+     * Get incomplete course completion criteria
+     * @access  public
+     * @return  void
+     */
+    public function get_incomplete_criteria() {
+        $incomplete = array();
+
+        foreach ($this->get_criteria() as $criteria) {
+            if (!$criteria->is_complete()) {
+                $incomplete[] = $criteria;
+            }
+        }
+
+        return $incomplete;
+    }
+
+    /**
+     * Clear old course completion criteria
+     */
+    public function clear_criteria() {
+        global $DB;
+        $DB->delete_records('course_completion_criteria', array('course' => $this->course_id));
+        $DB->delete_records('course_completion_aggr_methd', array('course' => $this->course_id));
+
+        $this->delete_course_completion_data();
+    }
+
+    /**
+     * Has the supplied user completed this course
+     * @access  public
+     * @param   $user_id    int     User's id
+     * @return  boolean
+     */
+    public function is_course_complete($user_id) {
+        $params = array(
+            'userid'    => $user_id,
+            'courseid'  => $this->course_id
+        );
+
+        $ccompletion = new completion_completion($params);
+        return $ccompletion->is_complete();
+    }
+
+    /**
      * Updates (if necessary) the completion state of activity $cm for the given
      * user.
      *
@@ -377,13 +608,12 @@
      * editing form.
      *
      * @global object
-     * @global object
      * @param object $cm Activity
      * @return int The number of users who have completion data stored for this
      *   activity, 0 if none
      */
     public function count_user_data($cm) {
-        global $CFG, $DB;
+        global $DB;
 
         return $DB->get_field_sql("
     SELECT
@@ -395,6 +625,63 @@
     }
 
     /**
+     * Determines how much course completion data exists for a course. This is used when
+     * deciding whether completion information should be 'locked' in the completion
+     * settings form and activity completion settings.
+     *
+     * @global object
+     * @param  int $user_id Optionally only get course completion data for a single user
+     * @return int The number of users who have completion data stored for this
+     *   course, 0 if none
+     */
+    public function count_course_user_data($user_id = null) {
+        global $DB;
+
+        $sql = '
+    SELECT
+        COUNT(1)
+    FROM
+        {course_completion_crit_compl}
+    WHERE
+        course = ?
+        ';
+
+        $params = array($this->course_id);
+
+        // Limit data to a single user if an ID is supplied
+        if ($user_id) {
+            $sql .= ' AND userid = ?';
+            $params[] = $user_id;
+        }
+
+        return $DB->get_field_sql($sql, $params);
+    }
+
+    /**
+     * Check if this course's completion criteria should be locked
+     *
+     * @return  boolean
+     */
+    public function is_course_locked() {
+        return (bool) $this->count_course_user_data();
+    }
+
+    /**
+     * Deletes all course completion completion data.
+     *
+     * Intended to be used when unlocking completion criteria settings.
+     *
+     * @global  object
+     * @return  void
+     */
+    public function delete_course_completion_data() {
+        global $DB;
+
+        $DB->delete_records('course_completions', array('course' => $this->course_id));
+        $DB->delete_records('course_completion_crit_compl', array('course' => $this->course_id));
+    }
+
+    /**
      * Deletes completion state related to an activity for all users.
      * 
      * Intended for use only when the activity itself is deleted.
@@ -416,8 +703,24 @@
 
             unset($SESSION->completioncache[$cm->course][$cm->id]);
         }
+
+        // Check if there is an associated course completion criteria
+        $criteria = $this->get_criteria(COMPLETION_CRITERIA_TYPE_ACTIVITY);
+        $acriteria = false;
+        foreach ($criteria as $criterion) {
+            if ($criterion->moduleinstance == $cm->id) {
+                $acriteria = $criterion;
+                break;
     }
+        }
 
+        if ($acriteria) {
+            // Delete all criteria completions relating to this activity
+            $DB->delete_records('course_completion_crit_compl', array('course' => $this->course_id, 'criteriaid' => $acriteria->id));
+            $DB->delete_records('course_completions', array('course' => $this->course_id));
+        }
+    }
+
     /**
      * Recalculates completion state related to an activity for all users.
      * 
@@ -496,7 +799,7 @@
         // Is this the current user?
         $currentuser = $userid==$USER->id;
 
-        if ($currentuser) {
+        if ($currentuser && is_object($SESSION)) {
             // Make sure cache is present and is for current user (loginas
             // changes this)
             if (!isset($SESSION->completioncache) || $SESSION->completioncacheuserid!=$USER->id) {
@@ -664,11 +967,11 @@
      * @global object
      * @global object
      * @uses CONTEXT_COURSE
-     * @param bool $sortfirstname True to sort with firstname
+     * @param bool $sortfirstname Optional True to sort with firstname
      * @param int $groupid Optionally restrict to groupid
      * @return array Array of user objects containing id, firstname, lastname (empty if none)
      */
-    function internal_get_tracked_users($sortfirstname, $groupid=0) {
+    function internal_get_tracked_users($sortfirstname = false, $groupid = 0) {
         global $CFG, $DB;
 
         if (!empty($CFG->progresstrackedroles)) {
Index: moodle/lib/data_object.php
--- moodle/lib/data_object.php No Base Revision
+++ moodle/lib/data_object.php Locally New
@@ -0,0 +1,309 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ * A data abstraction object that holds methods and attributes
+ * @abstract
+ */
+abstract class data_object {
+    public $table;
+
+    /**
+     * Array of required table fields, must start with 'id'.
+     * @var array $required_fields
+     */
+    public $required_fields = array('id');
+
+    /**
+     * Array of optional fields with default values - usually long text information that is not always needed.
+     * If you want to create an instance without optional fields use: new data_object($only_required_fields, false);
+     * @var array $optional_fields
+     */
+    public $optional_fields = array();
+
+    /**
+     * The PK.
+     * @var int $id
+     */
+    public $id;
+
+    /**
+     * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
+     * @param array $params an array with required parameters for this data object.
+     * @param boolean $fetch Whether to fetch corresponding row from DB or not,
+     *        optional fields might not be defined if false used
+     */
+    public function __construct($params=NULL, $fetch=true) {
+        if (!empty($params) and (is_array($params) or is_object($params))) {
+            if ($fetch) {
+                if ($data = $this->fetch($params)) {
+                    self::set_properties($this, $data);
+                } else {
+                    self::set_properties($this, $this->optional_fields);//apply defaults for optional fields
+                    self::set_properties($this, $params);
+                }
+
+            } else {
+                self::set_properties($this, $params);
+            }
+
+        } else {
+            self::set_properties($this, $this->optional_fields);//apply defaults for optional fields
+        }
+    }
+
+    /**
+     * Makes sure all the optional fields are loaded.
+     * If id present (==instance exists in db) fetches data from db.
+     * Defaults are used for new instances.
+     */
+    public function load_optional_fields() {
+        global $DB;
+        foreach ($this->optional_fields as $field=>$default) {
+            if (array_key_exists($field, $this)) {
+                continue;
+            }
+            if (empty($this->id)) {
+                $this->$field = $default;
+            } else {
+                $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
+            }
+        }
+    }
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static abstract
+     *
+     * @param array $params associative arrays varname=>value
+     * @return object data_object instance or false if none found.
+     */
+    public static abstract function fetch($params);
+
+    /**
+     * Finds and returns all data_object instances based on params.
+     *
+     * @param array $params associative arrays varname=>value
+     * @return array array of data_object insatnces or false if none found.
+     */
+    public static function fetch_all($params) {}
+
+    /**
+     * Factory method - uses the parameters to retrieve matching instance from the DB.
+     * @static final protected
+     * @return mixed object instance or false if not found
+     */
+    protected static function fetch_helper($table, $classname, $params) {
+        if ($instances = self::fetch_all_helper($table, $classname, $params)) {
+            if (count($instances) > 1) {
+                // we should not tolerate any errors here - problems might appear later
+                print_error('morethanonerecordinfetch','debug');
+            }
+            return reset($instances);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Factory method - uses the parameters to retrieve all matching instances from the DB.
+     * @static final protected
+     * @return mixed array of object instances or false if not found
+     */
+    public static function fetch_all_helper($table, $classname, $params) {
+        $instance = new $classname();
+
+        $classvars = (array)$instance;
+        $params    = (array)$params;
+
+        $wheresql = array();
+
+        foreach ($params as $var=>$value) {
+            if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
+                continue;
+            }
+            if (is_null($value)) {
+                $wheresql[] = " $var IS NULL ";
+            } else {
+                $wheresql[] = " $var = ? ";
+                $params[] = $value;
+            }
+        }
+
+        if (empty($wheresql)) {
+            $wheresql = '';
+        } else {
+            $wheresql = implode("AND", $wheresql);
+        }
+
+        global $DB;
+        if ($datas = $DB->get_records_select($table, $wheresql, $params)) {
+            
+            $result = array();
+            foreach($datas as $data) {
+                $instance = new $classname();
+                self::set_properties($instance, $data);
+                $result[$instance->id] = $instance;
+            }
+            return $result;
+
+        } else {
+            
+            return false;
+        }
+    }
+
+    /**
+     * Updates this object in the Database, based on its object variables. ID must be set.
+     * @return boolean success
+     */
+    public function update() {
+        global $DB;
+
+        if (empty($this->id)) {
+            debugging('Can not update data object, no id!');
+            return false;
+        }
+
+        $data = $this->get_record_data();
+
+        $DB->update_record($this->table, $data);
+
+        $this->notify_changed(false);
+        return true;
+    }
+
+    /**
+     * Deletes this object from the database.
+     * @return boolean success
+     */
+    public function delete() {
+        global $DB;
+
+        if (empty($this->id)) {
+            debugging('Can not delete data object, no id!');
+            return false;
+        }
+
+        $data = $this->get_record_data();
+
+        if ($DB->delete_records($this->table, array('id'=>$this->id))) {
+            $this->notify_changed(true);
+            return true;
+
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns object with fields and values that are defined in database
+     */
+    public function get_record_data() {
+        $data = new object();
+
+        foreach ($this as $var=>$value) {
+            if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
+                if (is_object($value) or is_array($value)) {
+                    debugging("Incorrect property '$var' found when inserting data object");
+                } else {
+                    $data->$var = $value;
+                }
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * Records this object in the Database, sets its id to the returned value, and returns that value.
+     * If successful this function also fetches the new object data from database and stores it
+     * in object properties.
+     * @return int PK ID if successful, false otherwise
+     */
+    public function insert() {
+        global $DB;
+
+        if (!empty($this->id)) {
+            debugging("Data object already exists!");
+            return false;
+        }
+
+        $data = $this->get_record_data();
+
+        $this->id = $DB->insert_record($this->table, $data);
+
+        // set all object properties from real db data
+        $this->update_from_db();
+
+        $this->notify_changed(false);
+        return $this->id;
+    }
+
+    /**
+     * Using this object's id field, fetches the matching record in the DB, and looks at
+     * each variable in turn. If the DB has different data, the db's data is used to update
+     * the object. This is different from the update() function, which acts on the DB record
+     * based on the object.
+     */
+    public function update_from_db() {
+        if (empty($this->id)) {
+            debugging("The object could not be used in its state to retrieve a matching record from the DB, because its id field is not set.");
+            return false;
+        }
+        global $DB;
+        if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
+            debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
+            return false;
+        }
+
+        self::set_properties($this, $params);
+
+        return true;
+    }
+
+    /**
+     * Given an associated array or object, cycles through each key/variable
+     * and assigns the value to the corresponding variable in this object.
+     * @static final
+     */
+    public static function set_properties(&$instance, $params) {
+        $params = (array) $params;
+        foreach ($params as $var => $value) {
+            if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
+                $instance->$var = $value;
+            }
+        }
+    }
+
+    /**
+     * Called immediately after the object data has been inserted, updated, or 
+     * deleted in the database. Default does nothing, can be overridden to 
+     * hook in special behaviour.
+     *
+     * @param bool $deleted
+     */
+    function notify_changed($deleted) {
+    }
+}
Index: moodle/lib/db/access.php
--- moodle/lib/db/access.php Base (1.106)
+++ moodle/lib/db/access.php Locally Modified (Based On 1.106)
@@ -1397,7 +1397,18 @@
             'coursecreator' => CAP_ALLOW,
             'admin' => CAP_ALLOW
         )
+    ),
+
+    'moodle/course:markcomplete' => array(
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'legacy' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'coursecreator' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
     )
+    )
 );
 
 
Index: moodle/lib/db/install.xml
--- moodle/lib/db/install.xml Base (1.216)
+++ moodle/lib/db/install.xml Locally Modified (Based On 1.216)
@@ -109,7 +109,8 @@
         <FIELD NAME="enrolenddate" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="enrolstartdate" NEXT="enrol"/>
         <FIELD NAME="enrol" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false" PREVIOUS="enrolenddate" NEXT="defaultrole"/>
         <FIELD NAME="defaultrole" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="The default role given to participants who self-enrol" PREVIOUS="enrol" NEXT="enablecompletion"/>
-        <FIELD NAME="enablecompletion" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 = allow use of 'completion' progress-tracking on this course. 0 = disable completion tracking on this course." PREVIOUS="defaultrole"/>
+        <FIELD NAME="enablecompletion" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 = allow use of 'activty completion' progress-tracking on this course. 0 = disable activity completion tracking on this course." PREVIOUS="defaultrole" NEXT="completionnotify"/>
+        <FIELD NAME="completionnotify" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Notify users when they complete this course" PREVIOUS="enablecompletion"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
@@ -120,7 +121,7 @@
         <INDEX NAME="shortname" UNIQUE="false" FIELDS="shortname" PREVIOUS="idnumber"/>
       </INDEXES>
     </TABLE>
-    <TABLE NAME="course_categories" COMMENT="Course categories" PREVIOUS="course" NEXT="course_display">
+    <TABLE NAME="course_categories" COMMENT="Course categories" PREVIOUS="course" NEXT="course_completion_aggr_methd">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="name"/>
         <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="id" NEXT="description"/>
@@ -139,9 +140,101 @@
         <KEY NAME="parent" TYPE="foreign" FIELDS="parent" REFTABLE="course_categories" REFFIELDS="id" COMMENT="note that to make this recursive FK working someday, the parent field must be declared NULL" PREVIOUS="primary"/>
       </KEYS>
     </TABLE>
-    <TABLE NAME="course_display" COMMENT="Stores info about how to display the course" PREVIOUS="course_categories" NEXT="course_meta">
+    <TABLE NAME="course_completion_aggr_methd" COMMENT="Course completion aggregation methods for criteria" PREVIOUS="course_categories" NEXT="course_completion_criteria">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="criteriatype"/>
+        <FIELD NAME="criteriatype" TYPE="int" LENGTH="20" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="The criteria type we are aggregating, or NULL if complete course aggregation" PREVIOUS="course" NEXT="method"/>
+        <FIELD NAME="method" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 = all, 2 = any, 3 = fraction, 4 = unit" PREVIOUS="criteriatype" NEXT="value"/>
+        <FIELD NAME="value" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" DECIMALS="5" COMMENT="NULL = all/any, 0..1 for method 'fraction', > 0 for method 'unit'" PREVIOUS="method"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course" NEXT="criteriatype"/>
+        <INDEX NAME="criteriatype" UNIQUE="false" FIELDS="criteriatype" PREVIOUS="course"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="course_completion_criteria" COMMENT="Course completion criteria" PREVIOUS="course_completion_aggr_methd" NEXT="course_completion_crit_compl">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="criteriatype"/>
+        <FIELD NAME="criteriatype" TYPE="int" LENGTH="20" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Type of criteria" PREVIOUS="course" NEXT="module"/>
+        <FIELD NAME="module" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Type of module (if using module criteria type)" PREVIOUS="criteriatype" NEXT="moduleinstance"/>
+        <FIELD NAME="moduleinstance" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Module instance id (if using module criteria type)" PREVIOUS="module" NEXT="enrolperiod"/>
+        <FIELD NAME="enrolperiod" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Number of days after enrolment the course is completed (if using enrolperiod criteria type)" PREVIOUS="moduleinstance" NEXT="date"/>
+        <FIELD NAME="date" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Timestamp of the date for course completion (if using date criteria type)" PREVIOUS="enrolperiod" NEXT="gradepass"/>
+        <FIELD NAME="gradepass" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" DECIMALS="5" COMMENT="The minimum grade needed to pass the course (if passing grade criteria enabled)" PREVIOUS="date" NEXT="role"/>
+        <FIELD NAME="role" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="gradepass" NEXT="lock"/>
+        <FIELD NAME="lock" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" COMMENT="Date this criteria was locked" SEQUENCE="false" PREVIOUS="role"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course" NEXT="lock"/>
+        <INDEX NAME="lock" UNIQUE="false" FIELDS="lock" PREVIOUS="course"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="course_completion_crit_compl" COMMENT="Course completion user records" PREVIOUS="course_completion_criteria" NEXT="course_completion_notify">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="userid" NEXT="criteriaid"/>
+        <FIELD NAME="criteriaid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="Completion criteria this references" PREVIOUS="course" NEXT="gradefinal"/>
+        <FIELD NAME="gradefinal" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" DECIMALS="5" COMMENT="The final grade for the course (included regardless of whether a passing grade was required)" PREVIOUS="criteriaid" NEXT="unenroled"/>
+        <FIELD NAME="unenroled" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Timestamp when the user was unenroled" PREVIOUS="gradefinal" NEXT="deleted"/>
+        <FIELD NAME="deleted" TYPE="int" LENGTH="1" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="unenroled" NEXT="timecompleted"/>
+        <FIELD NAME="timecompleted" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="deleted"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="userid" UNIQUE="false" FIELDS="userid" NEXT="course"/>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course" PREVIOUS="userid" NEXT="criteriaid"/>
+        <INDEX NAME="criteriaid" UNIQUE="false" FIELDS="criteriaid" PREVIOUS="course" NEXT="timecompleted"/>
+        <INDEX NAME="timecompleted" UNIQUE="false" FIELDS="timecompleted" PREVIOUS="criteriaid"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="course_completion_notify" COMMENT="Course completion notification emails" PREVIOUS="course_completion_crit_compl" NEXT="course_completions">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="role"/>
+        <FIELD NAME="role" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="ID of the role to receive this notification message when a course has been completed" PREVIOUS="course" NEXT="message"/>
+        <FIELD NAME="message" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" COMMENT="HTML formatted message to be sent" PREVIOUS="role" NEXT="timesent"/>
+        <FIELD NAME="timesent" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="message"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="course_completions" COMMENT="Course completion records" PREVIOUS="course_completion_notify" NEXT="course_display">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="userid" NEXT="deleted"/>
+        <FIELD NAME="deleted" TYPE="int" LENGTH="1" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="course" NEXT="timenotified"/>
+        <FIELD NAME="timenotified" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="deleted" NEXT="timeenroled"/>
+        <FIELD NAME="timeenroled" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timenotified" NEXT="timecompleted"/>
+        <FIELD NAME="timecompleted" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="timeenroled"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="userid" UNIQUE="false" FIELDS="userid" NEXT="course"/>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course" PREVIOUS="userid" NEXT="timecompleted"/>
+        <INDEX NAME="timecompleted" UNIQUE="false" FIELDS="timecompleted" PREVIOUS="course"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="course_display" COMMENT="Stores info about how to display the course" PREVIOUS="course_completions" NEXT="course_meta">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="course"/>
\ No newline at end of file
         <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="userid"/>
         <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="course" NEXT="display"/>
         <FIELD NAME="display" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="userid"/>
Index: moodle/lib/db/upgrade.php
--- moodle/lib/db/upgrade.php Base (1.336)
+++ moodle/lib/db/upgrade.php Locally Modified (Based On 1.336)
@@ -2595,6 +2595,158 @@
         upgrade_main_savepoint($result, 2009091310);
     }
 
+
+    if ($result && $oldversion < 2009091800) {
+
+    /// Add course completion tables
+    /// Define table course_completion_aggr_methd to be created
+        $table = new xmldb_table('course_completion_aggr_methd');
+
+    /// Adding fields to table course_completion_aggr_methd
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('criteriatype', XMLDB_TYPE_INTEGER, '20', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('method', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('value', XMLDB_TYPE_NUMBER, '10, 5', null, null, null, null);
+
+    /// Adding keys to table course_completion_aggr_methd
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Adding indexes to table course_completion_aggr_methd
+        $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+        $table->add_index('criteriatype', XMLDB_INDEX_NOTUNIQUE, array('criteriatype'));
+
+    /// Conditionally launch create table for course_completion_aggr_methd
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+
+    /// Define table course_completion_criteria to be created
+        $table = new xmldb_table('course_completion_criteria');
+
+    /// Adding fields to table course_completion_criteria
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('criteriatype', XMLDB_TYPE_INTEGER, '20', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('module', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+        $table->add_field('moduleinstance', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('enrolperiod', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('date', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('gradepass', XMLDB_TYPE_NUMBER, '10, 5', null, null, null, null);
+        $table->add_field('role', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('lock', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+
+    /// Adding keys to table course_completion_criteria
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Adding indexes to table course_completion_criteria
+        $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+        $table->add_index('lock', XMLDB_INDEX_NOTUNIQUE, array('lock'));
+
+    /// Conditionally launch create table for course_completion_criteria
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+
+    /// Define table course_completion_crit_compl to be created
+        $table = new xmldb_table('course_completion_crit_compl');
+
+    /// Adding fields to table course_completion_crit_compl
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('criteriaid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('gradefinal', XMLDB_TYPE_NUMBER, '10, 5', null, null, null, null);
+        $table->add_field('unenroled', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('deleted', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('timecompleted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+
+    /// Adding keys to table course_completion_crit_compl
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Adding indexes to table course_completion_crit_compl
+        $table->add_index('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
+        $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+        $table->add_index('criteriaid', XMLDB_INDEX_NOTUNIQUE, array('criteriaid'));
+        $table->add_index('timecompleted', XMLDB_INDEX_NOTUNIQUE, array('timecompleted'));
+
+    /// Conditionally launch create table for course_completion_crit_compl
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+
+    /// Define table course_completion_notify to be created
+        $table = new xmldb_table('course_completion_notify');
+
+    /// Adding fields to table course_completion_notify
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('role', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('message', XMLDB_TYPE_TEXT, 'small', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timesent', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+
+    /// Adding keys to table course_completion_notify
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Adding indexes to table course_completion_notify
+        $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+
+    /// Conditionally launch create table for course_completion_notify
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+    /// Define table course_completions to be created
+        $table = new xmldb_table('course_completions');
+
+    /// Adding fields to table course_completions
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('deleted', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('timenotified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('timeenroled', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timecompleted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+
+    /// Adding keys to table course_completions
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+    /// Adding indexes to table course_completions
+        $table->add_index('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
+        $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+        $table->add_index('timecompleted', XMLDB_INDEX_NOTUNIQUE, array('timecompleted'));
+
+    /// Conditionally launch create table for course_completions
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+
+    /// Add cols to course table
+    /// Define field enablecompletion to be added to course
+        $table = new xmldb_table('course');
+        $field = new xmldb_field('enablecompletion', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'defaultrole');
+
+    /// Conditionally launch add field enablecompletion
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+    /// Define field completionnotify to be added to course
+        $field = new xmldb_field('completionnotify', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'enablecompletion');
+
+    /// Conditionally launch add field completionnotify
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+
+        upgrade_main_savepoint($result, 2009091800);
+    }
+
     return $result;
 }
 
Index: moodle/lib/grade/grade_object.php
--- moodle/lib/grade/grade_object.php Base (1.47)
+++ moodle/lib/grade/grade_object.php Locally Modified (Based On 1.47)
@@ -23,12 +23,14 @@
 //                                                                       //
 ///////////////////////////////////////////////////////////////////////////
 
+require_once($CFG->libdir.'/data_object.php');
+
+
 /**
  * An abstract object that holds methods and attributes common to all grade_* objects defined here.
  * @abstract
  */
-abstract class grade_object {
-    public $table;
+abstract class grade_object extends data_object {
 
     /**
      * Array of required table fields, must start with 'id'.
@@ -37,19 +39,6 @@
     public $required_fields = array('id', 'timecreated', 'timemodified');
 
     /**
-     * Array of optional fields with default values - usually long text information that is not always needed.
-     * If you want to create an instance without optional fields use: new grade_object($only_required_fields, false);
-     * @var array $optional_fields
-     */
-    public $optional_fields = array();
-
-    /**
-     * The PK.
-     * @var int $id
-     */
-    public $id;
-
-    /**
      * The first time this grade_object was created.
      * @var int $timecreated
      */
@@ -62,133 +51,6 @@
     public $timemodified;
 
     /**
-     * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
-     * @param array $params an array with required parameters for this grade object.
-     * @param boolean $fetch Whether to fetch corresponding row from DB or not,
-     *        optional fields might not be defined if false used
-     */
-    public function __construct($params=NULL, $fetch=true) {
-        if (!empty($params) and (is_array($params) or is_object($params))) {
-            if ($fetch) {
-                if ($data = $this->fetch($params)) {
-                    grade_object::set_properties($this, $data);
-                } else {
-                    grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
-                    grade_object::set_properties($this, $params);
-                }
-
-            } else {
-                grade_object::set_properties($this, $params);
-            }
-
-        } else {
-            grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
-        }
-    }
-
-    /**
-     * Makes sure all the optional fields are loaded.
-     * If id present (==instance exists in db) fetches data from db.
-     * Defaults are used for new instances.
-     */
-    public function load_optional_fields() {
-        global $DB;
-        foreach ($this->optional_fields as $field=>$default) {
-            if (array_key_exists($field, $this)) {
-                continue;
-            }
-            if (empty($this->id)) {
-                $this->$field = $default;
-            } else {
-                $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
-            }
-        }
-    }
-
-    /**
-     * Finds and returns a grade_object instance based on params.
-     * @static abstract
-     *
-     * @param array $params associative arrays varname=>value
-     * @return object grade_object instance or false if none found.
-     */
-    public static abstract function fetch($params);
-
-    /**
-     * Finds and returns all grade_object instances based on params.
-     * @static abstract
-     *
-     * @param array $params associative arrays varname=>value
-     * @return array array of grade_object insatnces or false if none found.
-     */
-    public static abstract function fetch_all($params);
-
-    /**
-     * Factory method - uses the parameters to retrieve matching instance from the DB.
-     * @static final protected
-     * @return mixed object instance or false if not found
-     */
-    protected static function fetch_helper($table, $classname, $params) {
-        if ($instances = grade_object::fetch_all_helper($table, $classname, $params)) {
-            if (count($instances) > 1) {
-                // we should not tolerate any errors here - problems might appear later
-                print_error('morethanonerecordinfetch','debug');
-            }
-            return reset($instances);
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Factory method - uses the parameters to retrieve all matching instances from the DB.
-     * @static final protected
-     * @return mixed array of object instances or false if not found
-     */
-    public static function fetch_all_helper($table, $classname, $params) {
-        $instance = new $classname();
-
-        $classvars = (array)$instance;
-        $params    = (array)$params;
-
-        $wheresql = array();
-
-        foreach ($params as $var=>$value) {
-            if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
-                continue;
-            }
-            if (is_null($value)) {
-                $wheresql[] = " $var IS NULL ";
-            } else {
-                $wheresql[] = " $var = ? ";
-                $params[] = $value;
-            }
-        }
-
-        if (empty($wheresql)) {
-            $wheresql = '';
-        } else {
-            $wheresql = implode("AND", $wheresql);
-        }
-
-        global $DB;
-        if ($datas = $DB->get_records_select($table, $wheresql, $params)) {
-            
-            $result = array();
-            foreach($datas as $data) {
-                $instance = new $classname();
-                grade_object::set_properties($instance, $data);
-                $result[$instance->id] = $instance;
-            }
-            return $result;
-
-        } else {
-            
-            return false;
-        }
-    }
-
-    /**
      * Updates this object in the Database, based on its object variables. ID must be set.
      * @param string $source from where was the object updated (mod/forum, manual, etc.)
      * @return boolean success
@@ -254,24 +116,6 @@
     }
 
     /**
-     * Returns object with fields and values that are defined in database
-     */
-    public function get_record_data() {
-        $data = new object();
-
-        foreach ($this as $var=>$value) {
-            if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
-                if (is_object($value) or is_array($value)) {
-                    debugging("Incorrect property '$var' found when inserting grade object");
-                } else {
-                    $data->$var = $value;
-                }
-            }
-        }
-        return $data;
-    }
-
-    /**
      * Records this object in the Database, sets its id to the returned value, and returns that value.
      * If successful this function also fetches the new object data from database and stores it
      * in object properties.
@@ -308,51 +152,5 @@
         $this->notify_changed(false);
         return $this->id;
     }
-
-    /**
-     * Using this object's id field, fetches the matching record in the DB, and looks at
-     * each variable in turn. If the DB has different data, the db's data is used to update
-     * the object. This is different from the update() function, which acts on the DB record
-     * based on the object.
-     */
-    public function update_from_db() {
-        if (empty($this->id)) {
-            debugging("The object could not be used in its state to retrieve a matching record from the DB, because its id field is not set.");
-            return false;
         }
-        global $DB;
-        if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
-            debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
-            return false;
-        }
-
-        grade_object::set_properties($this, $params);
-
-        return true;
-    }
-
-    /**
-     * Given an associated array or object, cycles through each key/variable
-     * and assigns the value to the corresponding variable in this object.
-     * @static final
-     */
-    public static function set_properties(&$instance, $params) {
-        $params = (array) $params;
-        foreach ($params as $var => $value) {
-            if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
-                $instance->$var = $value;
-            }
-        }
-    }
-
-    /**
-     * Called immediately after the object data has been inserted, updated, or 
-     * deleted in the database. Default does nothing, can be overridden to 
-     * hook in special behaviour.
-     *
-     * @param bool $deleted
-     */
-    function notify_changed($deleted) {
-    }
-}
 ?>
Index: moodle/lib/navigationlib.php
--- moodle/lib/navigationlib.php Base (1.24)
+++ moodle/lib/navigationlib.php Locally Modified (Based On 1.24)
@@ -2479,6 +2479,12 @@
             $coursenode->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, $OUTPUT->old_icon_url('i/settings'));
         }
         
+        // Add controls for course completion
+        if (!empty($course->enablecompletion) && $course->id !== SITEID && has_capability('moodle/course:update', $course->context)) {
+            $url = new moodle_url($CFG->wwwroot.'/course/completion.php', array('id'=>$course->id));
+            $coursenode->add(get_string('completionmenuitem', 'completion'), $url, self::TYPE_SETTING, null, null, $OUTPUT->old_icon_url('i/settings'));
+        }
+        
         // Add assign or override roles if allowed
         if (has_capability('moodle/role:assign', $course->context)) {
             $url = new moodle_url($CFG->wwwroot.'/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$course->context->id));
Index: moodle/lib/simpletest/testcompletioncron.php
--- moodle/lib/simpletest/testcompletioncron.php No Base Revision
+++ moodle/lib/simpletest/testcompletioncron.php Locally New
@@ -0,0 +1,85 @@
+<?php  // $Id$
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.org                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+global $CFG;
+require_once($CFG->libdir.'/completion/cron.php');
+
+/**
+ * Unit tests for (some of) the course completion cron job
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package formslib
+ */
+class completetion_cron_test extends UnitTestCase {
+    private $element;
+    public  static $includecoverage = array('lib/completion/cron.php');
+
+    function test_aggregation() {
+        // Test criteria aggregation
+
+        // Test COMPLETION_AGGREGATION_ANY
+        $status = null;
+        $method = COMPLETION_AGGREGATION_ANY;
+
+        completion_cron_aggregate($method, false, $status);
+        $this->assertEqual($status, false);
+
+        completion_cron_aggregate($method, false, $status);
+        $this->assertEqual($status, false);
+
+        completion_cron_aggregate($method, true, $status);
+        $this->assertEqual($status, true);
+
+        $status = null;
+
+        completion_cron_aggregate($method, true, $status);
+        $this->assertEqual($status, true);
+
+        completion_cron_aggregate($method, false, $status);
+        $this->assertEqual($status, true);
+
+        // Test COMPLETION_AGGREGATION_ALL
+        $status = null;
+        $method = COMPLETION_AGGREGATION_ALL;
+
+        completion_cron_aggregate($method, false, $status);
+        $this->assertEqual($status, false);
+
+        completion_cron_aggregate($method, true, $status);
+        $this->assertEqual($status, false);
+
+        $status = null;
+
+        completion_cron_aggregate($method, true, $status);
+        $this->assertEqual($status, true);
+
+        completion_cron_aggregate($method, false, $status);
+        $this->assertEqual($status, false);
+    }
+}
Index: moodle/version.php
--- moodle/version.php Base (1.1259)
+++ moodle/version.php Locally Modified (Based On 1.1259)
@@ -6,7 +6,7 @@
 // This is compared against the values stored in the database to determine
 // whether upgrades should be performed (see lib/db/*.php)
 
-    $version = 2009091700;  // YYYYMMDD   = date of the last version bump
+    $version = 2009091800;  // YYYYMMDD   = date of the last version bump
                             //         XX = daily increments
 
     $release = '2.0 dev (Build: 20090922)';  // Human-friendly version name
