From f3a9464d235285e4040b2b7074b7c1431bd3a750 Mon Sep 17 00:00:00 2001
From: Rafi Eliasaf <rafi.eliasaf@kineo.co.il>
Date: Thu, 2 Aug 2012 11:10:20 +0300
Subject: [PATCH] refs #184 temp commit for recurrence course completion

---
 course/completion.php                              |   11 +
 course/completion_form.php                         |    8 +
 lib/completion/completion_completion.php           |   37 +++
 lib/completion/completion_completion_his.php       |  269 ++++++++++++++++++++
 lib/completion/completion_criteria.php             |    2 +
 .../completion_criteria_completion_his.php         |  149 +++++++++++
 lib/completion/completion_criteria_recurrence.php  |  135 ++++++++++
 lib/completion/cron.php                            |   17 +-
 local/db/upgrade.php                               |   97 +++++++
 local/version.php                                  |    2 +-
 10 files changed, 724 insertions(+), 3 deletions(-)
 create mode 100644 lib/completion/completion_completion_his.php
 create mode 100644 lib/completion/completion_criteria_completion_his.php
 create mode 100644 lib/completion/completion_criteria_recurrence.php

diff --git a/course/completion.php b/course/completion.php
index afebbef..192a4ef 100644
--- a/course/completion.php
+++ b/course/completion.php
@@ -34,6 +34,7 @@ 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.'/completion/completion_criteria_recurrence.php');
 require_once $CFG->libdir.'/gradelib.php';
 require_once('completion_form.php');
 
