# 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.175)
+++ moodle/admin/cron.php Locally Modified (Based On 1.175)
@@ -242,6 +242,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/admin/settings/courses.php
--- moodle/admin/settings/courses.php Base (1.41)
+++ moodle/admin/settings/courses.php Locally Modified (Based On 1.41)
@@ -109,11 +109,11 @@
     $languages += get_string_manager()->get_list_of_translations();
     $temp->add(new admin_setting_configselect('moodlecourse/lang', get_string('forcelanguage'), '',key($languages),$languages));
 
-    if (!empty($CFG->enablecompletion)) {
         $temp->add(new admin_setting_heading('progress', get_string('progress','completion'), ''));
         $temp->add(new admin_setting_configselect('moodlecourse/enablecompletion', get_string('completion','completion'), '',
             1, array(0 => get_string('completiondisabled','completion'), 1 => get_string('completionenabled','completion'))));
-    }
+
+    $temp->add(new admin_setting_configcheckbox('moodlecourse/completionstartonenrol', get_string('completionstartonenrol','completion'), get_string('completionstartonenrolhelp', 'completion'), 1));
     $ADMIN->add('courses', $temp);
 
 /// "courserequests" settingpage
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,198 @@
+<?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, $DB, $COURSE;
+
+        // 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))) {
+
+            // If not enrolled, but are can view the report:
+            if (has_capability('coursereport/completion:view', get_context_instance(CONTEXT_COURSE, $COURSE->id))) {
+                $this->content->text = '<a href="'.$CFG->wwwroot.'/course/report/completion/index.php?course='.$COURSE->id.
+                                       '">'.get_string('viewcoursereport', 'completion').'</a>';
+                return $this->content;
+            }
+
+            // Otherwise, show error
+            $this->content->text = get_string('notenroled', 'completion');
+            return $this->content;
+        }
+
+        // Generate markup for criteria statuses
+        $shtml = '';
+
+        // For aggregating activity completion
+        $activities = array();
+        $activities_complete = 0;
+
+        // For aggregating course prerequisites
+        $prerequisites = array();
+        $prerequisites_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;
+            }
+
+            // Prerequisites are also a special case, so cache them and leave them till last
+            if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) {
+                $prerequisites[$criteria->courseinstance] = $complete;
+
+                if ($complete) {
+                    $prerequisites_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 .= get_string('activitiescompleted', 'completion');
+            $shtml .= '</td><td style="text-align: right">';
+            $shtml .= $activities_complete.' of '.count($activities);
+            $shtml .= '</td></tr>';
+        }
+
+        // Aggregate prerequisites
+        if (!empty($prerequisites)) {
+
+            $phtml  = '<tr><td>';
+            $phtml .= get_string('prerequisitescompleted', 'completion');
+            $phtml .= '</td><td style="text-align: right">';
+            $phtml .= $prerequisites_complete.' of '.count($prerequisites);
+            $phtml .= '</td></tr>';
+
+            $shtml = $phtml . $shtml;
+        }
+
+        // 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>';
+        $this->content->text .= '<tr><td><b>'.get_string('requiredcriteria', 'completion').'</b></td><td style="text-align: right"><b>'.get_string('status').'</b></td></tr>';
+        $this->content->text .= $shtml.'</tbody></table>';
+
+        // Display link to detailed view
+        $this->content->footer = '<br><a href="'.$CFG->wwwroot.'/blocks/completionstatus/details.php?course='.$COURSE->id.'">More details</a>';
+
+        return $this->content;
+    }
+}
Index: moodle/blocks/completionstatus/details.php
--- moodle/blocks/completionstatus/details.php No Base Revision
+++ moodle/blocks/completionstatus/details.php Locally New
@@ -0,0 +1,250 @@
+<?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('../../config.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+
+///
+/// Load data
+///
+$id = required_param('course', PARAM_INT);
+// User id
+$userid = optional_param('user', 0, PARAM_INT);
+
+// Load course
+$course = $DB->get_record('course', array('id' => $id);
+
+// Load user
+if ($userid) {
+    if (!$user = get_record('user', 'id', $userid)) {
+        error('User ID incorrect');
+    }
+} else {
+    $user =& $USER;
+}
+
+
+// Check permissions
+require_login($course);
+
+$coursecontext   = get_context_instance(CONTEXT_COURSE, $course->id);
+$personalcontext = get_context_instance(CONTEXT_USER, $user->id);
+
+$can_view = false;
+
+// Can view own report
+if ($USER->id == $user->id) {
+    $can_view = true;
+}
+elseif (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext)) {
+    $can_view = true;
+}
+elseif (has_capability('coursereport/completion:view', $coursecontext)) {
+    $can_view = true;
+}
+elseif (has_capability('coursereport/completion:view', $personalcontext)) {
+    $can_view = true;
+}
+
+if (!$can_view) {
+    error('You do not have permissions to view this report');
+}
+
+
+// Don't display if completion isn't enabled!
+if (!$course->enablecompletion) {
+    error('completion not enabled');
+}
+
+// Load criteria to display
+$info = new completion_info($course);
+$completions = $info->get_completions($user->id);
+
+// Check if this course has any criteria
+if (empty($completions)) {
+    error('no criteria');
+}
+
+// Check this user is enroled
+$users = $info->internal_get_tracked_users(true);
+if (!in_array($user->id, array_keys($users))) {
+    error(get_string('notenroled', 'completion'));
+}
+
+
+///
+/// Display page
+///
+
+// Print header
+$page = get_string('completionprogressdetails', 'block_completionstatus');
+$title = format_string($course->fullname) . ': ' . $page;
+
+$navlinks[] = array('name' => $page, 'link' => null, 'type' => 'misc');
+$navigation = build_navigation($navlinks);
+
+print_header($title, $title, $navigation);
+print_heading($title);
+
+
+// Display completion status
+echo '<table class="generalbox boxaligncenter"><tbody>';
+
+// If not display logged in user, show user name
+if ($USER->id != $user->id) {
+    echo '<tr><td colspan="2"><b>'.get_string('showinguser', 'completion').'</b>: ';
+    echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$user->id.'&course='.$course->id.'">'.fullname($user).'</a>';
+    echo '</td></tr>';
+}
+
+echo '<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 ($coursecomplete) {
+    echo get_string('complete');
+} else if (!$criteriacomplete) {
+    echo '<i>'.get_string('notyetstarted', 'completion').'</i>';
+} else {
+    echo '<i>'.get_string('inprogress','completion').'</i>';
+}
+
+echo '</td></tr>';
+echo '<tr><td colspan="2"><b>'.get_string('required').':</b> ';
+
+// Get overall aggregation method
+$overall = $info->get_aggregation_method();
+
+if ($overall == COMPLETION_AGGREGATION_ALL) {
+    echo get_string('criteriarequiredall', 'completion');
+} else {
+    echo get_string('criteriarequiredany', 'completion');
+}
+
+echo '</td></tr></tbody></table>';
+
+// Generate markup for criteria statuses
+echo '<table class="generalbox boxaligncenter" cellpadding="3"><tbody>';
+echo '<tr class="ccheader">';
+echo '<th class="c0 header" scope="col">'.get_string('criteriagroup', 'block_completionstatus').'</th>';
+echo '<th class="c1 header" scope="col">'.get_string('criteria', 'completion').'</th>';
+echo '<th class="c2 header" scope="col">'.get_string('requirement', 'block_completionstatus').'</th>';
+echo '<th class="c3 header" scope="col">'.get_string('status').'</th>';
+echo '<th class="c4 header" scope="col">'.get_string('complete').'</th>';
+echo '<th class="c5 header" scope="col">'.get_string('completiondate', 'coursereport_completion').'</th>';
+echo '</tr>';
+
+// Save row data
+$rows = array();
+
+global $COMPLETION_CRITERIA_TYPES;
+
+// Loop through course criteria
+foreach ($completions as $completion) {
+    $criteria = $completion->get_criteria();
+    $complete = $completion->is_complete();
+
+    $row = array();
+    $row['type'] = $criteria->criteriatype;
+    $row['title'] = $criteria->get_title();
+    $row['status'] = $completion->get_status();
+    $row['timecompleted'] = $completion->timecompleted;
+    $row['details'] = $criteria->get_details($completion);
+    $rows[] = $row;
+}
+
+// Print table
+$last_type = '';
+$agg_type = false;
+
+foreach ($rows as $row) {
+
+    // Criteria group
+    echo '<td class="c0">';
+    if ($last_type !== $row['details']['type']) {
+        $last_type = $row['details']['type'];
+        echo $last_type;
+
+        // Reset agg type
+        $agg_type = true;
+    } else {
+        // Display aggregation type
+        if ($agg_type) {
+            $agg = $info->get_aggregation_method($row['type']);
+
+            echo '(<i>';
+
+            if ($agg == COMPLETION_AGGREGATION_ALL) {
+                echo strtolower(get_string('all', 'completion'));
+            } else {
+                echo strtolower(get_string('any', 'completion'));
+            }
+
+            echo '</i> '.strtolower(get_string('required')).')';
+            $agg_type = false;
+        }
+    }
+    echo '</td>';
+
+    // Criteria title
+    echo '<td class="c1">';
+    echo $row['details']['criteria'];
+    echo '</td>';
+
+    // Requirement
+    echo '<td class="c2">';
+    echo $row['details']['requirement'];
+    echo '</td>';
+
+    // Status
+    echo '<td class="c3">';
+    echo $row['details']['status'];
+    echo '</td>';
+
+    // Is complete
+    echo '<td class="c4">';
+    echo ($row['status'] === 'Yes') ? 'Yes' : 'No';
+    echo '</td>';
+
+    // Completion data
+    echo '<td class="c5">';
+    if ($row['timecompleted']) {
+        echo userdate($row['timecompleted'], '%e %B %G');
+    } else {
+        echo '-';
+    }
+    echo '</td>';
+    echo '</tr>';
+}
+
+echo '</tbody></table>';
+
+print_footer($course);
Index: moodle/blocks/completionstatus/lang/en/block_completionstatus.php
--- moodle/blocks/completionstatus/lang/en/block_completionstatus.php No Base Revision
+++ moodle/blocks/completionstatus/lang/en/block_completionstatus.php Locally New
@@ -0,0 +1,7 @@
+<?php
+
+$string['completionprogressdetails'] = 'Completion progress details';
+$string['completionstatus'] = 'Course Completion Status';
+$string['pluginname'] = 'Course Completion Status';
+$string['criteriagroup'] = 'Criteria group';
+$string['requirement'] = 'Requirement';
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/blocks/selfcompletion/lang/en/block_selfcompletion.php
--- moodle/blocks/selfcompletion/lang/en/block_selfcompletion.php No Base Revision
+++ moodle/blocks/selfcompletion/lang/en/block_selfcompletion.php Locally New
@@ -0,0 +1,5 @@
+<?php
+
+$string['selfcompletion'] = 'Self Completion';
+$string['pluginname'] = 'Self Completion';
+$string['completecourse'] = 'Complete Course';
Index: moodle/course/completion.php
--- moodle/course/completion.php No Base Revision
+++ moodle/course/completion.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                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+// 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($CFG->libdir.'/completion/completion_criteria_course.php');
+require_once $CFG->libdir.'/gradelib.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();
+
+    // Course aggregation
+    if (empty($data->course_aggregation)) {
+        $data->course_aggregation = 0;
+    }
+
+    $aggregation = new completion_aggregation();
+    $aggregation->course = $data->id;
+    $aggregation->criteriatype = COMPLETION_CRITERIA_TYPE_COURSE;
+    $aggregation->setMethod($data->course_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();
+
+    // Update course total passing grade
+    $grade_item = grade_category::fetch_course_category($course->id)->grade_item;
+    $grade_item->gradepass = $data->criteria_grade_value;
+    $grade_item->update('course/completion.php');
+
+    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,214 @@
+<?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, $js_enabled;
+
+        $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());
+
+        // Course prerequisite completion criteria
+        $mform->addElement('header', 'courseprerequisites', get_string('courseprerequisites', 'completion'));
+
+        // Get applicable courses
+        $courses = $DB->get_records_sql(
+            "
+                SELECT DISTINCT
+                    c.id,
+                    c.category,
+                    c.fullname,
+                    cc.id AS selected
+                FROM
+                    {course} c
+                LEFT JOIN
+                    {course_completion_criteria} cc
+                 ON cc.courseinstance = c.id
+                AND cc.course = {$course->id}
+                INNER JOIN
+                    {course_completion_criteria} ccc
+                 ON ccc.course = c.id
+                WHERE
+                    c.enablecompletion = ".COMPLETION_ENABLED."
+                AND c.id <> {$course->id}
+            "
+        );
+
+        if (!empty($courses)) {
+            if (count($courses) > 1) {
+                $mform->addElement('select', 'course_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+                $mform->setDefault('course_aggregation', $completion->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE));
+            }
+
+            // Get category list
+            $list = array();
+            $parents = array();
+            make_categories_list($list, $parents);
+
+            // Get course list for select box
+            $selectbox = array();
+            $selected = array();
+            foreach ($courses as $c) {
+                $selectbox[$c->id] = $list[$c->category] . ' / ' . s($c->fullname);
+
+                // If already selected
+                if ($c->selected) {
+                    $selected[] = $c->id;
+                }
+            }
+
+            // Show multiselect box
+            $mform->addElement('select', 'criteria_course', get_string('coursesavailable', 'completion'), $selectbox, array('multiple' => 'multiple', 'size' => 6));
+
+            // Select current criteria
+            $mform->setDefault('criteria_course', $selected);
+
+            // Explain list
+            $mform->addElement('static', 'criteria_courses_explaination', '', get_string('coursesavailableexplaination', 'completion'));
+
+        } else {
+            $mform->addElement('static', 'nocourses', '', get_string('err_nocourses', 'completion'));
+        }
+
+        // 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'));
+
+        // Grade enable and passing grade
+        $course_grade = $DB->get_field('grade_items', 'gradepass', array('courseid' => $course->id, 'itemtype' => 'course'));
+        $criteria = new completion_criteria_grade($params);
+        $criteria->config_form_display($mform, $course_grade);
+
+        // 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/edit_form.php
--- moodle/course/edit_form.php Base (1.79)
+++ moodle/course/edit_form.php Locally Modified (Based On 1.79)
@@ -376,10 +376,18 @@
             $mform->addElement('select', 'enablecompletion', get_string('completion','completion'),
                 array(0=>get_string('completiondisabled','completion'), 1=>get_string('completionenabled','completion')));
             $mform->setDefault('enablecompletion', $courseconfig->enablecompletion);
+
+            $mform->addElement('checkbox', 'completionstartonenrol', get_string('completionstartonenrol', 'completion'));
+            $mform->setDefault('completionstartonenrol', $courseconfig->completionstartonenrol);
+            $mform->disabledIf('completionstartonenrol', 'enablecompletion', 'eq', 0);
         } else {
             $mform->addElement('hidden', 'enablecompletion');
             $mform->setType('enablecompletion', PARAM_INT);
             $mform->setDefault('enablecompletion',0);
+
+            $mform->addElement('hidden', 'completionstartonenrol');
+            $mform->setType('completionstartonenrol', PARAM_INT);
+            $mform->setDefault('completionstartonenrol',0);
         }
 
 //--------------------------------------------------------------------------------
