MDL-15498: Completion system
[moodle.git] / lib / completionlib.php
CommitLineData
4e781c7b 1<?php
2// Contains a class used for tracking whether activities have been completed
3// by students ('completion')
4
5// Completion top-level options (admin setting enablecompletion)
6
7/** The completion system is enabled in this site/course */
8define('COMPLETION_ENABLED',1);
9/** The completion system is not enabled in this site/course */
10define('COMPLETION_DISABLED',0);
11
12// Completion tracking options per-activity (course_modules/completion)
13
14/** Completion tracking is disabled for this activity */
15define('COMPLETION_TRACKING_NONE',0);
16/** Manual completion tracking (user ticks box) is enabled for this activity */
17define('COMPLETION_TRACKING_MANUAL',1);
18/** Automatic completion tracking (system ticks box) is enabled for this activity */
19define('COMPLETION_TRACKING_AUTOMATIC',2);
20
21// Completion state values (course_modules_completion/completionstate)
22
23/** The user has not completed this activity. */
24define('COMPLETION_INCOMPLETE',0);
25/** The user has completed this activity. It is not specified whether they have
26 * passed or failed it. */
27define('COMPLETION_COMPLETE',1);
28/** The user has completed this activity with a grade above the pass mark. */
29define('COMPLETION_COMPLETE_PASS',2);
30/** The user has completed this activity but their grade is less than the pass mark */
31define('COMPLETION_COMPLETE_FAIL',3);
32
33// Completion effect changes (used only in update_state)
34
35/** The effect of this change to completion status is unknown. */
36define('COMPLETION_UNKNOWN',-1);
37/** The user's grade has changed, so their new state might be
38 * COMPLETION_COMPLETE_PASS or COMPLETION_COMPLETE_FAIL. */
39// TODO Is this useful?
40define('COMPLETION_GRADECHANGE',-2);
41
42// Whether view is required to create an activity (course_modules/completionview)
43
44/** User must view this activity */
45define('COMPLETION_VIEW_REQUIRED',1);
46/** User does not need to view this activity */
47define('COMPLETION_VIEW_NOT_REQUIRED',0);
48
49// Completion viewed state (course_modules_completion/viewed)
50
51/** User has viewed this activity */
52define('COMPLETION_VIEWED',1);
53/** User has not viewed this activity */
54define('COMPLETION_NOT_VIEWED',0);
55
56// Completion cacheing
57
58/** Cache expiry time in seconds (10 minutes) */
59define('COMPLETION_CACHE_EXPIRY',10*60);
60
61// Combining completion condition. This is also the value you should return
62// if you don't have any applicable conditions.
63/** Completion details should be ORed together and you should return false if
64 none apply */
65define('COMPLETION_OR',false);
66/** Completion details should be ANDed together and you should return true if
67 none apply */
68define('COMPLETION_AND',true);
69
70/**
71 * Class represents completion information for a course.
72 * (Does not contain any data, so you can safely construct it multiple times
73 * without causing any problems.)
74 */
75class completion_info {
76 private $course;
77
78 /**
79 * Constructs with course details.
80 *
81 * @param object $course Moodle course object. Must have at least ->id, ->enablecompletion
82 * @return completion_info
83 */
84 public function completion_info($course) {
85 $this->course=$course;
86 }
87
88 /**
89 * Static function. Determines whether completion is enabled across entire
90 * site.
91 *
92 * @return int COMPLETION_ENABLED (true) if completion is enabled for the site,
93 * COMPLETION_DISABLED (false) if it's complete
94 */
95 public static function is_enabled_for_site() {
96 global $CFG;
97 return $CFG->enablecompletion;
98 }
99
100 /**
101 * Checks whether completion is enabled in a particular course and possibly
102 * activity.
103 *
104 * @param object $cm Course-module object. If not specified, returns the course
105 * completion enable state.
106 * @return COMPLETION_ENABLED or COMPLETION_DISABLED (==0) in the case of
107 * site and course; COMPLETION_TRACKING_MANUAL, _AUTOMATIC or _NONE (==0)
108 * for a course-module.
109 */
110 public function is_enabled($cm=null) {
111 // First check global completion
112 global $CFG;
113 if($CFG->enablecompletion==COMPLETION_DISABLED) {
114 return COMPLETION_DISABLED;
115 }
116
117 // Check course completion
118 if($this->course->enablecompletion==COMPLETION_DISABLED) {
119 return COMPLETION_DISABLED;
120 }
121
122 // If there was no $cm and we got this far, then it's enabled
123 if(!$cm) {
124 return COMPLETION_ENABLED;
125 }
126
127 // Return course-module completion value
128 return $cm->completion;
129 }
130
131 /**
132 * Updates (if necessary) the completion state of activity $cm for the given
133 * user.
134 * <p>
135 * For manual completion, this function is called when completion is toggled
136 * with $possibleresult set to the target state.
137 * <p>
138 * For automatic completion, this function should be called every time a module
139 * does something which might influence a user's completion state. For example,
140 * if a forum provides options for marking itself 'completed' once a user makes
141 * N posts, this function should be called every time a user makes a new post.
142 * [After the post has been saved to the database]. When calling, you do not
143 * need to pass in the new completion state. Instead this function carries out
144 * completion calculation by checking grades and viewed state itself, and
145 * calling the involved module via modulename_get_completion_state() to check
146 * module-specific conditions.
147 *
148 * @param object $cm Course-module
149 * @param int $possibleresult Expected completion result. If the event that
150 * has just occurred (e.g. add post) can only result in making the activity
151 * complete when it wasn't before, use COMPLETION_COMPLETE. If the event that
152 * has just occurred (e.g. delete post) can only result in making the activity
153 * not complete when it was previously complete, use COMPLETION_INCOMPLETE.
154 * Otherwise use COMPLETION_UNKNOWN. Setting this value to something other than
155 * COMPLETION_UNKNOWN significantly improves performance because it will abandon
156 * processing early if the user's completion state already matches the expected
157 * result. For manual events, COMPLETION_COMPLETE or COMPLETION_INCOMPLETE
158 * must be used; these directly set the specified state.
159 * @param int $userid User ID to be updated. Default 0 = current user
160 */
161 public function update_state($cm,$possibleresult=COMPLETION_UNKNOWN,$userid=0) {
162 global $USER,$SESSION;
163 // Do nothing if completion is not enabled for that activity
164 if(!$this->is_enabled($cm)) {
165 return;
166 }
167
168 // Get current value of completion state and do nothing if it's same as
169 // the possible result of this change. If the change is to COMPLETE and the
170 // current value is one of the COMPLETE_xx subtypes, ignore that as well
171 $current=$this->get_data($cm,false,$userid);
172 if($possibleresult==$current->completionstate ||
173 ($possibleresult==COMPLETION_COMPLETE &&
174 ($current->completionstate==COMPLETION_COMPLETE_PASS ||
175 $current->completionstate==COMPLETION_COMPLETE_FAIL))) {
176 return;
177 }
178
179 if($cm->completion==COMPLETION_TRACKING_MANUAL) {
180 // For manual tracking we set the result directly
181 switch($possibleresult) {
182 case COMPLETION_COMPLETE:
183 case COMPLETION_INCOMPLETE:
184 $newstate=$possibleresult;
185 break;
186 default:
187 $this->internal_systemerror("Unexpected manual completion state for {$cm->id}: $possibleresult");
188 }
189 } else {
190 // Automatic tracking; get new state
191 $newstate=$this->internal_get_state($cm,$userid,$current);
192 }
193
194 // If changed, update
195 if($newstate!=$current->completionstate) {
196 $current->completionstate=$newstate;
197 $current->timemodified=time();
198 $this->internal_set_data($cm,$current);
199 }
200 }
201
202 /**
203 * Calculates the completion state for an activity and user.
204 * <p>
205 * (Internal function. Not private, so we can unit-test it.)
206 *
207 * @param object $cm Activity
208 * @param int $userid ID of user
209 * @param object $current Previous completion information from database
210 * @return unknown
211 */
212 function internal_get_state($cm,$userid,$current) {
213 // Get user ID
214 global $USER,$DB;
215 if(!$userid) {
216 $userid=$USER->id;
217 }
218
219 // Check viewed
220 if($cm->completionview==COMPLETION_VIEW_REQUIRED &&
221 $current->viewed==COMPLETION_NOT_VIEWED) {
222 return COMPLETION_INCOMPLETE;
223 }
224
225 // Modname hopefully is provided in $cm but just in case it isn't, let's grab it
226 if(!isset($cm->modname)) {
227 $cm->modname=$DB->get_field('modules','name',array('id'=>$cm->module));
228 }
229
230 $newstate=COMPLETION_COMPLETE;
231
232 // Check grade
233 if(!is_null($cm->completiongradeitemnumber)) {
234 $item=grade_item::fetch(array('courseid'=>$cm->course,'itemtype'=>'mod',
235 'itemmodule'=>$cm->modname,'iteminstance'=>$cm->instance,
236 'itemnumber'=>$cm->completiongradeitemnumber));
237 if($item) {
238 // Fetch 'grades' (will be one or none)
239 $grades=grade_grade::fetch_users_grades($item,array($userid),false);
240 if(empty($grades)) {
241 // No grade for user
242 return COMPLETION_INCOMPLETE;
243 }
244 if(count($grades)>1) {
245 $this->internal_systemerror("Unexpected result: multiple grades for
246 item '{$item->id}', user '{$userid}'");
247 }
248 $newstate=$this->internal_get_grade_state($item,reset($grades));
249 if($newstate==COMPLETION_INCOMPLETE) {
250 return COMPLETION_INCOMPLETE;
251 }
252 } else {
253 $this->internal_systemerror("Cannot find grade item for '{$cm->modname}'
254 cm '{$cm->id}' matching number '{$cm->completiongradeitemnumber}'");
255 }
256 }
257
258 if(plugin_supports('mod',$cm->modname,FEATURE_COMPLETION_HAS_RULES)) {
259 $function=$cm->modname.'_get_completion_state';
260 if(!function_exists($function)) {
261 $this->internal_systemerror("Module {$cm->modname} claims to support
262 FEATURE_COMPLETION_HAS_RULES but does not have required
263 {$cm->modname}_get_completion_state function");
264 }
265 if(!$function($this->course,$cm,$userid,COMPLETION_AND)) {
266 return COMPLETION_INCOMPLETE;
267 }
268 }
269
270 return $newstate;
271
272 }
273
274
275 /**
276 * Marks a module as viewed.
277 * <p>
278 * Should be called whenever a module is 'viewed' (it is up to the module how to
279 * determine that). Has no effect if viewing is not set as a completion condition.
280 *
281 * @param object $cm Activity
282 * @param int $userid User ID or 0 (default) for current user
283 */
284 public function set_module_viewed($cm,$userid=0) {
285 // Don't do anything if view condition is not turned on
286 if($cm->completionview==COMPLETION_VIEW_NOT_REQUIRED || !$this->is_enabled($cm)) {
287 return;
288 }
289 // Get current completion state
290 $data=$this->get_data($cm,$userid);
291 // If we already viewed it, don't do anything
292 if($data->viewed==COMPLETION_VIEWED) {
293 return;
294 }
295 // OK, change state, save it, and update completion
296 $data->viewed=COMPLETION_VIEWED;
297 $this->internal_set_data($cm,$data);
298 $this->update_state($cm,COMPLETION_COMPLETE,$userid);
299 }
300
301 /**
302 * Determines how much completion data exists for an activity. This is used when
303 * deciding whether completion information should be 'locked' in the module
304 * editing form.
305 *
306 * @param object $cm Activity
307 * @return int The number of users who have completion data stored for this
308 * activity, 0 if none
309 */
310 public function count_user_data($cm) {
311 global $CFG,$DB;
312
313 return $DB->get_field_sql("
314 SELECT
315 COUNT(1)
316 FROM
317 {$CFG->prefix}course_modules_completion
318 WHERE
319 coursemoduleid=? AND completionstate<>0",array($cm->id));
320 }
321
322 /**
323 * Deletes completion state related to an activity for all users.
324 * <p>
325 * Intended for use only when the activity itself is deleted.
326 *
327 * @param object $cm Activity
328 */
329 public function delete_all_state($cm) {
330 global $SESSION,$DB;
331
332 // Delete from database
333 $DB->delete_records('course_modules_completion',array('coursemoduleid'=>$cm->id));
334
335 // Erase cache data for current user if applicable
336 if(isset($SESSION->completioncache) &&
337 array_key_exists($cm->course,$SESSION->completioncache) &&
338 array_key_exists($cm->id,$SESSION->completioncache[$cm->course])) {
339 unset($SESSION->completioncache[$cm->course][$cm->id]);
340 }
341 }
342
343 /**
344 * Recalculates completion state related to an activity for all users.
345 * <p>
346 * Intended for use if completion conditions change. (This should be avoided
347 * as it may cause some things to become incomplete when they were previously
348 * complete, with the effect - for example - of hiding a later activity that
349 * was previously available.)
350 *
351 * @param object $cm Activity
352 */
353 public function reset_all_state($cm) {
354 global $DB;
355 // Get current list of users with completion state
356 $rs=$DB->get_recordset('course_modules_completion',array('coursemoduleid'=>$cm->id),'','userid');
357 $keepusers=array();
358 foreach($rs as $rec) {
359 $keepusers[]=$rec->userid;
360 }
361 $rs->close();
362
363 // Delete all existing state [also clears session cache for current user]
364 $this->delete_all_state($cm);
365
366 // Merge this with list of planned users (according to roles)
367 $trackedusers=$this->internal_get_tracked_users(false);
368 foreach($trackedusers as $trackeduser) {
369 $keepusers[]=$trackeduser->id;
370 }
371 $keepusers=array_unique($keepusers);
372
373 // Recalculate state for each kept user
374 foreach($keepusers as $keepuser) {
375 $this->update_state($cm,COMPLETION_UNKNOWN,$keepuser);
376 }
377 }
378
379 /**
380 * Obtains completion data for a particular activity and user (from the
381 * session cache if available, or by SQL query)
382 *
383 * @param object $cm Activity
384 * @param bool $wholecourse If true (default false) then, when necessary to
385 * fill the cache, retrieves information from the entire course not just for
386 * this one activity
387 * @param int $userid User ID or 0 (default) for current user
388 * @param array $modinfo For unit testing only, supply the value
389 * here. Otherwise the method calls get_fast_modinfo
390 * @return object Completion data (record from course_modules_completion)
391 * @throws Exception In some cases where the requested course-module is not
392 * found on the specified course
393 */
394 public function get_data($cm,$wholecourse=false,$userid=0,$modinfo=null) {
395 // Get user ID
396 global $USER,$CFG,$SESSION,$DB;
397 if(!$userid) {
398 $userid=$USER->id;
399 }
400
401 // Is this the current user?
402 $currentuser=$userid==$USER->id;
403
404 if($currentuser) {
405 // Make sure cache is present
406 if(!isset($SESSION->completioncache)) {
407 $SESSION->completioncache=array();
408 }
409 // Expire any old data from cache
410 foreach($SESSION->completioncache as $courseid=>$activities) {
411 if(empty($activities['updated']) || $activities['updated'] < time()-COMPLETION_CACHE_EXPIRY) {
412 unset($SESSION->completioncache[$courseid]);
413 }
414 }
415 // See if requested data is present, if so use cache to get it
416 if(isset($SESSION->completioncache) &&
417 array_key_exists($this->course->id,$SESSION->completioncache) &&
418 array_key_exists($cm->id,$SESSION->completioncache[$this->course->id])) {
419 return $SESSION->completioncache[$this->course->id][$cm->id];
420 }
421 }
422
423 // Not there, get via SQL
424 if($currentuser && $wholecourse) {
425 // Get whole course data for cache
426 $alldatabycmc=$DB->get_records_sql("
427 SELECT
428 cmc.*
429 FROM
430 {$CFG->prefix}course_modules cm
431 INNER JOIN {$CFG->prefix}course_modules_completion cmc ON cmc.coursemoduleid=cm.id
432 WHERE
433 cm.course=? AND cmc.userid=?",array($this->course->id,$userid));
434
435 // Reindex by cm id
436 $alldata=array();
437 if($alldatabycmc) {
438 foreach($alldatabycmc as $data) {
439 $alldata[$data->coursemoduleid]=$data;
440 }
441 }
442
443 // Get the module info and build up condition info for each one
444 if(empty($modinfo)) {
445 $modinfo=get_fast_modinfo($this->course,$userid);
446 }
447 foreach($modinfo->cms as $othercm) {
448 if(array_key_exists($othercm->id,$alldata)) {
449 $data=$alldata[$othercm->id];
450 } else {
451 // Row not present counts as 'not complete'
452 $data=new StdClass;
453 $data->id=0;
454 $data->coursemoduleid=$othercm->id;
455 $data->userid=$userid;
456 $data->completionstate=0;
457 $data->viewed=0;
458 $data->timemodified=0;
459 }
460 $SESSION->completioncache[$this->course->id][$othercm->id]=$data;
461 }
462 $SESSION->completioncache[$this->course->id]['updated']=time();
463
464 if(!isset($SESSION->completioncache[$this->course->id][$cm->id])) {
465 $this->internal_systemerror("Unexpected error: course-module {$cm->id} could not be found on course {$this->course->id}");
466 }
467 return $SESSION->completioncache[$this->course->id][$cm->id];
468 } else {
469 // Get single record
470 $data=$DB->get_record('course_modules_completion',array('coursemoduleid'=>$cm->id,'userid'=>$userid));
471 if($data==false) {
472 // Row not present counts as 'not complete'
473 $data=new StdClass;
474 $data->id=0;
475 $data->coursemoduleid=$cm->id;
476 $data->userid=$userid;
477 $data->completionstate=0;
478 $data->viewed=0;
479 $data->timemodified=0;
480 }
481
482 // Put in cache
483 if($currentuser) {
484 $SESSION->completioncache[$this->course->id][$cm->id]=$data;
485 // For single updates, only set date if it was empty before
486 if(empty($SESSION->completioncache[$this->course->id]['updated'])) {
487 $SESSION->completioncache[$this->course->id]['updated']=time();
488 }
489 }
490 }
491
492 return $data;
493 }
494
495 /**
496 * Updates completion data for a particular coursemodule and user (user is
497 * determined from $data).
498 * <p>
499 * (Internal function. Not private, so we can unit-test it.)
500 *
501 * @param object $cm Activity
502 * @param object $data Data about completion for that user
503 */
504 function internal_set_data($cm,$data) {
505 global $USER,$SESSION,$DB;
506 if($data->id) {
507 // Has real (nonzero) id meaning that a database row exists
508 $DB->update_record('course_modules_completion',$data);
509 } else {
510 // Didn't exist before, needs creating
511 $data->id=$DB->insert_record('course_modules_completion',$data);
512 }
513 if($data->userid==$USER->id) {
514 $SESSION->completioncache[$cm->course][$cm->id]=$data;
515 }
516 }
517
518 /**
519 * Obtains a list of activities for which completion is enabled on the
520 * course. The list is ordered by the section order of those activities.
521 * @param array $modinfo For unit testing only, supply the value
522 * here. Otherwise the method calls get_fast_modinfo
523 * @return array Array from $cmid => $cm of all activities with completion enabled,
524 * empty array if none
525 */
526 public function get_activities($modinfo=null) {
527 global $DB;
528
529 // Obtain those activities which have completion turned on
530 $withcompletion=$DB->get_records_select('course_modules','course='.$this->course->id.
531 ' AND completion<>'.COMPLETION_TRACKING_NONE);
532 if(count($withcompletion)==0) {
533 return array();
534 }
535
536 // Use modinfo to get section order and also add in names
537 if(empty($modinfo)) {
538 $modinfo=get_fast_modinfo($this->course);
539 }
540 $result=array();
541 foreach($modinfo->sections as $sectioncms) {
542 foreach($sectioncms as $cmid) {
543 if(array_key_exists($cmid,$withcompletion)) {
544 $result[$cmid]=$withcompletion[$cmid];
545 $result[$cmid]->modname=$modinfo->cms[$cmid]->modname;
546 $result[$cmid]->name=$modinfo->cms[$cmid]->name;
547 }
548 }
549 }
550
551 return $result;
552 }
553
554 /**
555 * Gets list of users in a course whose progress is tracked for display on the
556 * progress report.
557 * @param bool $sortfirstname True to sort with firstname
558 * @param int $groupid Optionally restrict to groupid
559 * @return array Array of user objects containing id, firstname, lastname (empty if none)
560 */
561 function internal_get_tracked_users($sortfirstname,$groupid=0) {
562 global $CFG,$DB;
563 if(!empty($CFG->progresstrackedroles)) {
564 $roles=explode(',',$CFG->progresstrackedroles);
565 } else {
566 // This causes it to default to everyone (if there is no student role)
567 $roles=array();
568 }
569 $users=get_role_users($roles,get_context_instance(CONTEXT_COURSE,$this->course->id),true,
570 'u.id,u.firstname,u.lastname',
571 $sortfirstname ? 'u.firstname ASC' : 'u.lastname ASC',true,$groupid);
572 $users=$users ? $users : array(); // In case it returns false
573 return $users;
574 }
575
576 /**
577 * Obtains progress information across a course for all users on that course, or
578 * for all users in a specific group. Intended for use when displaying progress.
579 * <p>
580 * This includes only users who, in course context, have one of the roles for
581 * which progress is tracked (the progresstrackedroles admin option).
582 * <p>
583 * Users are included (in the first array) even if they do not have
584 * completion progress for any course-module.
585 *
586 * @param bool $sortfirstname If true, sort by first name, otherwise sort by
587 * last name
588 * @param int $groupid Group ID or 0 (default)/false for all groups
589 * @return Array of user objects (like mdl_user id, firstname, lastname)
590 * containing an additional ->progress array of coursemoduleid => completionstate
591 */
592 public function get_progress_all($sortfirstname=false,$groupid=0) {
593 global $CFG,$DB;
594
595 // Get list of applicable users
596 $users=$this->internal_get_tracked_users($sortfirstname,$groupid);
597
598 // Get progress information for these users in groups of 1,000 (if needed)
599 // to avoid making the SQL IN too long
600 $result=array();
601 $userids=array();
602 foreach($users as $user) {
603 $userids[]=$user->id;
604 $result[$user->id]=$user;
605 $result[$user->id]->progress=array();
606 }
607
608 for($i=0;$i<count($userids);$i+=1000) {
609 $blocksize=count($userids)-$i < 1000 ? count($userids)-$i : 1000;
610
611 list($insql,$params)=$DB->get_in_or_equal(array_slice($userids,$i,$blocksize));
612 array_splice($params,0,0,array($this->course->id));
613 $rs=$DB->get_recordset_sql("
614SELECT
615 cmc.*
616FROM
617 {$CFG->prefix}course_modules cm
618 INNER JOIN {$CFG->prefix}course_modules_completion cmc ON cm.id=cmc.coursemoduleid
619WHERE
620 cm.course=? AND cmc.userid $insql
621 ",$params);
622 if(!$rs) {
623 $this->internal_systemerror('Failed to obtain completion progress');
624 }
625 foreach($rs as $progress) {
626 $result[$progress->userid]->progress[$progress->coursemoduleid]=$progress;
627 }
628 $rs->close();
629 }
630
631 return $result;
632 }
633
634 public function inform_grade_changed($cm,&$item,&$grade,$deleted) {
635 // Bail out now if completion is not enabled for course-module, grade
636 // is not used to compute completion, or this is a different numbered
637 // grade
638 if(!$this->is_enabled($cm) ||
639 is_null($cm->completiongradeitemnumber) ||
640 $item->itemnumber!=$cm->completiongradeitemnumber) {
641 return;
642 }
643
644 // What is the expected result based on this grade?
645 if($deleted) {
646 // Grade being deleted, so only change could be to make it incomplete
647 $possibleresult=COMPLETION_INCOMPLETE;
648 } else {
649 $possibleresult=$this->internal_get_grade_state($item,$grade);
650 }
651
652 // OK, let's update state based on this
653 $this->update_state($cm,$possibleresult,$grade->userid);
654 }
655
656 /**
657 * Calculates the completion state that would result from a graded item
658 * (where grade-based completion is turned on) based on the actual grade
659 * and settings.
660 * <p>
661 * (Internal function. Not private, so we can unit-test it.)
662 *
663 * @param grade_item &$item
664 * @param grade_grade &$grade
665 * @return int Completion state e.g. COMPLETION_INCOMPLETE
666 */
667 function internal_get_grade_state(&$item,&$grade) {
668 if(!$grade) {
669 return COMPLETION_INCOMPLETE;
670 }
671 // Conditions to show pass/fail:
672 // a) Grade has pass mark (default is 0.00000 which is boolean true so be careful)
673 // b) Grade is visible (neither hidden nor hidden-until)
674 if($item->gradepass && $item->gradepass>0.000009 && !$item->hidden) {
675 // Use final grade if set otherwise raw grade
676 $score=!is_null($grade->finalgrade) ? $grade->finalgrade : $grade->rawgrade;
677
678 // We are displaying and tracking pass/fail
679 if($score>=$item->gradepass) {
680 return COMPLETION_COMPLETE_PASS;
681 } else {
682 return COMPLETION_COMPLETE_FAIL;
683 }
684 } else {
685 // Not displaying pass/fail, but we know grade exists b/c we got here
686 return COMPLETION_COMPLETE;
687 }
688 }
689
690 /**
691 * This temporary function is intended to be replaced once a Moodle exception
692 * system is agreed. Code that used to call this function should instead
693 * throw an exception, so this function should be deleted. The function is
694 * only used internally.
695 *
696 * This is to be used only for system errors (things that shouldn't happen)
697 * and not user-level errors.
698 *
699 * @param string $error Error string (will not be displayed to user unless
700 * debugging is enabled)
701 */
702 function internal_systemerror($error) {
703 global $CFG;
704 debugging($error,DEBUG_ALL);
705 print_error('err_system','completion',$CFG->wwwroot.'/course/view.php?id='.$this->course->id);
706 }
707}
708
709
710?>