@@ -105,6 +106,16 @@ if ($form->is_cancelled()){
     $aggregation->setMethod($data->overall_aggregation);
     $aggregation->save();
 
+    // Recurrence aggregation
+    if (empty($data->recurrence_aggregation)) {
+        $data->course_aggregation = 0;
+    }
+
+    $agg_data['criteriatype'] = COMPLETION_CRITERIA_TYPE_RECURRENCE;
+    $aggregation = new completion_aggregation($agg_data);
+    $aggregation->setMethod($data->recurrence_aggregation);
+    $aggregation->save();
+    
     // Activity aggregation
     if (empty($data->activity_aggregation)) {
         $data->activity_aggregation = 0;
diff --git a/course/completion_form.php b/course/completion_form.php
index 2859f05..abd997c 100644
--- a/course/completion_form.php
+++ b/course/completion_form.php
@@ -54,6 +54,14 @@ class course_completion_form extends moodleform {
         // Get array of all available aggregation methods
         $aggregation_methods = $completion->get_aggregation_methods();
 
+        // Recurrence criteria aggregation
+        $mform->addElement('header', 'recurrencecriteria', get_string('recurrencecriteriaaggregation', 'completion'));
+        $mform->setHelpButton('recurrencecriteria', array('completionrecurrencecriteria', get_string('recurrencecriteriaaggregation', 'completion')), true);
+        $mform->addElement('advcheckbox', 'recurrence_aggregation', get_string('recurrencemethod', 'completion'),get_string('recurrencedec', 'completion'),null,array(0,1));
+        $mform->setDefault('recurrence_aggregation', $completion->get_aggregation_method(COMPLETION_CRITERIA_TYPE_RECURRENCE));//TODO FIXME
+        $mform->setHelpButton('recurrence_aggregation', array('completionaggregationmethod', get_string('aggregationmethod', 'completion')), true);
+        
+
         // Overall criteria aggregation
         $mform->addElement('header', 'overallcriteria', get_string('overallcriteriaaggregation', 'completion'));
         $mform->setHelpButton('overallcriteria', array('completionoverallcriteria', get_string('overallcriteriaaggregation', 'completion')), true);
diff --git a/lib/completion/completion_completion.php b/lib/completion/completion_completion.php
index 2ea5913..9b0d334 100644
--- a/lib/completion/completion_completion.php
+++ b/lib/completion/completion_completion.php
@@ -28,6 +28,7 @@ require_once($CFG->libdir.'/data_object.php');
 require_once($CFG->libdir.'/completionlib.php');
 require_once($CFG->dirroot.'/blocks/totara_stats/locallib.php');
 require_once($CFG->dirroot.'/local/plan/lib.php');
+require_once($CFG->libdir.'/completion/completion_completion_his.php');
 
 /**
  * Course completion status constants
@@ -35,6 +36,7 @@ require_once($CFG->dirroot.'/local/plan/lib.php');
  * For translating database recorded integers to strings and back
  */
 define('COMPLETION_STATUS_NOTYETSTARTED',   10);
+define('COMPLETION_STATUS_RECURRENCE',      15);
 define('COMPLETION_STATUS_INPROGRESS',      25);
 define('COMPLETION_STATUS_COMPLETE',        50);
 define('COMPLETION_STATUS_COMPLETEVIARPL',  75);
@@ -42,6 +44,7 @@ define('COMPLETION_STATUS_COMPLETEVIARPL',  75);
 global $COMPLETION_STATUS;
 $COMPLETION_STATUS = array(
     COMPLETION_STATUS_NOTYETSTARTED => 'notyetstarted',
+    COMPLETION_STATUS_RECURRENCE => 'recurrence',
     COMPLETION_STATUS_INPROGRESS => 'inprogress',
     COMPLETION_STATUS_COMPLETE => 'complete',
     COMPLETION_STATUS_COMPLETEVIARPL => 'completeviarpl',
@@ -415,6 +418,40 @@ class completion_completion extends data_object {
             return $this->insert();
         }
     }
+    
+    public function mark_recurrence(){
+
+        begin_sql();
+//        $data =  get_object_vars($this); //or (array)$this DATA_OBJECT_FETCH_BY_KEY
+        $comphis = new completion_completion_his();
+        $comphis->course         = $this->course;
+        $comphis->userid         = $this->userid;
+        $comphis->timestarted    = $this->timestarted;
+        $comphis->status         = $this->status;
+        $comphis->timenotified   = $this->timecompleted;//TODO RAFI
+        $comphis->timeenrolled   = $this->timeenrolled;
+        $comphis->timecompleted  = $this->timecompleted;
+        $comphis->positionid     = $this->positionid;
+        $comphis->organisationid = $this->organisationid;
+        $comphis->reaggregate    = $this->reaggregate;
+        $comphis->rpl            = $this->rpl;
+        
+        if($comphis->move_to_hist()){
+            $this->timecompleted = NULL;
+            $this->status = COMPLETION_STATUS_RECURRENCE;
+            if(!$this->update()){
+                debugging("move_to_hist() failed COMPLETION_STATUS_RECURRENCE");                
+                rollback_sql();               
+                return false;
+            }
+        }else{
+            debugging("hist failed");
+            rollback_sql();
+            return false;
+        }
+        return commit_sql();
+    }
+
 }
 
 
diff --git a/lib/completion/completion_completion_his.php b/lib/completion/completion_completion_his.php
new file mode 100644
index 0000000..d00888b
--- /dev/null
+++ b/lib/completion/completion_completion_his.php
@@ -0,0 +1,269 @@
+<?php
+
+/**
+ * Course completion status for a particular user/course
+ *
+ * @package   totara
+ * @copyright 2012 KINEO
+ * @author    Rafi Eliasaf <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once($CFG->libdir.'/completion/completion_completion.php');
+require_once($CFG->libdir.'/completion/completion_criteria_completion_his.php');
+
+/**
+ * Course completion status for a particular user/course
+ */
+class completion_completion_his extends data_object {//TODO ad abstract class for completion_completion
+
+    /**
+     * DB Table
+     * @var string $table
+     */
+    public $table = 'course_completions_his';
+
+    /**
+     * Array of required table fields, must start with 'id'.
+     * @var array $required_fields
+     */
+    public $required_fields = array('id', 'userid', 'course', 'organisationid', 'positionid',
+        'deleted', 'timenotified', 'timeenrolled', 'timestarted', 'timecompleted', 'rpl', 'reaggregate', 'status');
+
+    /**
+     * Array of optional table fields
+     * @var array $optional_fields
+     */
+    public $optional_fields = array('name' => '');
+
+    /**
+     * User ID
+     * @access  public
+     * @var     int
+     */
+    public $userid;
+
+    /**
+     * Course ID
+     * @access  public
+     * @var     int
+     */
+    public $course;
+
+    /**
+     * ID of the user's organisation when they achieved this course completion
+     *
+     * This is taken from their primary position assignment (if any)
+     * @access  public
+     * @var     int
+     */
+    public $organisationid;
+
+    /**
+     * ID of the user's position when they achieved this course completion
+     *
+     * This is taken from their primary position assignment (if any)
+     * @access  public
+     * @var     int
+     */
+    public $positionid;
+
+    /**
+     * Set to 1 if this record has been deleted
+     * @access  public
+     * @var     int
+     */
+    public $deleted;
+
+    /**
+     * Record of prior learning, leave blank if none
+     * @access  public
+     * @var     string
+     */
+    public $rpl;
+
+    /**
+     * 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;
+
+    /**
+     * Course name (optional field)
+     * @access  public
+     * @var     string
+     */
+    public $name;
+
+    /**
+     * Status constant
+     * @access  public
+     * @var     int
+     */
+    public $status;
+
+
+    /**
+     * Finds and returns a data_object instance based on params.
+     * @static static
+     *
+     * @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_his', __CLASS__, $params);
+    }
+
+    /**
+     * Save course completion status
+     *
+     * This method creates a course_completions_his record if none exists
+     * @access  private
+     * @return  bool
+     */
+    private function _save() {
+
+        if (!$this->timeenrolled or !$this->status or !$this->reaggregate or !$this->timestarted) {
+            debugging('course_completions_his failed missing param');
+            return false;
+        }
+        if($this->status < COMPLETION_STATUS_COMPLETE){
+            debugging('course_completions_his failed bad status :'.$this->status);
+            return false;
+        }
+        // Save record
+        if ($this->id) {
+            // Attempt to update, and return the results
+            return $this->update();
+        } else {
+            // Attempt to insert, and return the results
+            return $this->insert();
+        }
+    }
+
+    public function move_to_hist(){
+        // Attempt to insert, and return the results
+        if(!$this->_save()){
+            debugging(' failed to save history ');
+            return false;
+        }
+        return $this->mark_criteria_reported();
+
+    }
+
+    private function mark_criteria_reported(){//TODO change name
+        if($this->archive_course_completion_crit_compl()){
+            return  $this->archive_course_modules_completion();
+        }
+        return false;
+    }
+    /**
+     * 
+     * Gets all the course completed criteria and store them in archive
+     */
+    private function archive_course_completion_crit_compl(){
+        $param = array('userid'=>$this->userid,'course'=>$this->course,'timereported'=>$this->timecompleted);
+        $select = " userid={$this->userid} AND course={$this->course} AND timecompleted >0 ";
+        //gets all related criteria in order to  archive  them
+        $criteria = get_records_select('course_completion_crit_compl',$select);
+
+        foreach ($criteria as $criterion) {
+            $param['criteriaid']=$criterion->criteriaid;
+            $criterionhis = new completion_criteria_completion_his($param);
+            $criterionhis->gradefinal = $criterion->gradefinal;
+            $criterionhis->unenroled = $criterion->unenroled;
+            $criterionhis->deleted = $criterion->deleted;
+            $criterionhis->timecompleted = $criterion->timecompleted;
+            //move to archive
+            if(!$criterionhis->_save()){
+                debugging(' failed to save criteria history ');
+                return false;
+            }
+            
+            $srccriterion = new completion_criteria_completion(get_object_vars($criterion));
+            //delete from table
+            if(!$srccriterion->delete()){
+                return false;
+            }
+        }
+        return true;
+    }
+    /**
+     * 
+     * Gets all the modules completed criteria and store them in archive
+     */
+    private function archive_course_modules_completion(){
+        global $CFG;
+        $sql = "
+            SELECT 
+            	mc.id,
+                mc.coursemoduleid,
+                mc.userid,
+                mc.completionstate,
+                mc.viewed,
+                mc.timemodified,
+                {$this->timecompleted} AS timereported
+            FROM
+                {$CFG->prefix}course_modules_completion mc
+            LEFT JOIN
+                {$CFG->prefix}course_completion_criteria cc
+             ON mc.coursemoduleid = cc.moduleinstance            
+            WHERE
+                mc.userid = ".$this->userid."
+            AND cc.course = ".$this->course."
+            AND cc.criteriatype = ".COMPLETION_CRITERIA_TYPE_ACTIVITY;
+
+        $records = get_records_sql($sql);
+        if(!$records){
+            debugging(' no course_modules_completion records to archive ');
+            return true;
+        }
+        foreach ($records as $record) {
+            if(delete_records('course_modules_completion','id',$record->id)){//TODO switch delete and insert
+                unset($record->id);
+                $record->timereported = $this->timecompleted;
+                if(!insert_record('course_modules_comp_his', $record)){
+                    debugging(' failed to insert course_modules_comp_his record ');
+                    return false;
+                }
+            }else{
+                debugging(' failed to delete course_modules_completion record ID : '.$record->id);
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/lib/completion/completion_criteria.php b/lib/completion/completion_criteria.php
index c432d60..0a9c382 100644
--- a/lib/completion/completion_criteria.php
+++ b/lib/completion/completion_criteria.php
@@ -40,6 +40,7 @@ define('COMPLETION_CRITERIA_TYPE_DURATION',     5);
 define('COMPLETION_CRITERIA_TYPE_GRADE',        6);
 define('COMPLETION_CRITERIA_TYPE_ROLE',         7);
 define('COMPLETION_CRITERIA_TYPE_COURSE',       8);
+define('COMPLETION_CRITERIA_TYPE_RECURRENCE',   9);
 
 /**
  * Criteria type constant to class name mapping
@@ -53,6 +54,7 @@ $COMPLETION_CRITERIA_TYPES = array(
     COMPLETION_CRITERIA_TYPE_GRADE      => 'grade',
     COMPLETION_CRITERIA_TYPE_ROLE       => 'role',
     COMPLETION_CRITERIA_TYPE_COURSE     => 'course',
+    COMPLETION_CRITERIA_TYPE_RECURRENCE => 'recurrence',
 );
 
 
diff --git a/lib/completion/completion_criteria_completion_his.php b/lib/completion/completion_criteria_completion_his.php
new file mode 100644
index 0000000..894e531
--- /dev/null
+++ b/lib/completion/completion_criteria_completion_his.php
@@ -0,0 +1,149 @@
+<?php
+
+
+/**
+ * Completion data for a specific user, course and critieria
+ *
+ * @package   Totara
+ * @copyright 2012 Kineo 
+ * @author    Rafi Eliasaf <rafi.eliasaf@kineo.co.il>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+require_once('completion_criteria_completion.php');
+
+
+/**
+ * Completion data for a specific user, course and critieria
+ */
+class completion_criteria_completion_his extends data_object {//TODO ad abstract class for completion_criteria_completion
+
+    /**
+     * DB Table
+     * @var string $table
+     */
+    public $table = 'course_completion_crit_his';
+
+    /**
+     * 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','timereported');
+
+    /**
+     * Unique fields to be used in where clauses
+     * when the ID is not known
+     *
+     * @access  public
+     * @var     array       $unique fields
+     */
+    public $unique_fields = array('userid', 'course', 'criteriaid','timereported');
+    
+    /**
+     * 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;
+    
+    /**
+     * Timestamp of course criteria completion
+     * @see     completion_criteria_completion::mark_his()
+     * @access  public
+     * @var     int     (timestamp)
+     */
+    public $timereported;
+
+    
+    /**
+     * 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_his', __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 criteria status text for display in reports
+     * @see     completion_criteria::get_status()
+     * @access  public
+     * @return  string
+     */
+    public function get_status() {
+        return $this->status;
+    }
+    
+    public function _save(){
+    // Save record
+        if ($this->id) {
+            
+            return $this->update();
+        } else {
+            return $this->insert();
+        }
+    }
+}
diff --git a/lib/completion/completion_criteria_recurrence.php b/lib/completion/completion_criteria_recurrence.php
new file mode 100644
index 0000000..7ee9d6a
--- /dev/null
+++ b/lib/completion/completion_criteria_recurrence.php
@@ -0,0 +1,135 @@
+<?php
+
+
+
+/**
+ * Course completion critieria - recurrence
+ *
+ * @package   totara
+ * @copyright 2012 KINEO
+ * @author    Rafi Eliasaf <aaronb@catalyst.net.nz>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class completion_criteria_recurrence extends completion_criteria {
+
+    /**
+     * Criteria type constant
+     * @var int
+     */
+    public $criteriatype = COMPLETION_CRITERIA_TYPE_RECURRENCE;
+
+    /**
+     * 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_RECURRENCE;
+        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('advcheckbox', 'recurrence_aggregation', get_string('recurrencemethod', 'completion'),get_string('recurrencedec', 'completion'),null,array(0,1));
+//        $mform->setDefault('recurrence_aggregation', 0);
+//        $mform->setHelpButton('recurrence_aggregation', array('completionaggregationmethod', get_string('aggregationmethod', 'completion')), true);
+//
+//        if ($this->id ) {
+//            $mform->setDefault('recurrence_aggregation', 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->recurrence_aggregation)) {
+//            $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) {//TODO
+        $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) {//TODO
+        // If we are marking this as complete
+        if ($is_complete && $mark)
+        {
+            $completion->completedrecurrence = 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('recurrencecompletion', '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('recurrence', '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('markingyourrecurrencecomplete', 'completion');
+        $details['status'] = '';
+
+        return $details;
+    }
+}
\ No newline at end of file
diff --git a/lib/completion/cron.php b/lib/completion/cron.php
index b6d6b7c..cebc5a2 100644
--- a/lib/completion/cron.php
+++ b/lib/completion/cron.php
@@ -79,7 +79,7 @@ function completion_cron_criteria() {
  *
  * @return  void
  */
-function completion_cron_completions() {
+function completion_cron_completions() {//TODO RAFI  AND crc.timecompleted IS NULL
     global $CFG;
 
     if (debugging()) {
@@ -100,7 +100,8 @@ function completion_cron_completions() {
             cr.id AS criteriaid,
             ra.userid AS userid,
             cr.criteriatype AS criteriatype,
-            cc.timecompleted AS timecompleted
+            cc.timecompleted AS timecompleted,
+            ccam.method AS recurrence
         FROM
             {$CFG->prefix}course_completion_criteria cr
         INNER JOIN
@@ -120,6 +121,10 @@ function completion_cron_completions() {
             {$CFG->prefix}course_completions crc
          ON crc.course = c.id
         AND crc.userid = ra.userid
+        LEFT JOIN
+            {$CFG->prefix}course_completion_aggr_methd ccam
+         ON ccam.course = c.id
+        AND ccam.criteriatype = ".COMPLETION_CRITERIA_TYPE_RECURRENCE."
         WHERE
             con.contextlevel = ".CONTEXT_COURSE."
         AND c.enablecompletion = 1
@@ -173,6 +178,7 @@ function completion_cron_completions() {
             $activity_status = null;
             $dependency_status = null;
             $role_status = null;
+            $recurrence_status = null;
 
             // Get latest timecompleted
             $timecompleted = null;
@@ -182,6 +188,9 @@ function completion_cron_completions() {
                 $timecompleted = max($timecompleted, $params->timecompleted);
 
                 $completion = new completion_criteria_completion($params, false);
+                if(isset($params->recurrence)and $params->recurrence){
+                    $recurrence_status = true;
+                }
 
                 // Handle aggregation special cases
                 if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
@@ -218,6 +227,10 @@ function completion_cron_completions() {
 
                 $ccompletion = new completion_completion(array('course' => $params->course, 'userid' => $params->userid));
                 $ccompletion->mark_complete($timecompleted);
+                
+                if($recurrence_status){
+                  $ccompletion->mark_recurrence();
+                }
             }
         }
 
diff --git a/local/db/upgrade.php b/local/db/upgrade.php
index 67fe3dd..dbb3019 100644
--- a/local/db/upgrade.php
+++ b/local/db/upgrade.php
@@ -2910,6 +2910,103 @@ function xmldb_local_upgrade($oldversion) {
             }
         }
     }
+    if($result && $oldversion < 2012072902) {
+        /// Define field recurrence 
+        $table = new XMLDBTable('comp');
+        $field = new XMLDBField('recurrence');
+        $field->setAttributes(XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'sortthread');
+
+        /// Launch add field expired
+        $result = $result && add_field($table, $field);
+        if($result){
+            $field = new XMLDBField('expired');
+            $field->setAttributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'recurrence');
+            $result = $result && add_field($table, $field);
+        }
+    
+        if ($result) {
+    
+            /// Define table course_completions_his to be created
+            $table = new XMLDBTable('course_completions_his');
+    
+            /// Adding fields to table course_completions_his
+            $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+            $table->addFieldInfo('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('rpl', XMLDB_TYPE_CHAR, '255', null, null, null, null, null, null);
+            $table->addFieldInfo('organisationid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+            $table->addFieldInfo('positionid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+            $table->addFieldInfo('deleted', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null, null, null);
+            $table->addFieldInfo('timenotified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null, null, null);
+            $table->addFieldInfo('timestarted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('timeenrolled', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('timecompleted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('reaggregate', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('status', XMLDB_TYPE_INTEGER, '3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+    
+            /// Adding keys to table course_completions_his
+            $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+    
+            /// Launch create table for course_completions_his
+            $result = $result && create_table($table);
+        }
+        if ($result) {
+    
+            /// Define table course_completion_crit_his to be created
+            $table = new XMLDBTable('course_completion_crit_his');
+    
+        /// Adding fields to table course_completion_crit_his
+            $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+            $table->addFieldInfo('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+            $table->addFieldInfo('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+            $table->addFieldInfo('criteriaid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+            $table->addFieldInfo('gradefinal', XMLDB_TYPE_NUMBER, '10, 5', null, null, null, null);
+            $table->addFieldInfo('unenroled', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+            $table->addFieldInfo('deleted', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null);
+            $table->addFieldInfo('timecompleted', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+            $table->addFieldInfo('timereported', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+    
+        /// Adding keys to table course_completion_crit_his
+            $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+    
+        /// Adding indexes to table course_completion_crit_his
+            $table->addIndexInfo('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
+            $table->addIndexInfo('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+            $table->addIndexInfo('criteriaid', XMLDB_INDEX_NOTUNIQUE, array('criteriaid'));
+            $table->addIndexInfo('timecompleted', XMLDB_INDEX_NOTUNIQUE, array('timecompleted'));
+    
+        /// Launch create table for course_completions_his
+            $result = $result && create_table($table);
+            
+        }
+        if ($result) {
+    
+             /// Define table course_modules_comp_his to be created
+            $table = new XMLDBTable('course_modules_comp_his');
+            
+    
+        /// Adding fields to table course_modules_comp_his
+            $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+            $table->addFieldInfo('coursemoduleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('completionstate', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('viewed', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, null, null, null, null, null);
+            $table->addFieldInfo('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+            $table->addFieldInfo('timereported', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
+    
+        /// Adding keys to table course_modules_comp_his
+            $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
+    
+        /// Adding indexes to table course_modules_comp_his
+            $table->addIndexInfo('coursemoduleid', XMLDB_INDEX_NOTUNIQUE, array('coursemoduleid'));
+            $table->addIndexInfo('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
+    
+        /// Launch create table for course_modules_comp_his
+             /// Launch create table for course_completions_his
+            $result = $result && create_table($table);                
+            
+        }
+    }
 
     return $result;
 }
diff --git a/local/version.php b/local/version.php
index c842164..e804fcf 100644
--- a/local/version.php
+++ b/local/version.php
@@ -25,5 +25,5 @@
 /**
  * local version information - used to keep track of local changes
  */
-$local_version = 2012011000;
+$local_version = 2012072902;
 
-- 
1.7.10.msysgit.1