Index: moodle/course/report/completion/db/access.php
--- moodle/course/report/completion/db/access.php No Base Revision
+++ moodle/course/report/completion/db/access.php Locally New
@@ -0,0 +1,42 @@
+<?php  // $Id$
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards  Martin Dougiamas  http://moodle.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                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+$coursereport_completion_capabilities = array(
+
+    'coursereport/completion:view' => array(
+        'riskbitmask' => RISK_PERSONAL,
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_COURSE,
+        'legacy' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'admin' => CAP_ALLOW
+        ),
+
+        'clonepermissionsfrom' => 'moodle/site:viewreports',
+    )
+);
+
+?>
Index: moodle/course/report/completion/index.php
--- moodle/course/report/completion/index.php No Base Revision
+++ moodle/course/report/completion/index.php Locally New
@@ -0,0 +1,624 @@
+<?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 progress report
+ *
+ * @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('../../../config.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+
+/**
+ * Configuration
+ */
+define('COMPLETION_REPORT_PAGE',        25);
+define('COMPLETION_REPORT_COL_TITLES',  true);
+
+
+/**
+ * Setup page, check permissions
+ */
+
+// Get course
+$course = $DB->get_record('course', array('id' => required_param('course', PARAM_INT)));
+if(!$course) {
+    print_error('invalidcourseid');
+}
+
+// Non-js edit
+$edituser = optional_param('edituser', 0, PARAM_INT);
+
+// Sort (default lastname, optionally firstname)
+$sort = optional_param('sort','',PARAM_ALPHA);
+$firstnamesort = $sort == 'firstname';
+
+// CSV format
+$format = optional_param('format','',PARAM_ALPHA);
+$excel = $format == 'excelcsv';
+$csv = $format == 'csv' || $excel;
+
+// Whether to start at a particular position
+$start = optional_param('start',0,PARAM_INT);
+
+// Whether to show idnumber
+$idnumbers = $CFG->grade_report_showuseridnumber;
+
+// Function for quoting csv cell values
+function csv_quote($value) {
+    global $excel;
+    if($excel) {
+        $tl=textlib_get_instance();
+        return $tl->convert('"'.str_replace('"',"'",$value).'"','UTF-8','UTF-16LE');
+    } else {
+        return '"'.str_replace('"',"'",$value).'"';
+    }
+}
+
+
+// Check permissions
+require_login($course);
+
+$context=get_context_instance(CONTEXT_COURSE, $course->id);
+require_capability('coursereport/completion:view', $context);
+
+// Get group mode
+$group = groups_get_course_group($course, true); // Supposed to verify group
+if($group === 0 && $course->groupmode == SEPARATEGROUPS) {
+    require_capability('moodle/site:accessallgroups',$context);
+}
+
+
+$url = new moodle_url('/course/report/completion/index.php', array('course'=>$course->id));
+$PAGE->set_url($url);
+
+/**
+ * Load data
+ */
+
+// Get criteria for course
+$completion = new completion_info($course);
+
+if (!$completion->has_criteria()) {
+    print_error('err_nocriteria', 'completion', $CFG->wwwroot.'/course/report.php?id='.$course->id);
+}
+
+// Get criteria and put in correct order
+$criteria = array();
+
+foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_COURSE) as $criterion) {
+    $criteria[] = $criterion;
+}
+
+foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_ACTIVITY) as $criterion) {
+    $criteria[] = $criterion;
+}
+
+foreach ($completion->get_criteria() as $criterion) {
+    if (!in_array($criterion->criteriatype, array(
+            COMPLETION_CRITERIA_TYPE_COURSE, COMPLETION_CRITERIA_TYPE_ACTIVITY))) {
+        $criteria[] = $criterion;
+    }
+}
+
+// Can logged in user 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 (!$csv) {
+    // 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 ($users && in_array($USER->id, array_keys($users))) {
+                $allow_marking = true;
+                $allow_marking_criteria = $rcriterion->id;
+                break;
+            }
+        }
+    }
+}
+
+// Get user data
+$progress = $completion->get_progress_all(
+    $firstnamesort, $group,
+    $csv ? 0 : COMPLETION_REPORT_PAGE,
+    $csv ? 0 : $start);
+
+
+/**
+ * Setup page header
+ */
+if ($csv) {
+    header('Content-Disposition: attachment; filename=progress.'.
+        preg_replace('/[^a-z0-9-]/','_',strtolower($course->shortname)).'.csv');
+    // Unicode byte-order mark for Excel
+    if($excel) {
+        header('Content-Type: text/csv; charset=UTF-16LE');
+        print chr(0xFF).chr(0xFE);
+        $sep="\t".chr(0);
+        $line="\n".chr(0);
+    } else {
+        header('Content-Type: text/csv; charset=UTF-8');
+        $sep=",";
+        $line="\n";
+    }
+
+} else {
+    // Navigation and header
+    $strreports = get_string("reports");
+    $strcompletion = get_string('completionreport','completion');
+
+    $PAGE->set_title($strcompletion);
+    $PAGE->set_heading($course->fullname);
+    
+    if (has_capability('moodle/site:viewreports', $context)) {
+        $PAGE->navbar->add($strreports, new moodle_url('/course/report.php', array('id'=>$course->id)));
+    }
+    $PAGE->navbar->add($strcompletion);
+    echo $OUTPUT->header();
+
+    $PAGE->requires->yui2_lib(
+        array(
+            'yahoo',
+            'dom',
+            'element',
+            'event',
+        )
+    );
+
+    $PAGE->requires->js('/course/report/completion/textrotate.js');
+
+    // Handle groups (if enabled)
+    groups_print_course_menu($course, $CFG->wwwroot.'/course/report/progress/?course='.$course->id);
+}
+
+// Do we need a paging bar?
+if($progress->total > COMPLETION_REPORT_PAGE) {
+    $pagingbar='<div class="completion_pagingbar">';
+
+    if($start>0) {
+        $newstart=$start-COMPLETION_REPORT_PAGE;
+        if($newstart<0) {
+            $newstart=0;
+        }
+        $pagingbar.=link_arrow_left(get_string('previous'),'./?course='.$course->id.
+            ($newstart ? '&amp;start='.$newstart : ''),false,'completion_prev');
+    }
+
+    $a=new StdClass;
+    $a->from=$start+1;
+    $a->to=$start+COMPLETION_REPORT_PAGE;
+    $a->total=$progress->total;
+    $pagingbar.='<p>'.get_string('reportpage','completion',$a).'</p>';
+
+    if($start+COMPLETION_REPORT_PAGE < $progress->total) {
+        $pagingbar.=link_arrow_right(get_string('next'),'./?course='.$course->id.
+            '&amp;start='.($start+COMPLETION_REPORT_PAGE),false,'completion_next');
+    }
+
+    $pagingbar.='</div>';
+} else {
+    $pagingbar='';
+}
+
+
+/**
+ * Draw table header
+ */
+
+// Start of table
+if(!$csv) {
+    print '<br class="clearer"/>'; // ugh
+
+    if(count($progress->users)==0) {
+        echo $OUTPUT->box_start('errorbox errorboxcontent boxaligncenter boxwidthnormal');
+        print '<p class="nousers">'.get_string('err_nousers','completion').'</p>';
+        print '<p><a href="'.$CFG->wwwroot.'/course/report.php?id='.$course->id.'">'.get_string('continue').'</a></p>';
+        echo $OUTPUT->box_end();
+        echo $OUTPUT->footer($course);
+        exit;
+    }
+
+    print $pagingbar;
+    print '<table id="completion-progress" class="generaltable flexible boxaligncenter completionreport" style="text-align: left" cellpadding="5" border="1">';
+
+    // Print criteria group names
+    print PHP_EOL.'<tr style="vertical-align: top">';
+    print '<th scope="row" class="rowheader">'.get_string('criteriagroup', 'completion').'</th>';
+
+    $current_group = false;
+    $col_count = 0;
+    for ($i = 0; $i <= count($criteria); $i++) {
+
+        if (isset($criteria[$i])) {
+            $criterion = $criteria[$i];
+
+            if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
+                ++$col_count;
+                continue;
+            }
+        }
+
+        // Print header cell
+        if ($col_count) {
+            print '<th scope="col" colspan="'.$col_count.'" class="colheader criteriagroup">'.$current_group->get_type_title().'</th>';
+        }
+
+        if (isset($criteria[$i])) {
+            // Move to next criteria type
+            $current_group = $criterion;
+            $col_count = 1;
+        }
+    }
+
+    // Overall course completion status
+    print '<th style="text-align: center;">'.get_string('course').'</th>';
+
+    print '</tr>';
+
+    // Print aggregation methods
+    print PHP_EOL.'<tr style="vertical-align: top">';
+    print '<th scope="row" class="rowheader">'.get_string('aggregationmethod', 'completion').'</th>';
+
+    $current_group = false;
+    $col_count = 0;
+    for ($i = 0; $i <= count($criteria); $i++) {
+
+        if (isset($criteria[$i])) {
+            $criterion = $criteria[$i];
+
+            if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
+                ++$col_count;
+                continue;
+            }
+        }
+
+        // Print header cell
+        if ($col_count) {
+            $has_agg = array(
+                COMPLETION_CRITERIA_TYPE_COURSE,
+                COMPLETION_CRITERIA_TYPE_ACTIVITY,
+                COMPLETION_CRITERIA_TYPE_ROLE,
+            );
+
+            if (in_array($current_group->criteriatype, $has_agg)) {
+                // Try load a aggregation method
+                $method = $completion->get_aggregation_method($current_group->criteriatype);
+
+                $method = $method == 1 ? 'All' : 'Any';
+
+            } else {
+                $method = '-';
+            }
+
+            print '<th scope="col" colspan="'.$col_count.'" class="colheader aggheader">'.$method.'</th>';
+        }
+
+        if (isset($criteria[$i])) {
+            // Move to next criteria type
+            $current_group = $criterion;
+            $col_count = 1;
+        }
+    }
+
+    // Overall course aggregation method
+    print '<th scope="col" class="colheader aggheader aggcriteriacourse">';
+
+    // Get course aggregation
+    $method = $completion->get_aggregation_method();
+
+    print $method == 1 ? 'All' : 'Any';
+    print '</th>';
+
+    print '</tr>';
+
+
+    // Print criteria titles
+    if (COMPLETION_REPORT_COL_TITLES) {
+
+        print PHP_EOL.'<tr>';
+        print '<th scope="row" class="rowheader">'.get_string('criteria', 'completion').'</th>';
+
+        foreach ($criteria as $criterion) {
+            // Get criteria details
+            $details = $criterion->get_title_detailed();
+            print '<th scope="col" class="colheader criterianame">';
+            print '<span class="completion-criterianame">'.$details.'</span>';
+            print '</th>';
+        }
+
+        // Overall course completion status
+        print '<th scope="col" class="colheader criterianame">';
+
+        print '<span class="completion-criterianame">'.get_string('coursecomplete', 'completion').'</span>';
+
+        print '</th></tr>';
+    }
+
+    // Print user heading and icons
+    print '<tr>';
+
+    // User heading / sort option
+    print '<th scope="col" class="completion-sortchoice" style="clear: both;">';
+    if($firstnamesort) {
+        print
+            get_string('firstname').' / <a href="./?course='.$course->id.'">'.
+            get_string('lastname').'</a>';
+    } else {
+        print '<a href="./?course='.$course->id.'&amp;sort=firstname">'.
+            get_string('firstname').'</a> / '.
+            get_string('lastname');
+    }
+    print '</th>';
+
+
+    // Print user id number column
+    if($idnumbers) {
+        print '<th>'.get_string('idnumber').'</th>';
+    }
+
+    ///
+    /// Print criteria icons
+    ///
+    foreach ($criteria as $criterion) {
+
+        // Generate icon details
+        $icon = '';
+        $iconlink = '';
+        $icontitle = ''; // Required if $iconlink set
+        $iconalt = ''; // Required
+        switch ($criterion->criteriatype) {
+
+            case COMPLETION_CRITERIA_TYPE_ACTIVITY:
+                // Load activity
+                $activity = $criterion->get_mod_instance();
+
+                // Display icon
+                $icon = $OUTPUT->pix_url('icon', $criterion->module).'/icon.gif';
+                $iconlink = $CFG->wwwroot.'/mod/'.$criterion->module.'/view.php?id='.$activity->id;
+                $icontitle = $activity->name;
+                $iconalt = get_string('modulename', $criterion->module);
+                break;
+
+            case COMPLETION_CRITERIA_TYPE_COURSE:
+                // Load course
+                $crs = get_record('course', 'id', $criterion->courseinstance);
+
+                // Display icon
+                $iconlink = $CFG->wwwroot.'/course/view.php?id='.$criterion->courseinstance;
+                $icontitle = $crs->fullname;
+                $iconalt = $crs->shortname;
+                break;
+
+            case COMPLETION_CRITERIA_TYPE_ROLE:
+                // Load role
+                $role = $DB->get_record('role', array('id' => $criterion->role));
+
+                // Display icon
+                $iconalt = $role->name;
+                break;
+        }
+
+        // Print icon and cell
+        print '<th class="criteriaicon">';
+
+        // Create icon if not supplied
+        if (!$icon) {
+            $icon = $OUTPUT->pix_url('i/'.$COMPLETION_CRITERIA_TYPES[$criterion->criteriatype].'.gif');
+        }
+
+        print ($iconlink ? '<a href="'.$iconlink.'" title="'.$icontitle.'">' : '');
+        print '<img src="'.$icon.'" class="icon" alt="'.$iconalt.'" '.(!$iconlink ? 'title="'.$iconalt.'"' : '').' />';
+        print ($iconlink ? '</a>' : '');
+
+        print '</th>';
+    }
+
+    // Overall course completion status
+    print '<th class="criteriaicon">';
+    print '<img src="'.$OUTPUT->pix_url('i/course.gif').'" class="icon" alt="Course" title="Course Complete" />';
+    print '</th>';
+
+    print '</tr>';
+
+
+} else {
+    // TODO
+    if($idnumbers) {
+        print $sep;
+    }
+}
+
+
+///
+/// Display a row for each user
+///
+foreach($progress->users as $user) {
+
+    // User name
+    if($csv) {
+        print csv_quote(fullname($user));
+        if($idnumbers) {
+            print $sep.csv_quote($user->idnumber);
+        }
+    } else {
+        print PHP_EOL.'<tr id="user-'.$user->id.'">';
+
+        print '<th scope="row"><a href="'.$CFG->wwwroot.'/user/view.php?id='.
+            $user->id.'&amp;course='.$course->id.'">'.fullname($user).'</a></th>';
+        if($idnumbers) {
+            print '<td>'.htmlspecialchars($user->idnumber).'</td>';
+        }
+    }
+
+    // Progress for each course completion criteria
+    foreach ($criteria as $criterion) {
+
+        // Handle activity completion differently
+        if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
+
+            // Load activity
+            $mod = $criterion->get_mod_instance();
+            $activity = $DB->get_record('course_modules', array('id' => $criterion->moduleinstance));
+            $activity->name = $mod->name;
+
+
+            // Get progress information and state
+            if(array_key_exists($activity->id,$user->progress)) {
+                $thisprogress=$user->progress[$activity->id];
+                $state=$thisprogress->completionstate;
+                $date=userdate($thisprogress->timemodified);
+            } else {
+                $state=COMPLETION_INCOMPLETE;
+                $date='';
+            }
+
+            $criteria_completion = $completion->get_user_completion($user->id, $criterion);
+
+            // Work out how it corresponds to an icon
+            switch($state) {
+                case COMPLETION_INCOMPLETE : $completiontype='n'; break;
+                case COMPLETION_COMPLETE : $completiontype='y'; break;
+                case COMPLETION_COMPLETE_PASS : $completiontype='pass'; break;
+                case COMPLETION_COMPLETE_FAIL : $completiontype='fail'; break;
+            }
+
+            $completionicon='completion-'.
+                ($activity->completion==COMPLETION_TRACKING_AUTOMATIC ? 'auto' : 'manual').
+                '-'.$completiontype;
+
+            $describe=get_string('completion-alt-auto-'.$completiontype,'completion');
+            $a=new StdClass;
+            $a->state=$describe;
+            $a->date=$date;
+            $a->user=fullname($user);
+            $a->activity=strip_tags($activity->name);
+            $fulldescribe=get_string('progress-title','completion',$a);
+
+            if($csv) {
+                print $sep.csv_quote($describe).$sep.csv_quote($date);
+            } else {
+                print '<td class="completion-progresscell">';
+
+                print '<img src="'.$OUTPUT->pix_url('i/'.$completionicon).
+                      '" alt="'.$describe.'" class="icon" title="'.$fulldescribe.'" />';
+
+                print '</td>';
+            }
+
+            continue;
+        }
+
+        // Handle all other criteria
+        $criteria_completion = $completion->get_user_completion($user->id, $criterion);
+        $is_complete = $criteria_completion->is_complete();
+
+        $completiontype = $is_complete ? 'y' : 'n';
+        $completionicon = 'completion-auto-'.$completiontype;
+
+        $describe = get_string('completion-alt-auto-'.$completiontype, 'completion');
+
+        $a = new Object();
+        $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 {
+
+            if ($allow_marking_criteria === $criterion->id) {
+                $describe = get_string('completion-alt-auto-'.$criteria_completion->is_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->pix_url('i/completion-manual-'.($is_complete ? 'y' : 'n')).
+                    '" alt="'.$describe.'" class="icon" title="Mark as complete" /></a></td>';
+            } else {
+                print '<td class="completion-progresscell">'.
+                    '<img src="'.$OUTPUT->pix_url('i/'.$completionicon).
+                    '" alt="'.$describe.'" class="icon" title="'.$fulldescribe.'" /></td>';
+            }
+        }
+    }
+
+    // Handle overall course completion
+
+    // Load course completion
+    $params = array(
+        'userid'    => $user->id,
+        'course'    => $course->id
+    );
+
+    $ccompletion = new completion_completion($params);
+    $completiontype =  $ccompletion->is_complete() ? '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">';
+
+        // Display course completion status icon
+        print '<img src="'.$OUTPUT->pix_url('i/completion-auto-'.$completiontype).
+               '" alt="'.$describe.'" class="icon" title="'.$fulldescribe.'" />';
+
+        print '</td>';
+    }
+
+    if($csv) {
+        print $line;
+    } else {
+        print '</tr>';
+    }
+}
+
+if($csv) {
+    exit;
+}
+print '</table>';
+print $pagingbar;
+
+print '<ul class="progress-actions"><li><a href="index.php?course='.$course->id.
+    '&amp;format=csv">'.get_string('csvdownload','completion').'</a></li>
+    <li><a href="index.php?course='.$course->id.'&amp;format=excelcsv">'.
+    get_string('excelcsvdownload','completion').'</a></li></ul>';
+
+echo $OUTPUT->footer($course);
Index: moodle/course/report/completion/lang/en/coursereport_completion.php
--- moodle/course/report/completion/lang/en/coursereport_completion.php No Base Revision
+++ moodle/course/report/completion/lang/en/coursereport_completion.php Locally New
@@ -0,0 +1,5 @@
+<?php
+
+$string['completion:view'] = 'View course completion report';
+$string['completiondate']='Completion date';
+$string['pluginname']='Course completion report';
Index: moodle/course/report/completion/mod.php
--- moodle/course/report/completion/mod.php No Base Revision
+++ moodle/course/report/completion/mod.php Locally New
@@ -0,0 +1,15 @@
+<?php  //$Id$
+
+    if (!defined('MOODLE_INTERNAL')) {
+        die('Direct access to this script is forbidden.'); // It must be included from a Moodle page
+    }
+
+    if (has_capability('coursereport/completion:view', $context)) {
+        $completion = new completion_info($course);
+        if ($completion->is_enabled()) {
+            echo '<p>';
+            echo '<a href="'.$CFG->wwwroot.'/course/report/completion/index.php?course='.$course->id.'">'.get_string('coursecompletionreport','completion').'</a>';
+            echo '</p>';
+        }
+    }
+?>
Index: moodle/course/report/completion/textrotate.js
--- moodle/course/report/completion/textrotate.js No Base Revision
+++ moodle/course/report/completion/textrotate.js Locally New
@@ -0,0 +1,68 @@
+var SVGNS='http://www.w3.org/2000/svg',XLINKNS='http://www.w3.org/1999/xlink';
+
+function textrotate_make_svg(el)
+{
+  var string=el.firstChild.nodeValue;
+
+  // Add absolute-positioned string (to measure length)
+  var abs=document.createElement('div');
+  abs.appendChild(document.createTextNode(string));
+  abs.style.position='absolute';
+  el.parentNode.insertBefore(abs,el);
+  var textWidth=abs.offsetWidth,textHeight=abs.offsetHeight;
+  el.parentNode.removeChild(abs);
+
+  // Create SVG
+  var svg=document.createElementNS(SVGNS,'svg');
+  svg.setAttribute('version','1.1');
+  svg.setAttribute('width',20);
+  svg.setAttribute('height',textWidth);
+
+  // Add text
+  var text=document.createElementNS(SVGNS,'text');
+  svg.appendChild(text);
+  text.setAttribute('x',textWidth);
+  text.setAttribute('y',-textHeight/4);
+  text.setAttribute('text-anchor','end');
+  text.setAttribute('transform','rotate(90)');
+
+  if (el.className.indexOf('completion-rplheader') != -1) {
+      text.setAttribute('fill','#238E23');
+  }
+
+  text.appendChild(document.createTextNode(string));
+
+  // Is there an icon near the text?
+  var icon=el.parentNode.firstChild;
+  if(icon.nodeName.toLowerCase()=='img') {
+    el.parentNode.removeChild(icon);
+    var image=document.createElementNS(SVGNS,'image');
+    var iconx=el.offsetHeight/4;
+    if(iconx>width-16) iconx=width-16;
+    image.setAttribute('x',iconx);
+    image.setAttribute('y',textWidth+4);
+    image.setAttribute('width',16);
+    image.setAttribute('height',16);
+    image.setAttributeNS(XLINKNS,'href',icon.src);
+    svg.appendChild(image);
+  }
+
+  // Replace original content with this new SVG
+  el.parentNode.insertBefore(svg,el);
+  el.parentNode.removeChild(el);
+}
+
+function textrotate_init() {
+  var elements=YAHOO.util.Dom.getElementsByClassName('completion-criterianame', 'span');
+  for(var i=0;i<elements.length;i++)
+  {
+    var el=elements[i];
+    el.parentNode.style.verticalAlign='bottom';
+    el.parentNode.style.width='20px';
+
+    textrotate_make_svg(el);
+  }
+}
+
+YAHOO.util.Event.onDOMReady(textrotate_init);
+
Index: moodle/course/report/completion/version.php
--- moodle/course/report/completion/version.php No Base Revision
+++ moodle/course/report/completion/version.php Locally New
@@ -0,0 +1,29 @@
+<?PHP // $Id$
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards  Martin Dougiamas  http://moodle.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                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+$plugin->version  = 2007101501;
+$plugin->requires = 2007101532;
+
+?>
Index: moodle/course/report/progress/lang/en/coursereport_progress.php
--- moodle/course/report/progress/lang/en/coursereport_progress.php Base (1.3)
+++ moodle/course/report/progress/lang/en/coursereport_progress.php Locally Modified (Based On 1.3)
@@ -24,4 +24,4 @@
  */
 
 $string['pluginname'] = 'Course progress';
