Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/completionlib.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00030 defined('MOODLE_INTERNAL') || die();
00031 
00032 require_once $CFG->libdir.'/completion/completion_aggregation.php';
00033 require_once $CFG->libdir.'/completion/completion_criteria.php';
00034 require_once $CFG->libdir.'/completion/completion_completion.php';
00035 require_once $CFG->libdir.'/completion/completion_criteria_completion.php';
00036 
00037 
00039 define('COMPLETION_ENABLED', 1);
00041 define('COMPLETION_DISABLED', 0);
00042 
00043 // Completion tracking options per-activity (course_modules/completion)
00044 
00046 define('COMPLETION_TRACKING_NONE', 0);
00048 define('COMPLETION_TRACKING_MANUAL', 1);
00050 define('COMPLETION_TRACKING_AUTOMATIC', 2);
00051 
00052 // Completion state values (course_modules_completion/completionstate)
00053 
00055 define('COMPLETION_INCOMPLETE', 0);
00058 define('COMPLETION_COMPLETE', 1);
00060 define('COMPLETION_COMPLETE_PASS', 2);
00062 define('COMPLETION_COMPLETE_FAIL', 3);
00063 
00064 // Completion effect changes (used only in update_state)
00065 
00067 define('COMPLETION_UNKNOWN', -1);
00070 // TODO Is this useful?
00071 define('COMPLETION_GRADECHANGE', -2);
00072 
00073 // Whether view is required to create an activity (course_modules/completionview)
00074 
00076 define('COMPLETION_VIEW_REQUIRED', 1);
00078 define('COMPLETION_VIEW_NOT_REQUIRED', 0);
00079 
00080 // Completion viewed state (course_modules_completion/viewed)
00081 
00083 define('COMPLETION_VIEWED', 1);
00085 define('COMPLETION_NOT_VIEWED', 0);
00086 
00087 // Completion cacheing
00088 
00090 define('COMPLETION_CACHE_EXPIRY', 10*60);
00091 
00092 // Combining completion condition. This is also the value you should return
00093 // if you don't have any applicable conditions. Used for activity completion.
00096 define('COMPLETION_OR', false);
00099 define('COMPLETION_AND', true);
00100 
00101 // Course completion criteria aggregation methods
00102 define('COMPLETION_AGGREGATION_ALL',        1);
00103 define('COMPLETION_AGGREGATION_ANY',        2);
00104 
00105 
00115 class completion_info {
00121     private $course;
00122 
00128     public $course_id;
00129 
00136     private $criteria;
00137 
00143     public static function get_aggregation_methods() {
00144         return array(
00145             COMPLETION_AGGREGATION_ALL       => get_string('all'),
00146             COMPLETION_AGGREGATION_ANY       => get_string('any', 'completion'),
00147         );
00148     }
00149 
00155     public function __construct($course) {
00156         $this->course = $course;
00157         $this->course_id = $course->id;
00158     }
00159 
00169     public static function is_enabled_for_site() {
00170         global $CFG;
00171         return !empty($CFG->enablecompletion);
00172     }
00173 
00187     public function is_enabled($cm=null) {
00188         global $CFG, $DB;
00189 
00190         // First check global completion
00191         if (!isset($CFG->enablecompletion) || $CFG->enablecompletion == COMPLETION_DISABLED) {
00192             return COMPLETION_DISABLED;
00193         }
00194 
00195         // Load data if we do not have enough
00196         if (!isset($this->course->enablecompletion)) {
00197             $this->course->enablecompletion = $DB->get_field('course', 'enablecompletion', array('id' => $this->course->id));
00198         }
00199 
00200         // Check course completion
00201         if ($this->course->enablecompletion == COMPLETION_DISABLED) {
00202             return COMPLETION_DISABLED;
00203         }
00204 
00205         // If there was no $cm and we got this far, then it's enabled
00206         if (!$cm) {
00207             return COMPLETION_ENABLED;
00208         }
00209 
00210         // Return course-module completion value
00211         return $cm->completion;
00212     }
00213 
00220     public function print_help_icon() {
00221         print $this->display_help_icon();
00222     }
00223 
00229     public function display_help_icon() {
00230         global $PAGE, $OUTPUT;
00231         $result = '';
00232         if ($this->is_enabled() && !$PAGE->user_is_editing() && isloggedin() && !isguestuser()) {
00233             $result .= '<span id = "completionprogressid" class="completionprogress">'.get_string('yourprogress','completion').' ';
00234             $result .= $OUTPUT->help_icon('completionicons', 'completion');
00235             $result .= '</span>';
00236         }
00237         return $result;
00238     }
00239 
00247     public function get_completion($user_id, $criteriatype) {
00248         $completions = $this->get_completions($user_id, $criteriatype);
00249 
00250         if (empty($completions)) {
00251             return false;
00252         } elseif (count($completions) > 1) {
00253             print_error('multipleselfcompletioncriteria', 'completion');
00254         }
00255 
00256         return $completions[0];
00257     }
00258 
00266     public function get_completions($user_id, $criteriatype = null) {
00267         $criterion = $this->get_criteria($criteriatype);
00268 
00269         $completions = array();
00270 
00271         foreach ($criterion as $criteria) {
00272             $params = array(
00273                 'course'        => $this->course_id,
00274                 'userid'        => $user_id,
00275                 'criteriaid'    => $criteria->id
00276             );
00277 
00278             $completion = new completion_criteria_completion($params);
00279             $completion->attach_criteria($criteria);
00280 
00281             $completions[] = $completion;
00282         }
00283 
00284         return $completions;
00285     }
00286 
00294     public function get_user_completion($user_id, $criteria) {
00295         $params = array(
00296             'criteriaid'    => $criteria->id,
00297             'userid'        => $user_id
00298         );
00299 
00300         $completion = new completion_criteria_completion($params);
00301         return $completion;
00302     }
00303 
00310     public function has_criteria() {
00311         $criteria = $this->get_criteria();
00312 
00313         return (bool) count($criteria);
00314     }
00315 
00316 
00323     public function get_criteria($criteriatype = null) {
00324 
00325         // Fill cache if empty
00326         if (!is_array($this->criteria)) {
00327             global $DB;
00328 
00329             $params = array(
00330                 'course'    => $this->course->id
00331             );
00332 
00333             // Load criteria from database
00334             $records = (array)$DB->get_records('course_completion_criteria', $params);
00335 
00336             // Build array of criteria objects
00337             $this->criteria = array();
00338             foreach ($records as $record) {
00339                 $this->criteria[$record->id] = completion_criteria::factory($record);
00340             }
00341         }
00342 
00343         // If after all criteria
00344         if ($criteriatype === null) {
00345             return $this->criteria;
00346         }
00347 
00348         // If we are only after a specific criteria type
00349         $criteria = array();
00350         foreach ($this->criteria as $criterion) {
00351 
00352             if ($criterion->criteriatype != $criteriatype) {
00353                 continue;
00354             }
00355 
00356             $criteria[$criterion->id] = $criterion;
00357         }
00358 
00359         return $criteria;
00360     }
00361 
00368     public function get_aggregation_method($criteriatype = null) {
00369         $params = array(
00370             'course'        => $this->course_id,
00371             'criteriatype'  => $criteriatype
00372         );
00373 
00374         $aggregation = new completion_aggregation($params);
00375 
00376         if (!$aggregation->id) {
00377             $aggregation->method = COMPLETION_AGGREGATION_ALL;
00378         }
00379 
00380         return $aggregation->method;
00381     }
00382 
00388     public function get_incomplete_criteria() {
00389         $incomplete = array();
00390 
00391         foreach ($this->get_criteria() as $criteria) {
00392             if (!$criteria->is_complete()) {
00393                 $incomplete[] = $criteria;
00394             }
00395         }
00396 
00397         return $incomplete;
00398     }
00399 
00403     public function clear_criteria() {
00404         global $DB;
00405         $DB->delete_records('course_completion_criteria', array('course' => $this->course_id));
00406         $DB->delete_records('course_completion_aggr_methd', array('course' => $this->course_id));
00407 
00408         $this->delete_course_completion_data();
00409     }
00410 
00417     public function is_course_complete($user_id) {
00418         $params = array(
00419             'userid'    => $user_id,
00420             'course'  => $this->course_id
00421         );
00422 
00423         $ccompletion = new completion_completion($params);
00424         return $ccompletion->is_complete();
00425     }
00426 
00465     public function update_state($cm, $possibleresult=COMPLETION_UNKNOWN, $userid=0) {
00466         global $USER, $SESSION;
00467 
00468         // Do nothing if completion is not enabled for that activity
00469         if (!$this->is_enabled($cm)) {
00470             return;
00471         }
00472 
00473         // Get current value of completion state and do nothing if it's same as
00474         // the possible result of this change. If the change is to COMPLETE and the
00475         // current value is one of the COMPLETE_xx subtypes, ignore that as well
00476         $current = $this->get_data($cm, false, $userid);
00477         if ($possibleresult == $current->completionstate ||
00478             ($possibleresult == COMPLETION_COMPLETE &&
00479                 ($current->completionstate == COMPLETION_COMPLETE_PASS ||
00480                 $current->completionstate == COMPLETION_COMPLETE_FAIL))) {
00481             return;
00482         }
00483 
00484         if ($cm->completion == COMPLETION_TRACKING_MANUAL) {
00485             // For manual tracking we set the result directly
00486             switch($possibleresult) {
00487                 case COMPLETION_COMPLETE:
00488                 case COMPLETION_INCOMPLETE:
00489                     $newstate = $possibleresult;
00490                     break;
00491                 default:
00492                     $this->internal_systemerror("Unexpected manual completion state for {$cm->id}: $possibleresult");
00493             }
00494 
00495         } else {
00496             // Automatic tracking; get new state
00497             $newstate = $this->internal_get_state($cm, $userid, $current);
00498         }
00499 
00500         // If changed, update
00501         if ($newstate != $current->completionstate) {
00502             $current->completionstate = $newstate;
00503             $current->timemodified    = time();
00504             $this->internal_set_data($cm, $current);
00505         }
00506     }
00507 
00527     function internal_get_state($cm, $userid, $current) {
00528         global $USER, $DB, $CFG;
00529 
00530         // Get user ID
00531         if (!$userid) {
00532             $userid = $USER->id;
00533         }
00534 
00535         // Check viewed
00536         if ($cm->completionview == COMPLETION_VIEW_REQUIRED &&
00537             $current->viewed == COMPLETION_NOT_VIEWED) {
00538 
00539             return COMPLETION_INCOMPLETE;
00540         }
00541 
00542         // Modname hopefully is provided in $cm but just in case it isn't, let's grab it
00543         if (!isset($cm->modname)) {
00544             $cm->modname = $DB->get_field('modules', 'name', array('id'=>$cm->module));
00545         }
00546 
00547         $newstate = COMPLETION_COMPLETE;
00548 
00549         // Check grade
00550         if (!is_null($cm->completiongradeitemnumber)) {
00551             require_once($CFG->libdir.'/gradelib.php');
00552             $item = grade_item::fetch(array('courseid'=>$cm->course, 'itemtype'=>'mod',
00553                 'itemmodule'=>$cm->modname, 'iteminstance'=>$cm->instance,
00554                 'itemnumber'=>$cm->completiongradeitemnumber));
00555             if ($item) {
00556                 // Fetch 'grades' (will be one or none)
00557                 $grades = grade_grade::fetch_users_grades($item, array($userid), false);
00558                 if (empty($grades)) {
00559                     // No grade for user
00560                     return COMPLETION_INCOMPLETE;
00561                 }
00562                 if (count($grades) > 1) {
00563                     $this->internal_systemerror("Unexpected result: multiple grades for
00564                         item '{$item->id}', user '{$userid}'");
00565                 }
00566                 $newstate = $this->internal_get_grade_state($item, reset($grades));
00567                 if ($newstate == COMPLETION_INCOMPLETE) {
00568                     return COMPLETION_INCOMPLETE;
00569                 }
00570 
00571             } else {
00572                 $this->internal_systemerror("Cannot find grade item for '{$cm->modname}'
00573                     cm '{$cm->id}' matching number '{$cm->completiongradeitemnumber}'");
00574             }
00575         }
00576 
00577         if (plugin_supports('mod', $cm->modname, FEATURE_COMPLETION_HAS_RULES)) {
00578             $function = $cm->modname.'_get_completion_state';
00579             if (!function_exists($function)) {
00580                 $this->internal_systemerror("Module {$cm->modname} claims to support
00581                     FEATURE_COMPLETION_HAS_RULES but does not have required
00582                     {$cm->modname}_get_completion_state function");
00583             }
00584             if (!$function($this->course, $cm, $userid, COMPLETION_AND)) {
00585                 return COMPLETION_INCOMPLETE;
00586             }
00587         }
00588 
00589         return $newstate;
00590 
00591     }
00592 
00593 
00610     public function set_module_viewed($cm, $userid=0) {
00611         global $PAGE, $UNITTEST;
00612         if ($PAGE->headerprinted && empty($UNITTEST->running)) {
00613             debugging('set_module_viewed must be called before header is printed',
00614                     DEBUG_DEVELOPER);
00615         }
00616         // Don't do anything if view condition is not turned on
00617         if ($cm->completionview == COMPLETION_VIEW_NOT_REQUIRED || !$this->is_enabled($cm)) {
00618             return;
00619         }
00620         // Get current completion state
00621         $data = $this->get_data($cm, $userid);
00622         // If we already viewed it, don't do anything
00623         if ($data->viewed == COMPLETION_VIEWED) {
00624             return;
00625         }
00626         // OK, change state, save it, and update completion
00627         $data->viewed = COMPLETION_VIEWED;
00628         $this->internal_set_data($cm, $data);
00629         $this->update_state($cm, COMPLETION_COMPLETE, $userid);
00630     }
00631 
00642     public function count_user_data($cm) {
00643         global $DB;
00644 
00645         return $DB->get_field_sql("
00646     SELECT
00647         COUNT(1)
00648     FROM
00649         {course_modules_completion}
00650     WHERE
00651         coursemoduleid=? AND completionstate<>0", array($cm->id));
00652     }
00653 
00664     public function count_course_user_data($user_id = null) {
00665         global $DB;
00666 
00667         $sql = '
00668     SELECT
00669         COUNT(1)
00670     FROM
00671         {course_completion_crit_compl}
00672     WHERE
00673         course = ?
00674         ';
00675 
00676         $params = array($this->course_id);
00677 
00678         // Limit data to a single user if an ID is supplied
00679         if ($user_id) {
00680             $sql .= ' AND userid = ?';
00681             $params[] = $user_id;
00682         }
00683 
00684         return $DB->get_field_sql($sql, $params);
00685     }
00686 
00692     public function is_course_locked() {
00693         return (bool) $this->count_course_user_data();
00694     }
00695 
00704     public function delete_course_completion_data() {
00705         global $DB;
00706 
00707         $DB->delete_records('course_completions', array('course' => $this->course_id));
00708         $DB->delete_records('course_completion_crit_compl', array('course' => $this->course_id));
00709     }
00710 
00720     public function delete_all_state($cm) {
00721         global $SESSION, $DB;
00722 
00723         // Delete from database
00724         $DB->delete_records('course_modules_completion', array('coursemoduleid'=>$cm->id));
00725 
00726         // Erase cache data for current user if applicable
00727         if (isset($SESSION->completioncache) &&
00728             array_key_exists($cm->course, $SESSION->completioncache) &&
00729             array_key_exists($cm->id, $SESSION->completioncache[$cm->course])) {
00730 
00731             unset($SESSION->completioncache[$cm->course][$cm->id]);
00732         }
00733 
00734         // Check if there is an associated course completion criteria
00735         $criteria = $this->get_criteria(COMPLETION_CRITERIA_TYPE_ACTIVITY);
00736         $acriteria = false;
00737         foreach ($criteria as $criterion) {
00738             if ($criterion->moduleinstance == $cm->id) {
00739                 $acriteria = $criterion;
00740                 break;
00741     }
00742         }
00743 
00744         if ($acriteria) {
00745             // Delete all criteria completions relating to this activity
00746             $DB->delete_records('course_completion_crit_compl', array('course' => $this->course_id, 'criteriaid' => $acriteria->id));
00747             $DB->delete_records('course_completions', array('course' => $this->course_id));
00748         }
00749     }
00750 
00767     public function reset_all_state($cm) {
00768         global $DB;
00769 
00770         if ($cm->completion == COMPLETION_TRACKING_MANUAL) {
00771             $this->delete_all_state($cm);
00772             return;
00773         }
00774         // Get current list of users with completion state
00775         $rs = $DB->get_recordset('course_modules_completion', array('coursemoduleid'=>$cm->id), '', 'userid');
00776         $keepusers = array();
00777         foreach ($rs as $rec) {
00778             $keepusers[] = $rec->userid;
00779         }
00780         $rs->close();
00781 
00782         // Delete all existing state [also clears session cache for current user]
00783         $this->delete_all_state($cm);
00784 
00785         // Merge this with list of planned users (according to roles)
00786         $trackedusers = $this->get_tracked_users();
00787         foreach ($trackedusers as $trackeduser) {
00788             $keepusers[] = $trackeduser->id;
00789         }
00790         $keepusers = array_unique($keepusers);
00791 
00792         // Recalculate state for each kept user
00793         foreach ($keepusers as $keepuser) {
00794             $this->update_state($cm, COMPLETION_UNKNOWN, $keepuser);
00795         }
00796     }
00797 
00818     public function get_data($cm, $wholecourse=false, $userid=0, $modinfo=null) {
00819         global $USER, $CFG, $SESSION, $DB;
00820 
00821         // Get user ID
00822         if (!$userid) {
00823             $userid = $USER->id;
00824         }
00825 
00826         // Is this the current user?
00827         $currentuser = $userid==$USER->id;
00828 
00829         if ($currentuser && is_object($SESSION)) {
00830             // Make sure cache is present and is for current user (loginas
00831             // changes this)
00832             if (!isset($SESSION->completioncache) || $SESSION->completioncacheuserid!=$USER->id) {
00833                 $SESSION->completioncache = array();
00834                 $SESSION->completioncacheuserid = $USER->id;
00835             }
00836             // Expire any old data from cache
00837             foreach ($SESSION->completioncache as $courseid=>$activities) {
00838                 if (empty($activities['updated']) || $activities['updated'] < time()-COMPLETION_CACHE_EXPIRY) {
00839                     unset($SESSION->completioncache[$courseid]);
00840                 }
00841             }
00842             // See if requested data is present, if so use cache to get it
00843             if (isset($SESSION->completioncache) &&
00844                 array_key_exists($this->course->id, $SESSION->completioncache) &&
00845                 array_key_exists($cm->id, $SESSION->completioncache[$this->course->id])) {
00846                 return $SESSION->completioncache[$this->course->id][$cm->id];
00847             }
00848         }
00849 
00850         // Not there, get via SQL
00851         if ($currentuser && $wholecourse) {
00852             // Get whole course data for cache
00853             $alldatabycmc = $DB->get_records_sql("
00854     SELECT
00855         cmc.*
00856     FROM
00857         {course_modules} cm
00858         INNER JOIN {course_modules_completion} cmc ON cmc.coursemoduleid=cm.id
00859     WHERE
00860         cm.course=? AND cmc.userid=?", array($this->course->id, $userid));
00861 
00862             // Reindex by cm id
00863             $alldata = array();
00864             if ($alldatabycmc) {
00865                 foreach ($alldatabycmc as $data) {
00866                     $alldata[$data->coursemoduleid] = $data;
00867                 }
00868             }
00869 
00870             // Get the module info and build up condition info for each one
00871             if (empty($modinfo)) {
00872                 $modinfo = get_fast_modinfo($this->course, $userid);
00873             }
00874             foreach ($modinfo->cms as $othercm) {
00875                 if (array_key_exists($othercm->id, $alldata)) {
00876                     $data = $alldata[$othercm->id];
00877                 } else {
00878                     // Row not present counts as 'not complete'
00879                     $data = new StdClass;
00880                     $data->id              = 0;
00881                     $data->coursemoduleid  = $othercm->id;
00882                     $data->userid          = $userid;
00883                     $data->completionstate = 0;
00884                     $data->viewed          = 0;
00885                     $data->timemodified    = 0;
00886                 }
00887                 $SESSION->completioncache[$this->course->id][$othercm->id] = $data;
00888             }
00889             $SESSION->completioncache[$this->course->id]['updated'] = time();
00890 
00891             if (!isset($SESSION->completioncache[$this->course->id][$cm->id])) {
00892                 $this->internal_systemerror("Unexpected error: course-module {$cm->id} could not be found on course {$this->course->id}");
00893             }
00894             return $SESSION->completioncache[$this->course->id][$cm->id];
00895 
00896         } else {
00897             // Get single record
00898             $data = $DB->get_record('course_modules_completion', array('coursemoduleid'=>$cm->id, 'userid'=>$userid));
00899             if ($data == false) {
00900                 // Row not present counts as 'not complete'
00901                 $data = new StdClass;
00902                 $data->id              = 0;
00903                 $data->coursemoduleid  = $cm->id;
00904                 $data->userid          = $userid;
00905                 $data->completionstate = 0;
00906                 $data->viewed          = 0;
00907                 $data->timemodified    = 0;
00908             }
00909 
00910             // Put in cache
00911             if ($currentuser) {
00912                 $SESSION->completioncache[$this->course->id][$cm->id] = $data;
00913                 // For single updates, only set date if it was empty before
00914                 if (empty($SESSION->completioncache[$this->course->id]['updated'])) {
00915                     $SESSION->completioncache[$this->course->id]['updated'] = time();
00916                 }
00917             }
00918         }
00919 
00920         return $data;
00921     }
00922 
00935     function internal_set_data($cm, $data) {
00936         global $USER, $SESSION, $DB;
00937 
00938         $transaction = $DB->start_delegated_transaction();
00939         if (!$data->id) {
00940             // Check there isn't really a row
00941             $data->id = $DB->get_field('course_modules_completion', 'id',
00942                     array('coursemoduleid'=>$data->coursemoduleid, 'userid'=>$data->userid));
00943         }
00944         if (!$data->id) {
00945             // Didn't exist before, needs creating
00946             $data->id = $DB->insert_record('course_modules_completion', $data);
00947         } else {
00948             // Has real (nonzero) id meaning that a database row exists, update
00949             $DB->update_record('course_modules_completion', $data);
00950         }
00951         $transaction->allow_commit();
00952 
00953         if ($data->userid == $USER->id) {
00954             $SESSION->completioncache[$cm->course][$cm->id] = $data;
00955             $reset = 'reset';
00956             get_fast_modinfo($reset);
00957         }
00958     }
00959 
00971     public function get_activities($modinfo=null) {
00972         global $DB;
00973 
00974         // Obtain those activities which have completion turned on
00975         $withcompletion = $DB->get_records_select('course_modules', 'course='.$this->course->id.
00976           ' AND completion<>'.COMPLETION_TRACKING_NONE);
00977         if (!$withcompletion) {
00978             return array();
00979         }
00980 
00981         // Use modinfo to get section order and also add in names
00982         if (empty($modinfo)) {
00983             $modinfo = get_fast_modinfo($this->course);
00984         }
00985         $result = array();
00986         foreach ($modinfo->sections as $sectioncms) {
00987             foreach ($sectioncms as $cmid) {
00988                 if (array_key_exists($cmid, $withcompletion)) {
00989                     $result[$cmid] = $withcompletion[$cmid];
00990                     $result[$cmid]->modname = $modinfo->cms[$cmid]->modname;
00991                     $result[$cmid]->name    = $modinfo->cms[$cmid]->name;
00992                 }
00993             }
00994         }
00995 
00996         return $result;
00997     }
00998 
00999 
01007     function is_tracked_user($userid) {
01008         global $DB;
01009 
01010         $tracked = $this->generate_tracked_user_sql();
01011 
01012         $sql  = "SELECT u.id ";
01013         $sql .= $tracked->sql;
01014         $sql .= ' AND u.id = :userid';
01015 
01016         $params = $tracked->data;
01017         $params['userid'] = (int)$userid;
01018         return $DB->record_exists_sql($sql, $params);
01019     }
01020 
01021 
01032     function get_num_tracked_users($where = '', $where_params = array(), $groupid = 0) {
01033         global $DB;
01034 
01035         $tracked = $this->generate_tracked_user_sql($groupid);
01036 
01037         $sql  = "SELECT COUNT(u.id) ";
01038         $sql .= $tracked->sql;
01039 
01040         if ($where) {
01041             $sql .= " AND $where";
01042         }
01043 
01044         $params = array_merge($tracked->data, $where_params);
01045         return $DB->count_records_sql($sql, $params);
01046     }
01047 
01048 
01064     function get_tracked_users($where = '', $where_params = array(), $groupid = 0,
01065              $sort = '', $limitfrom = '', $limitnum = '', context $extracontext = null) {
01066 
01067         global $DB;
01068 
01069         $tracked = $this->generate_tracked_user_sql($groupid);
01070         $params = $tracked->data;
01071 
01072         $sql = "
01073             SELECT
01074                 u.id,
01075                 u.firstname,
01076                 u.lastname,
01077                 u.idnumber
01078         ";
01079         if ($extracontext) {
01080             $sql .= get_extra_user_fields_sql($extracontext, 'u', '', array('idnumber'));
01081         }
01082 
01083         $sql .= $tracked->sql;
01084 
01085         if ($where) {
01086             $sql .= " AND $where";
01087             $params = array_merge($params, $where_params);
01088         }
01089 
01090         if ($sort) {
01091             $sql .= " ORDER BY $sort";
01092         }
01093 
01094         $users = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
01095         return $users ? $users : array(); // In case it returns false
01096     }
01097 
01098 
01108     function generate_tracked_user_sql($groupid = 0) {
01109         global $CFG;
01110 
01111         $return = new stdClass();
01112         $return->sql = '';
01113         $return->data = array();
01114 
01115         if (!empty($CFG->gradebookroles)) {
01116             $roles = ' AND ra.roleid IN ('.$CFG->gradebookroles.')';
01117         } else {
01118             // This causes it to default to everyone (if there is no student role)
01119             $roles = '';
01120         }
01121 
01122         // Build context sql
01123         $context = get_context_instance(CONTEXT_COURSE, $this->course->id);
01124         $parentcontexts = substr($context->path, 1); // kill leading slash
01125         $parentcontexts = str_replace('/', ',', $parentcontexts);
01126         if ($parentcontexts !== '') {
01127             $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
01128         }
01129 
01130         $groupjoin   = '';
01131         $groupselect = '';
01132         if ($groupid) {
01133             $groupjoin   = "JOIN {groups_members} gm
01134                               ON gm.userid = u.id";
01135             $groupselect = " AND gm.groupid = :groupid ";
01136             
01137             $return->data['groupid'] = $groupid;
01138         }
01139 
01140         $return->sql = "
01141             FROM
01142                 {user} u
01143             INNER JOIN
01144                 {role_assignments} ra
01145              ON ra.userid = u.id
01146             INNER JOIN
01147                 {role} r
01148              ON r.id = ra.roleid
01149             INNER JOIN
01150                 {user_enrolments} ue
01151              ON ue.userid = u.id
01152             INNER JOIN
01153                 {enrol} e
01154              ON e.id = ue.enrolid
01155             INNER JOIN
01156                 {course} c
01157              ON c.id = e.courseid
01158             $groupjoin
01159             WHERE
01160                 (ra.contextid = :contextid $parentcontexts)
01161             AND c.id = :courseid
01162             AND ue.status = 0
01163             AND e.status = 0
01164             AND ue.timestart < :now1
01165             AND (ue.timeend > :now2 OR ue.timeend = 0)
01166                 $groupselect
01167                 $roles
01168         ";
01169 
01170         $now = time();
01171         $return->data['now1'] = $now;
01172         $return->data['now2'] = $now;
01173         $return->data['contextid'] = $context->id;
01174         $return->data['courseid'] = $this->course->id;
01175 
01176         return $return;
01177     }
01178 
01204     public function get_progress_all($where = '', $where_params = array(), $groupid = 0,
01205             $sort = '', $pagesize = '', $start = '', context $extracontext = null) {
01206         global $CFG, $DB;
01207 
01208         // Get list of applicable users
01209         $users = $this->get_tracked_users($where, $where_params, $groupid, $sort,
01210                 $start, $pagesize, $extracontext);
01211 
01212         // Get progress information for these users in groups of 1, 000 (if needed)
01213         // to avoid making the SQL IN too long
01214         $results = array();
01215         $userids = array();
01216         foreach ($users as $user) {
01217             $userids[] = $user->id;
01218             $results[$user->id] = $user;
01219             $results[$user->id]->progress = array();
01220         }
01221 
01222         for($i=0; $i<count($userids); $i+=1000) {
01223             $blocksize = count($userids)-$i < 1000 ? count($userids)-$i : 1000;
01224 
01225             list($insql, $params) = $DB->get_in_or_equal(array_slice($userids, $i, $blocksize));
01226             array_splice($params, 0, 0, array($this->course->id));
01227             $rs = $DB->get_recordset_sql("
01228                 SELECT
01229                     cmc.*
01230                 FROM
01231                     {course_modules} cm
01232                     INNER JOIN {course_modules_completion} cmc ON cm.id=cmc.coursemoduleid
01233                 WHERE
01234                     cm.course=? AND cmc.userid $insql
01235     ", $params);
01236             foreach ($rs as $progress) {
01237                 $progress = (object)$progress;
01238                 $results[$progress->userid]->progress[$progress->coursemoduleid] = $progress;
01239             }
01240             $rs->close();
01241         }
01242 
01243         return $results;
01244     }
01245 
01259     public function inform_grade_changed($cm, $item, $grade, $deleted) {
01260         // Bail out now if completion is not enabled for course-module, it is enabled
01261         // but is set to manual, grade is not used to compute completion, or this
01262         // is a different numbered grade
01263         if (!$this->is_enabled($cm) ||
01264             $cm->completion == COMPLETION_TRACKING_MANUAL ||
01265             is_null($cm->completiongradeitemnumber) ||
01266             $item->itemnumber != $cm->completiongradeitemnumber) {
01267             return;
01268         }
01269 
01270         // What is the expected result based on this grade?
01271         if ($deleted) {
01272             // Grade being deleted, so only change could be to make it incomplete
01273             $possibleresult = COMPLETION_INCOMPLETE;
01274         } else {
01275             $possibleresult = $this->internal_get_grade_state($item, $grade);
01276         }
01277         
01278         // OK, let's update state based on this
01279         $this->update_state($cm, $possibleresult, $grade->userid);
01280     }
01281 
01297     function internal_get_grade_state($item, $grade) {
01298         if (!$grade) {
01299             return COMPLETION_INCOMPLETE;
01300         }
01301         // Conditions to show pass/fail:
01302         // a) Grade has pass mark (default is 0.00000 which is boolean true so be careful)
01303         // b) Grade is visible (neither hidden nor hidden-until)
01304         if ($item->gradepass && $item->gradepass > 0.000009 && !$item->hidden) {
01305             // Use final grade if set otherwise raw grade
01306             $score = !is_null($grade->finalgrade) ? $grade->finalgrade : $grade->rawgrade;
01307 
01308             // We are displaying and tracking pass/fail
01309             if ($score >= $item->gradepass) {
01310                 return COMPLETION_COMPLETE_PASS;
01311             } else {
01312                 return COMPLETION_COMPLETE_FAIL;
01313             }
01314         } else {
01315             // Not displaying pass/fail, so just if there is a grade
01316             if (!is_null($grade->finalgrade) || !is_null($grade->rawgrade)) {
01317                 // Grade exists, so maybe complete now
01318                 return COMPLETION_COMPLETE;
01319             } else {
01320                 // Grade does not exist, so maybe incomplete now
01321                 return COMPLETION_INCOMPLETE;
01322             }
01323         }
01324     }
01325 
01335     function internal_systemerror($error) {
01336         global $CFG;
01337         throw new moodle_exception('err_system','completion',
01338             $CFG->wwwroot.'/course/view.php?id='.$this->course->id,null,$error);
01339     }
01340 
01346     static function wipe_session_cache() {
01347         global $SESSION;
01348         unset($SESSION->completioncache);
01349         unset($SESSION->completioncacheuserid);
01350     }
01351 }
 All Data Structures Namespaces Files Functions Variables Enumerations