|
Moodle
2.2.1
http://www.collinsharper.com
|
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 }