-$string['progress:view'] = 'View course progress report';
+$string['progress:view'] = 'View activity completion reports';
Index: moodle/course/togglecompletion.php
--- moodle/course/togglecompletion.php Base (1.7)
+++ moodle/course/togglecompletion.php Locally Modified (Based On 1.7)
@@ -16,7 +16,8 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Toggles the manual completion flag for a particular activity and the current user.
+ * Toggles the manual completion flag for a particular activity or course completion
+ * and the current user.
  *
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  * @package course
@@ -26,7 +27,77 @@
 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);
 $fromajax    = optional_param('fromajax', 0, PARAM_INT);
 
Index: moodle/course/user.php
--- moodle/course/user.php Base (1.108)
+++ moodle/course/user.php Locally Modified (Based On 1.108)
@@ -111,6 +111,19 @@
     $modes[] = 'grade';
 }
 
+// Course completion tab
+if (!empty($CFG->enablecompletion) && ($course->id == SITEID || !empty($course->enablecompletion)) && // completion enabled
+    ($myreports || $anyreport || ($course->id == SITEID || has_capability('coursereport/completion:view', $coursecontext)))) { // permissions to view the report
+
+    // Decide if singular or plural
+    if ($course->id == SITEID) {
+        $modes[] = 'coursecompletions';
+    } else {
+        $modes[] = 'coursecompletion';
+    }
+}
+
+
 if (empty($modes)) {
     require_capability('moodle/user:viewuseractivitiesreport', $personalcontext);
 }
