MDL-16234 defaults support in plugin_supports()
[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 */
49f6e5f4 84 public function __construct($course) {
4e781c7b 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
49f6e5f4 317 {course_modules_completion}
4e781c7b 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.)
81f64ed3 350 * <p>
351 * Resetting state of manual tickbox has same result as deleting state for
352 * it.
4e781c7b 353 * @param object $cm Activity
354 */
355 public function reset_all_state($cm) {
81f64ed3 356 if($cm->completion==COMPLETION_TRACKING_MANUAL) {
357 $this->delete_all_state($cm);
358 return;
359 }
4e781c7b 360 global $DB;
361 // Get current list of users with completion state
362 $rs=$DB->get_recordset('course_modules_completion',array('coursemoduleid'=>$cm->id),'','userid');
363 $keepusers=array();
364 foreach($rs as $rec) {
365 $keepusers[]=$rec->userid;
366 }
367 $rs->close();
368
369 // Delete all existing state [also clears session cache for current user]
370 $this->delete_all_state($cm);
371
372 // Merge this with list of planned users (according to roles)
373 $trackedusers=$this->internal_get_tracked_users(false);
374 foreach($trackedusers as $trackeduser) {
375 $keepusers[]=$trackeduser->id;
376 }
377 $keepusers=array_unique($keepusers);
378
379 // Recalculate state for each kept user
380 foreach($keepusers as $keepuser) {
381 $this->update_state($cm,COMPLETION_UNKNOWN,$keepuser);
382 }
383 }
384
385 /**
386 * Obtains completion data for a particular activity and user (from the
387 * session cache if available, or by SQL query)
388 *
389 * @param object $cm Activity
390 * @param bool $wholecourse If true (default false) then, when necessary to
391 * fill the cache, retrieves information from the entire course not just for
392 * this one activity
393 * @param int $userid User ID or 0 (default) for current user
394 * @param array $modinfo For unit testing only, supply the value
395 * here. Otherwise the method calls get_fast_modinfo
396 * @return object Completion data (record from course_modules_completion)
397 * @throws Exception In some cases where the requested course-module is not
398 * found on the specified course
399 */
400 public function get_data($cm,$wholecourse=false,$userid=0,$modinfo=null) {
401 // Get user ID
402 global $USER,$CFG,$SESSION,$DB;
403 if(!$userid) {
404 $userid=$USER->id;
405 }
406
407 // Is this the current user?
408 $currentuser=$userid==$USER->id;
409
410 if($currentuser) {
49f6e5f4 411 // Make sure cache is present and is for current user (loginas
412 // changes this)
413 if(!isset($SESSION->completioncache) || $SESSION->completioncacheuserid!=$USER->id) {
4e781c7b 414 $SESSION->completioncache=array();
49f6e5f4 415 $SESSION->completioncacheuserid=$USER->id;
4e781c7b 416 }
417 // Expire any old data from cache
418 foreach($SESSION->completioncache as $courseid=>$activities) {
419 if(empty($activities['updated']) || $activities['updated'] < time()-COMPLETION_CACHE_EXPIRY) {
420 unset($SESSION->completioncache[$courseid]);
421 }
422 }
423 // See if requested data is present, if so use cache to get it
424 if(isset($SESSION->completioncache) &&
425 array_key_exists($this->course->id,$SESSION->completioncache) &&
426 array_key_exists($cm->id,$SESSION->completioncache[$this->course->id])) {
427 return $SESSION->completioncache[$this->course->id][$cm->id];
428 }
429 }
430
431 // Not there, get via SQL
432 if($currentuser && $wholecourse) {
433 // Get whole course data for cache
434 $alldatabycmc=$DB->get_records_sql("
435 SELECT
436 cmc.*
437 FROM
49f6e5f4 438 {course_modules} cm
439 INNER JOIN {course_modules_completion} cmc ON cmc.coursemoduleid=cm.id
4e781c7b 440 WHERE
441 cm.course=? AND cmc.userid=?",array($this->course->id,$userid));
442
443 // Reindex by cm id
444 $alldata=array();
445 if($alldatabycmc) {
446 foreach($alldatabycmc as $data) {
447 $alldata[$data->coursemoduleid]=$data;
448 }
449 }
450
451 // Get the module info and build up condition info for each one
452 if(empty($modinfo)) {
453 $modinfo=get_fast_modinfo($this->course,$userid);
454 }
455 foreach($modinfo->cms as $othercm) {
456 if(array_key_exists($othercm->id,$alldata)) {
457 $data=$alldata[$othercm->id];
458 } else {
459 // Row not present counts as 'not complete'
460 $data=new StdClass;
461 $data->id=0;
462 $data->coursemoduleid=$othercm->id;
463 $data->userid=$userid;
464 $data->completionstate=0;
465 $data->viewed=0;
466 $data->timemodified=0;
467 }
468 $SESSION->completioncache[$this->course->id][$othercm->id]=$data;
469 }
470 $SESSION->completioncache[$this->course->id]['updated']=time();
471
472 if(!isset($SESSION->completioncache[$this->course->id][$cm->id])) {
473 $this->internal_systemerror("Unexpected error: course-module {$cm->id} could not be found on course {$this->course->id}");
474 }
475 return $SESSION->completioncache[$this->course->id][$cm->id];
476 } else {
477 // Get single record
478 $data=$DB->get_record('course_modules_completion',array('coursemoduleid'=>$cm->id,'userid'=>$userid));
479 if($data==false) {
480 // Row not present counts as 'not complete'
481 $data=new StdClass;
482 $data->id=0;
483 $data->coursemoduleid=$cm->id;
484 $data->userid=$userid;
485 $data->completionstate=0;
486 $data->viewed=0;
487 $data->timemodified=0;
488 }
489
490 // Put in cache
491 if($currentuser) {
492 $SESSION->completioncache[$this->course->id][$cm->id]=$data;
493 // For single updates, only set date if it was empty before
494 if(empty($SESSION->completioncache[$this->course->id]['updated'])) {
495 $SESSION->completioncache[$this->course->id]['updated']=time();
496 }
497 }
498 }
499
500 return $data;
501 }
502
503 /**
504 * Updates completion data for a particular coursemodule and user (user is
505 * determined from $data).
506 * <p>
507 * (Internal function. Not private, so we can unit-test it.)
508 *
509 * @param object $cm Activity
510 * @param object $data Data about completion for that user
511 */
512 function internal_set_data($cm,$data) {
513 global $USER,$SESSION,$DB;
514 if($data->id) {
515 // Has real (nonzero) id meaning that a database row exists
516 $DB->update_record('course_modules_completion',$data);
517 } else {
518 // Didn't exist before, needs creating
519 $data->id=$DB->insert_record('course_modules_completion',$data);
520 }
521 if($data->userid==$USER->id) {
522 $SESSION->completioncache[$cm->course][$cm->id]=$data;
523 }
524 }
525
526 /**
527 * Obtains a list of activities for which completion is enabled on the
528 * course. The list is ordered by the section order of those activities.
529 * @param array $modinfo For unit testing only, supply the value
530 * here. Otherwise the method calls get_fast_modinfo
531 * @return array Array from $cmid => $cm of all activities with completion enabled,
532 * empty array if none
533 */
534 public function get_activities($modinfo=null) {
535 global $DB;
536
537 // Obtain those activities which have completion turned on
538 $withcompletion=$DB->get_records_select('course_modules','course='.$this->course->id.
539 ' AND completion<>'.COMPLETION_TRACKING_NONE);
540 if(count($withcompletion)==0) {
541 return array();
542 }
543
544 // Use modinfo to get section order and also add in names
545 if(empty($modinfo)) {
546 $modinfo=get_fast_modinfo($this->course);
547 }
548 $result=array();
549 foreach($modinfo->sections as $sectioncms) {
550 foreach($sectioncms as $cmid) {
551 if(array_key_exists($cmid,$withcompletion)) {
552 $result[$cmid]=$withcompletion[$cmid];
553 $result[$cmid]->modname=$modinfo->cms[$cmid]->modname;
554 $result[$cmid]->name=$modinfo->cms[$cmid]->name;
555 }
556 }
557 }
558
559 return $result;
560 }
561
562 /**
563 * Gets list of users in a course whose progress is tracked for display on the
564 * progress report.
565 * @param bool $sortfirstname True to sort with firstname
566 * @param int $groupid Optionally restrict to groupid
567 * @return array Array of user objects containing id, firstname, lastname (empty if none)
568 */
569 function internal_get_tracked_users($sortfirstname,$groupid=0) {
570 global $CFG,$DB;
571 if(!empty($CFG->progresstrackedroles)) {
572 $roles=explode(',',$CFG->progresstrackedroles);
573 } else {
574 // This causes it to default to everyone (if there is no student role)
575 $roles=array();
576 }
577 $users=get_role_users($roles,get_context_instance(CONTEXT_COURSE,$this->course->id),true,
f9498855 578 'u.id,u.firstname,u.lastname,u.idnumber',
4e781c7b 579 $sortfirstname ? 'u.firstname ASC' : 'u.lastname ASC',true,$groupid);
580 $users=$users ? $users : array(); // In case it returns false
581 return $users;
582 }
583
584 /**
585 * Obtains progress information across a course for all users on that course, or
586 * for all users in a specific group. Intended for use when displaying progress.
587 * <p>
588 * This includes only users who, in course context, have one of the roles for
589 * which progress is tracked (the progresstrackedroles admin option).
590 * <p>
591 * Users are included (in the first array) even if they do not have
592 * completion progress for any course-module.
593 *
594 * @param bool $sortfirstname If true, sort by first name, otherwise sort by
595 * last name
596 * @param int $groupid Group ID or 0 (default)/false for all groups
f9498855 597 * @return Array of user objects (like mdl_user id, firstname, lastname, idnumber)
4e781c7b 598 * containing an additional ->progress array of coursemoduleid => completionstate
599 */
600 public function get_progress_all($sortfirstname=false,$groupid=0) {
601 global $CFG,$DB;
602
603 // Get list of applicable users
604 $users=$this->internal_get_tracked_users($sortfirstname,$groupid);
605
606 // Get progress information for these users in groups of 1,000 (if needed)
607 // to avoid making the SQL IN too long
608 $result=array();
609 $userids=array();
610 foreach($users as $user) {
611 $userids[]=$user->id;
612 $result[$user->id]=$user;
613 $result[$user->id]->progress=array();
614 }
615
616 for($i=0;$i<count($userids);$i+=1000) {
617 $blocksize=count($userids)-$i < 1000 ? count($userids)-$i : 1000;
618
619 list($insql,$params)=$DB->get_in_or_equal(array_slice($userids,$i,$blocksize));
620 array_splice($params,0,0,array($this->course->id));
621 $rs=$DB->get_recordset_sql("
622SELECT
623 cmc.*
624FROM
49f6e5f4 625 {course_modules} cm
626 INNER JOIN {course_modules_completion} cmc ON cm.id=cmc.coursemoduleid
4e781c7b 627WHERE
628 cm.course=? AND cmc.userid $insql
629 ",$params);
630 if(!$rs) {
631 $this->internal_systemerror('Failed to obtain completion progress');
632 }
633 foreach($rs as $progress) {
634 $result[$progress->userid]->progress[$progress->coursemoduleid]=$progress;
635 }
636 $rs->close();
637 }
638
639 return $result;
640 }
641
642 public function inform_grade_changed($cm,&$item,&$grade,$deleted) {
643 // Bail out now if completion is not enabled for course-module, grade
644 // is not used to compute completion, or this is a different numbered
645 // grade
646 if(!$this->is_enabled($cm) ||
647 is_null($cm->completiongradeitemnumber) ||
648 $item->itemnumber!=$cm->completiongradeitemnumber) {
649 return;
650 }
651
652 // What is the expected result based on this grade?
653 if($deleted) {
654 // Grade being deleted, so only change could be to make it incomplete
655 $possibleresult=COMPLETION_INCOMPLETE;
656 } else {
657 $possibleresult=$this->internal_get_grade_state($item,$grade);
658 }
659
660 // OK, let's update state based on this
661 $this->update_state($cm,$possibleresult,$grade->userid);
662 }
663
664 /**
665 * Calculates the completion state that would result from a graded item
666 * (where grade-based completion is turned on) based on the actual grade
667 * and settings.
668 * <p>
669 * (Internal function. Not private, so we can unit-test it.)
670 *
671 * @param grade_item &$item
672 * @param grade_grade &$grade
673 * @return int Completion state e.g. COMPLETION_INCOMPLETE
674 */
675 function internal_get_grade_state(&$item,&$grade) {
676 if(!$grade) {
677 return COMPLETION_INCOMPLETE;
678 }
679 // Conditions to show pass/fail:
680 // a) Grade has pass mark (default is 0.00000 which is boolean true so be careful)
681 // b) Grade is visible (neither hidden nor hidden-until)
682 if($item->gradepass && $item->gradepass>0.000009 && !$item->hidden) {
683 // Use final grade if set otherwise raw grade
684 $score=!is_null($grade->finalgrade) ? $grade->finalgrade : $grade->rawgrade;
685
686 // We are displaying and tracking pass/fail
687 if($score>=$item->gradepass) {
688 return COMPLETION_COMPLETE_PASS;
689 } else {
690 return COMPLETION_COMPLETE_FAIL;
691 }
692 } else {
693 // Not displaying pass/fail, but we know grade exists b/c we got here
694 return COMPLETION_COMPLETE;
695 }
696 }
697
698 /**
699 * This temporary function is intended to be replaced once a Moodle exception
700 * system is agreed. Code that used to call this function should instead
701 * throw an exception, so this function should be deleted. The function is
702 * only used internally.
703 *
704 * This is to be used only for system errors (things that shouldn't happen)
705 * and not user-level errors.
706 *
707 * @param string $error Error string (will not be displayed to user unless
708 * debugging is enabled)
709 */
710 function internal_systemerror($error) {
711 global $CFG;
712 debugging($error,DEBUG_ALL);
713 print_error('err_system','completion',$CFG->wwwroot.'/course/view.php?id='.$this->course->id);
714 }
715}
716
717
718?>