@@ -351,7 +364,240 @@
             }
         }
         break;
+    case "coursecompletion":
+    case "coursecompletions":
+
+        // Display course completion user report
+        require_once $CFG->libdir.'/completionlib.php';
+
+        // Grab all courses the user is enrolled in and their completion status
+        $sql = "
+            SELECT DISTINCT
+                c.id AS id
+            FROM
+                {$CFG->prefix}course c
+            INNER JOIN
+                {$CFG->prefix}context con
+             ON con.instanceid = c.id
+            INNER JOIN
+                {$CFG->prefix}role_assignments ra
+             ON ra.contextid = con.id
+            AND ra.userid = {$user->id}
+        ";
+
+        // Get roles that are tracked by course completion
+        if ($roles = $CFG->progresstrackedroles) {
+            $sql .= '
+                AND ra.roleid IN ('.$roles.')
+            ';
+        }
+
+        $sql .= '
+            WHERE
+                con.contextlevel = '.CONTEXT_COURSE.'
+            AND c.enablecompletion = 1
+        ';
+
+
+        // If we are looking at a specific course
+        if ($course->id != 1) {
+            $sql .= '
+                AND c.id = '.(int)$course->id.'
+            ';
+        }
+
+        // Check if result is empty
+        if (!$rs = get_recordset_sql($sql)) {
+
+            if ($course->id != 1) {
+                $error = get_string('nocompletions', 'coursereport_completion');
+            } else {
+                $error = get_string('nocompletioncoursesenroled', 'coursereport_completion');
+            }
+
+            echo $OUTPUT->notification($error);
+            break;
+        }
+
+        // Categorize courses by their status
+        $courses = array(
+            'inprogress'    => array(),
+            'complete'      => array(),
+            'unstarted'     => array()
+        );
+
+        // Sort courses by the user's status in each
+        foreach ($rs as $course_completion) {
+            $c_info = new completion_info((object)$course_completion);
+
+            // Is course complete?
+            $coursecomplete = $c_info->is_course_complete($user->id);
+
+            // Has this user completed any criteria?
+            $criteriacomplete = $c_info->count_course_user_data($user->id);
+
+            if ($coursecomplete) {
+                $courses['complete'][] = $c_info;
+            } else if ($criteriacomplete) {
+                $courses['inprogress'][] = $c_info;
+            } else {
+                $courses['unstarted'][] = $c_info;
+            }
+        }
+
+        $rs->close();
+
+        // Loop through course status groups
+        foreach ($courses as $type => $infos) {
+
+            // If there are courses with this status
+            if (!empty($infos)) {
+
+                echo '<h1 align="center">'.get_string($type, 'coursereport_completion').'</h1>';
+                echo '<table class="generalbox boxaligncenter">';
+                echo '<tr class="ccheader">';
+                echo '<th class="c0 header" scope="col">'.get_string('course').'</th>';
+                echo '<th class="c1 header" scope="col">'.get_string('requiredcriteria', 'completion').'</th>';
+                echo '<th class="c2 header" scope="col">'.get_string('status').'</th>';
+                echo '<th class="c3 header" scope="col" width="15%">'.get_string('info').'</th>';
+
+                if ($type === 'complete') {
+                    echo '<th class="c4 header" scope="col">'.get_string('completiondate', 'coursereport_completion').'</th>';
+                }
+
+                echo '</tr>';
+
+                // For each course
+                foreach ($infos as $c_info) {
+
+                    // Get course info
+                    $c_course = get_record('course', 'id', $c_info->course_id);
+                    $course_name = $c_course->fullname;
+
+                    // Get completions
+                    $completions = $c_info->get_completions($user->id);
+
+                    // Save row data
+                    $rows = array();
+
+                    // For aggregating activity completion
+                    $activities = array();
+                    $activities_complete = 0;
+                    
+                    // For aggregating prerequisites
+                    $prerequisites = array();
+                    $prerequisites_complete = 0;
+
+                    // Loop through course criteria
+                    foreach ($completions as $completion) {
+                        $criteria = $completion->get_criteria();
+                        $complete = $completion->is_complete();
+
+                        // 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;
+                        }
+
+                        // Prerequisites are also a special case, so cache them and leave them till last
+                        if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) {
+                            $prerequisites[$criteria->courseinstance] = $complete;
+
+                            if ($complete) {
+                                $prerequisites_complete++;
+                            }
+
+                            continue;
+                        }
+
+                        $row = array();
+                        $row['title'] = $criteria->get_title();
+                        $row['status'] = $completion->get_status();
+                        $rows[] = $row;
+                    }
+
+                    // Aggregate activities
+                    if (!empty($activities)) {
+
+                        $row = array();
+                        $row['title'] = get_string('activitiescomplete', 'coursereport_completion');
+                        $row['status'] = $activities_complete.' of '.count($activities);
+                        $rows[] = $row;
+                    }
+
+                    // Aggregate prerequisites
+                    if (!empty($prerequisites)) {
+
+                        $row = array();
+                        $row['title'] = get_string('prerequisitescompleted', 'completion');
+                        $row['status'] = $prerequisites_complete.' of '.count($prerequisites);
+                        array_splice($rows, 0, 0, array($row));
+                    }
+
+                    $first_row = true;
+
+                    // Print table
+                    foreach ($rows as $row) {
+
+                        // Display course name on first row
+                        if ($first_row) {
+                            echo '<tr><td class="c0"><a href="'.$CFG->wwwroot.'/course/view.php?id='.$c_course->id.'">'.format_string($course_name).'</a></td>';
+                        } else {
+                            echo '<tr><td class="c0"></td>';
+                        }
+
+                        echo '<td class="c1">';
+                        echo $row['title'];
+                        echo '</td><td class="c2">';
+
+                        switch ($row['status']) {
+                            case 'Yes':
+                                echo get_string('complete');
+                                break;
+
+                            case 'No':
+                                echo get_string('incomplete', 'coursereport_completion');
+                                break;
+
     default:
+                                echo $row['status'];
+                        }
+
+                        // Display link on first row
+                        echo '</td><td class="c3">';
+                        if ($first_row) {
+                            echo '<a href="'.$CFG->wwwroot.'/blocks/completionstatus/details.php?course='.$c_course->id.'&user='.$user->id.'">'.get_string('detailedview', 'coursereport_completion').'</a>';
+                        }
+                        echo '</td>';
+
+                        // Display completion date for completed courses on first row
+                        if ($type === 'complete' && $first_row) {
+                            $params = array(
+                                'userid'    => $user->id,
+                                'course'  => $c_course->id
+                            );
+
+                            $ccompletion = new completion_completion($params);
+                            echo '<td class="c4">'.userdate($ccompletion->timecompleted, '%e %B %G').'</td>';
+                        }
+
+                        $first_row = false;
+                        echo '</tr>';
+                    }
+                }
+
+                echo '</table>';
+            }
+
+        }
+
+        break;
+    default:
         // can not be reached ;-)
 }
 
Index: moodle/lang/en/completion.php
--- moodle/lang/en/completion.php Base (1.2)
+++ moodle/lang/en/completion.php Locally Modified (Based On 1.2)
@@ -40,7 +40,6 @@
 $string['completion-alt-manual-y'] = 'Completed; select to mark as not complete';
 $string['completion_automatic'] = 'Show activity as complete when conditions are met';
 $string['completiondisabled'] = 'Disabled, not shown in activity settings';
-$string['completionenabled'] = 'Enabled, control via activity settings';
 $string['completionexpected'] = 'Expect completed on';
 $string['completionicons'] = 'progress tick boxes';
 $string['completion_manual'] = 'Users can manually mark the activity as completed';
@@ -61,15 +60,84 @@
 $string['err_system'] = 'An internal error occurred in the completion system. (System administrators can enable debugging information to see more detail.)';
 $string['excelcsvdownload'] = 'Download in Excel-compatible format (.csv)';
 $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['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['restoringcompletiondata'] = 'Writing completion data';
 $string['saved'] = 'Saved';
 $string['unlockcompletion'] = 'Unlock completion options';
 $string['writingcompletiondata'] = 'Writing completion data';
 $string['yourprogress'] = 'Your progress';
+
+$string['achievinggrade']='Achieving grade';
+$string['activities']='Activities';
+$string['activitiescompleted']='Activities completed';
+$string['activitycompletionreport']='Activity completion progress report';
+$string['addcourseprerequisite']='Add course prerequisite';
+$string['afterspecifieddate']='After specified date';
+$string['aggregationmethod']='Aggregation method';
+$string['all']='All';
+$string['any']='Any';
+$string['approval']='Approval';
+$string['completionenabled']='Enabled, control via completion and activity settings';
+$string['completionmenuitem']='Completion';
+$string['completionsettingslocked']='Completion settings locked';
+$string['completionstartonenrol']='Completion tracking begins on enrolment';
+$string['completionstartonenrolhelp']='Begin tracking a user\'s progress in course completion after course enrolment';
+$string['confirmselfcompletion']='Confirm self completion';
+$string['coursecomplete']='Course Complete';
+$string['coursecompleted']='Course Completed';
+$string['coursecompletionreport']='Course completion progress report';
+$string['coursegrade']='Course grade';
+$string['courseprerequisites']='Course prerequisites';
+$string['coursesavailable']='Courses available';
+$string['coursesavailableexplaination']='<i>Course completion criteria must be set for a course to appear in this list</i>';
+$string['criteria']='Criteria';
+$string['criteriagroup']='Criteria group';
+$string['criteriarequiredall']='All criteria below are required';
+$string['criteriarequiredany']='Any criteria below are required';
+$string['days']='Days';
+$string['editcoursecompletionsettings']='Edit Course Completion Settings';
+$string['enrolmentduration']='Days left';
+$string['err_nocourses']='Course completion is not enabled for any other courses, so none can be displayed. You can enable course completion in the course settings.';
+$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['datepassed']='Date passed';
+$string['daysafterenrolment']='Days after enrolment';
+$string['durationafterenrolment']='Duration after enrolment';
+$string['fraction']='Fraction';
+$string['help_completionexpected']='the date completion is expected';
+$string['inprogress']='In progress';
+$string['manualcompletionby']='Manual completion by';
+$string['manualselfcompletion']='Manual self completion';
+$string['markcomplete']='Mark complete';
+$string['markedcompleteby']='Marked complete by $a';
+$string['markingyourselfcomplete']='Marking yourself 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['periodpostenrolment']='Period post enrolment';
+$string['prerequisites']='Prerequisites';
+$string['prerequisitescompleted']='Prerequisites completed';
+$string['progresstrackedroles']='Progress-tracked roles';
+$string['recognitionofpriorlearning']='Recognition of prior learning';
+$string['remainingenroledfortime']='Remaining enroled for a specified period of time';
+$string['remainingenroleduntildate']='Remaining enroled until a specified date';
+$string['requiredcriteria']='Required Criteria';
+$string['seedetails']='See details';
+$string['self']='Self';
+$string['selfcompletion']='Self completion';
+$string['showinguser']='Showing user';
+$string['unit']='Unit';
+$string['unenrolingfromcourse']='Unenroling from course';
+$string['unenrolment']='Unenrolment';
+$string['unlockcompletiondelete']='Unlock completion options and delete user completion data';
+$string['usealternateselector']='Use the alternate course selector';
+$string['viewcoursereport']='View course report';
+$string['viewingactivity']='Viewing the $a';
+$string['xdays']='$a days';
Index: moodle/lang/en/moodle.php
--- moodle/lang/en/moodle.php Base (1.488)
+++ moodle/lang/en/moodle.php Locally Modified (Based On 1.488)
@@ -287,6 +287,8 @@
 $string['coursecategories'] = 'Course categories';
 $string['coursecategory'] = 'Course category';
 $string['coursecategorydeleted'] = 'Deleted course category {$a}';
+$string['coursecompletion'] = 'Course completion';
+$string['coursecompletions'] = 'Course completions';
 $string['coursecreators'] = 'Course creator';
 $string['coursecreatorsdescription'] = 'Course creators can create new courses.';
 $string['coursedeleted'] = 'Deleted course {$a}';
Index: moodle/lang/en/role.php
--- moodle/lang/en/role.php Base (1.4)
+++ moodle/lang/en/role.php Locally Modified (Based On 1.4)
@@ -104,6 +104,7 @@
 $string['course:managegroups'] = 'Manage groups';
 $string['course:managemetacourse'] = 'Manage metacourse';
 $string['course:managescales'] = 'Manage scales';
+$string['course:markcomplete'] = 'Mark users as complete in course completion';
 $string['course:participate'] = 'Participate in courses';
 $string['course:request'] = 'Request new courses';
 $string['course:reset'] = 'Reset course';
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,239 @@
+<?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',
+        'timeenrolled', 'timestarted', 'timecompleted', 'reaggregate');
+
+    /**
+     * 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
+     * @see     completion_completion::mark_enrolled()
+     * @access  public
+     * @var     int
+     */
+    public $timeenrolled;
+
+    /**
+     * Time the user started their course completion
+     * @see     completion_completion::mark_inprogress()
+     * @access  public
+     * @var     int
+     */
+    public $timestarted;
+
+    /**
+     * Timestamp of course completion
+     * @see     completion_completion::mark_complete()
+     * @access  public
+     * @var     int
+     */
+    public $timecompleted;
+
+    /**
+     * Flag to trigger cron aggregation (timestamp)
+     * @access  public
+     * @var     int
+     */
+    public $reaggregate;
+
+
+    /**
+     * 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 as started (or enrolled) in this course
+     *
+     * If the user is already marked as started, no change will occur
+     *
+     * @access  public
+     * @param   integer $timeenrolled Time enrolled (optional)
+     * @return  void
+     */
+    public function mark_enrolled($timeenrolled = null) {
+
+        if (!$this->timeenrolled) {
+
+            if (!$timeenrolled) {
+                $timeenrolled = time();
+            }
+
+            $this->timeenrolled = $timeenrolled;
+        }
+
+        $this->_save();
+    }
+
+    /**
+     * Mark this user as inprogress in this course
+     *
+     * If the user is already marked as inprogress,
+     * the time will not be changed
+     *
+     * @access  public
+     * @param   integer $timestarted Time started (optional)
+     * @return  void
+     */
+    public function mark_inprogress($timestarted = null) {
+
+        $timenow = time();
+
+        // Set reaggregate flag
+        $this->reaggregate = $timenow;
+
+        if (!$this->timestarted) {
+
+            if (!$timestarted) {
+                $timestarted = $timenow;
+            }
+
+            $this->timestarted = $timestarted;
+        }
+
+        $this->_save();
+    }
+
+    /**
+     * Mark this user complete in this course
+     *
+     * This generally happens when the required completion criteria
+     * in the course are complete.
+     *
+     * @access  public
+     * @param   integer $timecomplete Time completed (optional)
+     * @return  void
+     */
+    public function mark_complete($timecomplete = null) {
+
+        // Never change a completion time
+        if ($this->timecompleted) {
+            return;
+        }
+
+        // Use current time if nothing supplied
+        if (!$timecomplete) {
+            $timecomplete = time();
+        }
+
+        // Set time complete
+        $this->timecompleted = $timecomplete;
+
+        // Save record
+        $this->_save();
+    }
+
+    /**
+     * Save course completion status
+     *
+     * This method creates a course_completions record if none exists
+     * @access  public
+     * @return  void
+     */
+    private function _save() {
+
+        if (!$this->timeenrolled) {
+            // Get users timenrolled
+            // Can't find a more efficient way of doing this without alter get_users_by_capability()
+            $context = get_context_instance(CONTEXT_COURSE, $this->course);
+            $this->timeenrolled = get_field('role_assignments', 'timestart', 'contextid', $context->id, 'userid', $this->userid);
+        }
+
+        // Save record
+        if ($this->id) {
+            $this->update();
+        } else {
+            // Make sure reaggregate field is not null
+            if (!$this->reaggregate) {
+                $this->reaggregate = 0;
+            }
+
+            $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,231 @@
+<?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);
+define('COMPLETION_CRITERIA_TYPE_COURSE',       8);
+
+/**
+ * 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_TYPE_COURSE     => 'course',
+);
+
+
+/**
+ * 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', 'courseinstance', '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($params->criteriatype) || !isset($COMPLETION_CRITERIA_TYPES[$params->criteriatype])) {
+            error('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 a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    abstract public function get_title_detailed();
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    abstract public function get_type_title();
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    abstract public function get_details($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) {
+        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,286 @@
+<?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;
+    }
+
+    /**
+     * Get module instance
+     * @access  public
+     * @return  object|false
+     */
+    public function get_mod_instance() {
+        global $DB;
+
+        return $DB->get_record_sql(
+            "
+                SELECT
+                    m.*
+                FROM
+                    {{$this->module}} m
+                INNER JOIN
+                    {course_modules} cm
+                 ON cm.id = {$this->moduleinstance}
+                AND m.id = cm.instance
+            "
+        );
+    }
+
+    /**
+     * 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');
+    }
+
+    /**
+     * Return a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        global $DB;
+        $module = $DB->get_record('course_modules', array('id' => $this->moduleinstance));
+        $activity = $DB->get_record($this->module, array('id' => $module->instance));
+
+        return shorten_text(urldecode($activity->name));
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_title() {
+        return get_string('activities', '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,
+                mc.timemodified 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
+            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($record->timecompleted);
+            }
+
+            $rs->close();
+        }
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        global $DB;
+
+        // Get completion info
+        $course = new object();
+        $course->id = $completion->course;
+        $info = new completion_info($course);
+
+        $module = $DB->get_record('course_modules', array('id' => $this->moduleinstance));
+        $data = $info->get_data($module, false, $completion->userid);
+
+        $activity = $DB->get_record($this->module, array('id' => $module->instance));
+
+        $details = array();
+        $details['type'] = $this->get_title();
+        $details['criteria'] = '<a href="'.$CFG->wwwroot.'/mod/'.$this->module.'/view.php?id='.$this->moduleinstance.'">'.$activity->name.'</a>';
+
+        // Build requirements
+        $details['requirement'] = array();
+
+        if ($module->completion == 1) {
+            $details['requirement'][] = get_string('markingyourselfcomplete', 'completion');
+        } elseif ($module->completion == 2) {
+            if ($module->completionview) {
+                $details['requirement'][] = get_string('viewingactivity', 'completion', $this->module);
+            }
+
+            if ($module->completiongradeitemnumber) {
+                $details['requirement'][] = get_string('achievinggrade', 'completion');
+            }
+        }
+
+        $details['requirement'] = implode($details['requirement'], ', ');
+
+        $details['status'] = '';
+
+        return $details;
+    }
+}
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,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/>.
+
+/**
+ * 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', 'rpl', '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;
+
+    /**
+     * Record of prior learning, leave blank if none
+     * @access  public
+     * @var     string
+     */
+    public $rpl;
+
+    /**
+     * 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();
+
+        // Save record
+        if ($this->id) {
+            $this->update();
+        } else {
+            $this->insert();
+        }
+
+        // Mark course completion record as started (if not already)
+        $cc = array(
+            'course'    => $this->course,
+            'userid'    => $this->userid
+        );
+        $ccompletion = new completion_completion($cc);
+        $ccompletion->mark_inprogress($this->timecompleted);
+    }
+
+    /**
+     * 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_course.php
--- moodle/lib/completion/completion_criteria_course.php No Base Revision
+++ moodle/lib/completion/completion_criteria_course.php Locally New
@@ -0,0 +1,220 @@
+<?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 course completion
+ *
+ * This course completion criteria depends on another course with
+ * completion enabled to be marked as complete for this user
+ *
+ * @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_course extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_COURSE;
+
+    /**
+     * 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_COURSE;
+        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) {
+        global $CFG;
+
+        $link = "<a href=\"{$CFG->wwwroot}/course/view.php?id={$data->id}\">".s($data->fullname).'</a>';
+        $mform->addElement('checkbox', 'criteria_course['.$data->id.']', $link);
+
+        if ($this->id) {
+            $mform->setDefault('criteria_course['.$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_course) && is_array($data->criteria_course)) {
+
+            $this->course = $data->id;
+
+            foreach ($data->criteria_course as $course) {
+
+                $this->courseinstance = $course;
+                $this->id = NULL;
+                $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) {
+
+        $course = get_record('course', 'id', $this->courseinstance);
+        $info = new completion_info($course);
+
+        // If the course is complete
+        if ($info->is_course_complete($completion->userid)) {
+
+            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('prerequisitescompleted', 'completion');
+    }
+
+    /**
+     * Return a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        $prereq = get_record('course', 'id', $this->courseinstance);
+        return shorten_text(urldecode($prereq->fullname));
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_title() {
+        return get_string('prerequisites', '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.id AS criteriaid,
+                ra.userid AS userid,
+                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
+            INNER JOIN
+                {course_completions} cc
+             ON cc.course = cr.courseinstance
+            AND cc.userid = ra.userid
+            LEFT JOIN
+                {course_completion_crit_compl} ccc
+             ON ccc.criteriaid = cr.id
+            AND ccc.userid = ra.userid
+            WHERE
+                cr.criteriatype = ".COMPLETION_CRITERIA_TYPE_COURSE."
+            AND con.contextlevel = ".CONTEXT_COURSE."
+            AND c.enablecompletion = 1
+            AND ccc.id IS NULL
+            AND cc.timecompleted IS NOT NULL
+        ";
+
+        // 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($record->timecompleted);
+            }
+
+            $rs->close();
+        }
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        global $CFG;
+
+        // Get completion info
+        $course = new object();
+        $course->id = $completion->course;
+        $info = new completion_info($course);
+
+        $prereq = get_record('course', 'id', $this->courseinstance);
+        $prereq_info = new completion_info($prereq);
+
+        $details = array();
+        $details['type'] = $this->get_title();
+        $details['criteria'] = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->courseinstance.'">'.s($prereq->fullname).'</a>';
+        $details['requirement'] = get_string('coursecompleted', 'completion');
+        $details['status'] = '<a href="'.$CFG->wwwroot.'/blocks/completionstatus/details.php?course='.$this->courseinstance.'">'.get_string('seedetails', 'completion').'</a>';
+
+        return $details;
+    }
+}
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,208 @@
+<?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 a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        return userdate($this->date, '%d-%h-%y');
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_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 $completion->is_complete() ? get_string('yes') : 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($record->date);
+            }
+
+            $rs->close();
+        }
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        $details = array();
+        $details['type'] = get_string('datepassed', 'completion');
+        $details['criteria'] = get_string('remainingenroleduntildate', 'completion');
+        $details['requirement'] = userdate($this->date, '%d %B %Y');
+        $details['status'] = '';
+
+        return $details;
+    }
+}
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,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/>.
+
+/**
+ * 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_timeenrolled($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) {
+        $timeenrolled = $this->get_timeenrolled($completion);
+
+        // If duration since enrollment has passed
+        if (!$this->enrolperiod || !$timeenrolled) {
+            return false;
+        }
+
+        if (time() > ($timeenrolled + $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 a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        return ceil($this->enrolperiod / (60 * 60 * 24)) . ' days';
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_title() {
+        return get_string('days', '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) {
+        $timeenrolled = $this->get_timeenrolled($completion);
+        $timeleft = $timeenrolled + $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,
+                (ra.timestart + cr.enrolperiod) 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
+            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($record->timecompleted);
+            }
+
+            $rs->close();
+        }
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        $details = array();
+        $details['type'] = get_string('periodpostenrolment', 'completion');
+        $details['criteria'] = get_string('remainingenroledfortime', 'completion');
+        $details['requirement'] = get_string('xdays', 'completion', ceil($this->enrolperiod / (60*60*24)));
+
+        // Get status
+        $timeenrolled = $this->get_timeenrolled($completion);
+        $timepassed = time() - $timeenrolled;
+        $details['status'] = get_string('xdays', 'completion', floor($timepassed / (60*60*24)));
+
+        return $details;
+    }
+}
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,248 @@
+<?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);
+        }
+    }
+
+    /**
+     * 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 a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        return (float) $this->gradepass . '% required';
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_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,
+                gg.timemodified 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
+            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($record->timecompleted);
+            }
+
+            $rs->close();
+        }
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        $details = array();
+        $details['type'] = get_string('coursegrade', 'completion');
+        $details['criteria'] = get_string('passinggrade', 'completion');
+        $details['requirement'] = ((float)$this->gradepass).'%';
+        $details['status'] = '';
+
+        $grade = (float)$this->get_grade($completion);
+        if ($grade) {
+            $details['status'] = $grade.'%';
+        }
+
+        return $details;
+    }
+}
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,158 @@
+<?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;
+    }
+
+    /**
+     * Return a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        global $DB;
+        return $DB->get_field('role', 'name', array('id' => $this->role));
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_title() {
+        return get_string('approval', 'completion');
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        $details = array();
+        $details['type'] = get_string('manualcompletionby', 'completion');
+        $details['criteria'] = $this->get_title();
+        $details['requirement'] = get_string('markedcompleteby', 'completion', $details['criteria']);
+        $details['status'] = '';
+
+        return $details;
+    }
+}
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,146 @@
+<?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');
+    }
+
+    /**
+     * Return a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        return $this->get_title();
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_title() {
+        return get_string('self', 'completion');
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        $details = array();
+        $details['type'] = $this->get_title();
+        $details['criteria'] = $this->get_title();
+        $details['requirement'] = get_string('markingyourselfcomplete', 'completion');
+        $details['status'] = '';
+
+        return $details;
+    }
+}
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,128 @@
+<?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');
+    }
+
+    /**
+     * Return a more detailed criteria title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_title_detailed() {
+        return $this->get_title();
+    }
+
+    /**
+     * Return criteria type title for display in reports
+     * @access  public
+     * @return  string
+     */
+    public function get_type_title() {
+        return get_string('unenrol');
+    }
+
+    /**
+     * Return criteria progress details for display in reports
+     * @access  public
+     * @param   object  $completion     The user's completion record
+     * @return  array
+     */
+    public function get_details($completion) {
+        $details = array();
+        $details['type'] = get_string('unenrolment', 'completion');
+        $details['criteria'] = get_string('unenrolment', 'completion');
+        $details['requirement'] = get_string('unenrolingfromcourse', 'completion');
+        $details['status'] = '';
+
+        return $details;
+    }
+}
Index: moodle/lib/completion/cron.php
--- moodle/lib/completion/cron.php No Base Revision
+++ moodle/lib/completion/cron.php Locally New
@@ -0,0 +1,345 @@
+<?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_mark_started();
+
+    completion_cron_criteria();
+
+    completion_cron_completions();
+}
+
+/**
+ * Mark users as started if the config option is set
+ *
+ * @return  void
+ */
+function completion_cron_mark_started() {
+    global $CFG, $DB;
+
+    if (debugging()) {
+        mtrace('Marking users as started');
+    }
+
+    $roles = '';
+    if (!empty($CFG->progresstrackedroles)) {
+        $roles = 'AND ra.roleid IN ('.$CFG->progresstrackedroles.')';
+    }
+
+    $sql = "
+        SELECT DISTINCT
+            c.id AS course,
+            ra.userid AS userid,
+            crc.id AS completionid,
+            MIN(ra.timestart) AS timestarted
+        FROM
+            {course} c
+        INNER JOIN
+            {context} con
+         ON con.instanceid = c.id
+        INNER JOIN
+            {role_assignments} ra
+         ON ra.contextid = con.id
+        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 c.completionstartonenrol = 1
+        AND crc.timeenrolled IS NULL
+        AND (ra.timeend IS NULL OR ra.timeend > ".time().")
+        {$roles}
+        GROUP BY
+            c.id,
+            ra.userid,
+            crc.id
+        ORDER BY
+            course,
+            userid
+    ";
+
+    // Check if result is empty
+    if (!$rs = $DB->get_recordset_sql($sql)) {
+        return;
+    }
+
+    // Grab records for current user/course
+    foreach ($rs as $record) {
+        $completion = new completion_completion();
+        $completion->userid = $record->userid;
+        $completion->course = $record->course;
+        $completion->timeenrolled = $record->timestarted;
+
+        if ($record->completionid) {
+            $completion->id = $record->completionid;
+        }
+
+        $completion->mark_enrolled();
+
+        if (debugging()) {
+            mtrace('Marked started user '.$record->userid.' in course '.$record->course);
+        }
+    }
+
+    $rs->close();
+}
+
+/**
+ * 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');
+    }
+
+    // Save time started
+    $timestarted = time();
+
+    // 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.timecompleted IS NULL
+        AND crc.reaggregate > 0
+        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);
+            $prerequisite = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE);
+            $role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE);
+
+            $overall_status = null;
+            $activity_status = null;
+            $prerequisite_status = null;
+            $role_status = null;
+
+            // Get latest timecompleted
+            $timecompleted = null;
+
+            // Check each of the criteria
+            foreach ($completions as $params) {
+                $timecompleted = max($timecompleted, $params->timecompleted);
+
+                $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_COURSE) {
+                    completion_cron_aggregate($prerequisite, $completion->is_complete(), $prerequisite_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);
+            }
+
+            // Include prerequisite criteria aggregation in overall aggregation
+            if ($prerequisite_status !== null) {
+                completion_cron_aggregate($overall, $prerequisite_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($timecompleted);
+            }
+        }
+
+        // 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;
+    }
+
+    // Mark all users as aggregated
+    $sql = "
+        UPDATE
+            {course_completions}
+        SET
+            reaggregate = 0
+        WHERE
+            reaggregate < {$timestarted}
+    ";
+
+    $DB->execute($sql);
+}
+
+/**
+ * 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.22)
+++ moodle/lib/completionlib.php Locally Modified (Based On 1.22)
@@ -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,195 @@
             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);
+
+        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,
+            'course'  => $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 +607,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 +624,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 +702,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 +798,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) {
@@ -635,7 +937,7 @@
         // Obtain those activities which have completion turned on
         $withcompletion = $DB->get_records_select('course_modules', 'course='.$this->course->id.
           ' AND completion<>'.COMPLETION_TRACKING_NONE);
-        if (count($withcompletion) == 0) {
+        if (!$withcompletion) {
             return array();
         }
 
@@ -664,11 +966,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,313 @@
+<?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 {
+    /**
+     * Table that the class maps to in the database
+     * @var string $table
+     */
+    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.128)
+++ moodle/lib/db/access.php Locally Modified (Based On 1.128)
@@ -1537,5 +1537,16 @@
             'editingteacher' => CAP_ALLOW,
             'manager' => 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.242)
+++ moodle/lib/db/install.xml Locally Modified (Based On 1.242)
@@ -110,7 +110,9 @@
         <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="completionstartonenrol"/>
+        <FIELD NAME="completionstartonenrol" 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="enablecompletion" 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="completionstartonenrol"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
@@ -121,7 +123,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"/>
@@ -141,9 +143,104 @@
         <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="courseinstance"/>
+        <FIELD NAME="courseinstance" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" COMMENT="Course instance id (if using course criteria type)" PREVIOUS="moduleinstance" 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="courseinstance" 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="timeenrolled"/>
+        <FIELD NAME="timeenrolled" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timenotified" NEXT="timestarted"/>
+        <FIELD NAME="timestarted" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timeenrolled" NEXT="timecompleted"/>
+        <FIELD NAME="timecompleted" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" PREVIOUS="timestarted" NEXT="reaggregate"/>
+        <FIELD NAME="reaggregate" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timecompleted"/>
+      </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"/>
         <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.392)
+++ moodle/lib/db/upgrade.php Locally Modified (Based On 1.392)
@@ -3645,6 +3645,169 @@
       }
 
 
+
+    if ($result && $oldversion < 2010042400) {
+
+    /// 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('courseinstance', 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('timeenrolled', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timestarted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timecompleted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+        $table->add_field('reaggregate', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+
+    /// 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 completionstartonenrol to be added to course
+        $field = new xmldb_field('completionstartonenrol', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0', 'enablecompletion');
+
+    /// Conditionally launch add field completionstartonenrol
+        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, 2010042400);
+    }
+
+
     return $result;
 }
 
Index: moodle/lib/navigationlib.php
--- moodle/lib/navigationlib.php Base (1.97)
+++ moodle/lib/navigationlib.php Locally Modified (Based On 1.97)
@@ -2279,7 +2279,13 @@
             // Add the course settings link
             $url = new moodle_url('/course/edit.php', array('id'=>$course->id));
             $coursenode->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
+
+            // Add the course completoins settings link
+            if ($CFG->enablecompletion && $course->enablecompletion) {
+                $url = new moodle_url('/course/completion.php', array('id'=>$course->id));
+                $coursenode->add(get_string('completion', 'completion'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
         }
+        }
 
         if (has_capability('moodle/role:assign', $coursecontext)) {
             // Add assign or override roles if allowed
Index: moodle/nbproject/.cvsignore
--- moodle/nbproject/.cvsignore No Base Revision
+++ moodle/nbproject/.cvsignore Locally New
@@ -0,0 +1 @@
+private
Index: moodle/nbproject/project.properties
--- moodle/nbproject/project.properties No Base Revision
+++ moodle/nbproject/project.properties Locally New
@@ -0,0 +1,7 @@
+include.path=${php.global.include.path}
+php.version=PHP_5
+source.encoding=UTF-8
+src.dir=.
+tags.asp=false
+tags.short=true
+web.root=.
Index: moodle/nbproject/project.xml
--- moodle/nbproject/project.xml No Base Revision
+++ moodle/nbproject/project.xml Locally New
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.php.project</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/php-project/1">
+            <name>headcc</name>
+        </data>
+    </configuration>
+</project>
Index: moodle/user/tabs.php
--- moodle/user/tabs.php Base (1.87)
+++ moodle/user/tabs.php Locally Modified (Based On 1.87)
@@ -273,6 +273,23 @@
 
     }
 
+    // Course completion tab
+    if (!empty($CFG->enablecompletion) && ($course->id == SITEID || !empty($course->enablecompletion)) && // completion enabled
+        ($myreports || $anyreport || ($course->id == SITEID || has_capability('coursereport/completion:view', $coursecontext)))) { // permissions to view the report
+
+        // Decide if singular or plural
+        $coursecompletion = $course->id == SITEID ? 'coursecompletions' : 'coursecompletion';
+
+        // Add tab
+        $reportsecondrow[] = new tabobject(
+            'completion',
+            $CFG->wwwroot.'/course/user.php?id='.$course->id.'&amp;user='.$user->id.'&amp;mode='.$coursecompletion,
+            get_string($coursecompletion)
+        );
+    }
+
+
+
 /// Add second row to display if there is one
 
     if (!empty($secondrow)) {
Index: moodle/version.php
--- moodle/version.php Base (1.1532)
+++ moodle/version.php Locally Modified (Based On 1.1532)
@@ -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 = 2010042303;  // YYYYMMDD   = date of the last version bump
+    $version = 2010042400;  // YYYYMMDD   = date of the last version bump
                             //         XX = daily increments
 
     $release = '2.0 dev (Build: 20100425)';  // Human-friendly version name
