2023-05-17 21:19:14 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace local_treestudyplan;
|
|
|
|
require_once($CFG->libdir.'/externallib.php');
|
|
|
|
require_once($CFG->libdir.'/gradelib.php');
|
|
|
|
require_once($CFG->dirroot.'/course/lib.php');
|
|
|
|
|
|
|
|
use core_course\local\repository\caching_content_item_readonly_repository;
|
|
|
|
use core_course\local\repository\content_item_readonly_repository;
|
|
|
|
use \grade_item;
|
|
|
|
use \grade_scale;
|
|
|
|
use \grade_outcome;
|
|
|
|
|
|
|
|
class corecompletioninfo {
|
|
|
|
private $course;
|
|
|
|
private $completion;
|
|
|
|
private $modinfo;
|
|
|
|
private static $COMPLETION_HANDLES = null;
|
|
|
|
|
|
|
|
public function id(){
|
|
|
|
return $this->course->id;
|
|
|
|
}
|
|
|
|
public function __construct($course){
|
|
|
|
global $DB;
|
|
|
|
$this->course = $course;
|
|
|
|
$this->completion = new \completion_info($this->course);
|
|
|
|
$this->modinfo = get_fast_modinfo($this->course);
|
|
|
|
}
|
|
|
|
|
|
|
|
static public function completiontypes(){
|
|
|
|
global $COMPLETION_CRITERIA_TYPES;
|
|
|
|
// Just return the keys of the global array COMPLETION_CRITERIA_TYPES, so we don't have to manually
|
|
|
|
// add any completion types....
|
|
|
|
return \array_keys($COMPLETION_CRITERIA_TYPES);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Translate a numeric completion constant to a text string
|
|
|
|
* @param $completion The completion code as defined in completionlib.php to translate to a text handle
|
|
|
|
*/
|
|
|
|
static public function completion_handle($completion){
|
|
|
|
if(empty(self::$COMPLETION_HANDLES)){
|
|
|
|
// Cache the translation table, to avoid overhead
|
|
|
|
self::$COMPLETION_HANDLES = [
|
|
|
|
COMPLETION_INCOMPLETE => "incomplete",
|
|
|
|
COMPLETION_COMPLETE => "complete",
|
|
|
|
COMPLETION_COMPLETE_PASS => "complete-pass",
|
|
|
|
COMPLETION_COMPLETE_FAIL => "complete-fail",
|
|
|
|
COMPLETION_COMPLETE_FAIL_HIDDEN => "complete-fail"]; // the front end won't differentiate between hidden or not
|
|
|
|
}
|
|
|
|
return self::$COMPLETION_HANDLES[$completion] ?? "undefined";
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function completion_item_editor_structure($value=VALUE_REQUIRED){
|
|
|
|
return new \external_single_structure([
|
|
|
|
"title" => new \external_value(PARAM_TEXT,'name of subitem',VALUE_OPTIONAL),
|
|
|
|
"link" => new \external_value(PARAM_TEXT, 'optional link to more details',VALUE_OPTIONAL),
|
|
|
|
// ADD BELOW IF NEEDED - try using name, description and link fields first
|
|
|
|
/*
|
|
|
|
"required_grade" => new \external_value(PARAM_TEXT, 'required_grade',VALUE_OPTIONAL),
|
|
|
|
"course_link" => course_info::simple_structure(VALUE_OPTIONAL),
|
|
|
|
*/
|
|
|
|
], 'completion type',$value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function completion_type_editor_structure($value=VALUE_REQUIRED){
|
|
|
|
return new \external_single_structure([
|
|
|
|
"items" => new \external_multiple_structure(self::completion_item_editor_structure(),'subitems',VALUE_OPTIONAL),
|
|
|
|
"title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL),
|
|
|
|
"desc" => new \external_value(PARAM_TEXT, 'optional description',VALUE_OPTIONAL),
|
|
|
|
"type" => new \external_value(PARAM_TEXT, 'completion type name'),
|
|
|
|
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all","any"]'),
|
|
|
|
], 'completion type',$value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function editor_structure($value=VALUE_REQUIRED){
|
|
|
|
return new \external_single_structure([
|
|
|
|
"conditions" => new \external_multiple_structure(self::completion_type_editor_structure(),'completion conditions'),
|
|
|
|
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all","any"]'),
|
|
|
|
"enabled" => new \external_value(PARAM_BOOL,"whether completion is enabled here"),
|
|
|
|
], 'course completion info',$value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function completion_item_user_structure($value=VALUE_REQUIRED){
|
|
|
|
return new \external_single_structure([
|
|
|
|
"id" => new \external_value(PARAM_INT,'id of subitem',VALUE_OPTIONAL),
|
|
|
|
"title" => new \external_value(PARAM_TEXT,'name of subitem',VALUE_OPTIONAL),
|
|
|
|
"details" => new \external_single_structure([
|
|
|
|
"type" => new \external_value(PARAM_RAW, 'type',VALUE_OPTIONAL),
|
|
|
|
"criteria" => new \external_value(PARAM_RAW, 'criteria',VALUE_OPTIONAL),
|
|
|
|
"requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL),
|
|
|
|
"status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL),
|
|
|
|
]),
|
|
|
|
"link" => new \external_value(PARAM_TEXT, 'optional link to more details',VALUE_OPTIONAL),
|
|
|
|
// ADD BELOW IF NEEDED - try using name, description and link fields first
|
|
|
|
/*
|
|
|
|
"required_grade" => new \external_value(PARAM_TEXT, 'required_grade',VALUE_OPTIONAL),
|
|
|
|
"course_link" => course_info::simple_structure(VALUE_OPTIONAL),
|
|
|
|
*/
|
|
|
|
"completed" => new \external_value(PARAM_BOOL, 'simple completed or not'),
|
|
|
|
"status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete","progress","complete", "complete-pass","complete-fail"]'),
|
|
|
|
"pending" => new \external_value(PARAM_BOOL, 'optional pending state, for submitted but not yet reviewed activities',VALUE_OPTIONAL),
|
|
|
|
"grade" => new \external_value(PARAM_TEXT, 'optional grade result for this subitem',VALUE_OPTIONAL),
|
|
|
|
"feedback" => new \external_value(PARAM_RAW, 'optional feedback for this subitem ',VALUE_OPTIONAL),
|
|
|
|
], 'completion type',$value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function completion_type_user_structure($value=VALUE_REQUIRED){
|
|
|
|
return new \external_single_structure([
|
|
|
|
"items" => new \external_multiple_structure(self::completion_item_user_structure(),'subitems',VALUE_OPTIONAL),
|
|
|
|
"title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL),
|
|
|
|
"desc" => new \external_value(PARAM_TEXT, 'optional description',VALUE_OPTIONAL),
|
|
|
|
"type" => new \external_value(PARAM_TEXT, 'completion type name'),
|
|
|
|
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all","any"]'),
|
|
|
|
"completed" => new \external_value(PARAM_BOOL, 'current completion value for this type'),
|
|
|
|
"status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete","progress","complete", "complete-pass","complete-fail"]')
|
|
|
|
], 'completion type',$value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function user_structure($value=VALUE_REQUIRED){
|
|
|
|
return new \external_single_structure([
|
|
|
|
"progress" => new \external_value(PARAM_INT, 'completed sub-conditions'),
|
|
|
|
"enabled" => new \external_value(PARAM_BOOL,"whether completion is enabled here"),
|
|
|
|
"tracked" => new \external_value(PARAM_BOOL,"whether completion is tracked for the user",VALUE_OPTIONAL),
|
|
|
|
"count" => new \external_value(PARAM_INT, 'total number of sub-conditions'),
|
|
|
|
"conditions" => new \external_multiple_structure(self::completion_type_user_structure(),'completion conditions'),
|
|
|
|
"completed" => new \external_value(PARAM_BOOL, 'current completion value'),
|
|
|
|
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all","any"]'),
|
|
|
|
"pending" => new \external_value(PARAM_BOOL,"true if the user has any assignments pending grading",VALUE_OPTIONAL),
|
|
|
|
], 'course completion info',$value);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function aggregation_handle($method){
|
|
|
|
return ($method==COMPLETION_AGGREGATION_ALL)?"all":"any";
|
|
|
|
}
|
|
|
|
|
|
|
|
public function editor_model() {
|
|
|
|
global $DB, $COMPLETION_CRITERIA_TYPES;
|
|
|
|
|
|
|
|
$conditions = [];
|
|
|
|
$aggregation = "all"; // default
|
|
|
|
$info = [
|
|
|
|
"conditions" => $conditions,
|
|
|
|
"aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
|
|
|
|
"enabled" => $this->completion->is_enabled()
|
|
|
|
];
|
|
|
|
|
|
|
|
// Check if completion tracking is enabled for this course - otherwise, revert to defaults
|
|
|
|
if($this->completion->is_enabled())
|
|
|
|
{
|
|
|
|
$aggregation = $this->completion->get_aggregation_method();
|
|
|
|
// Loop through all condition types to see if they are applicable
|
|
|
|
foreach(self::completiontypes() as $type){
|
|
|
|
$criterias = $this->completion->get_criteria($type); // Returns array of relevant criteria items
|
|
|
|
if(count($criterias) > 0 ) // Only take it into account if the criteria count is > 0
|
|
|
|
{
|
|
|
|
$cinfo = [
|
|
|
|
"type" => $COMPLETION_CRITERIA_TYPES[$type],
|
|
|
|
"aggregation" => self::aggregation_handle($this->completion->get_aggregation_method($type)),
|
|
|
|
"title" => reset($criterias)->get_type_title(),
|
|
|
|
"items" => [],
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach($criterias as $criteria){
|
|
|
|
$iinfo = [
|
|
|
|
"title" => $criteria->get_title_detailed(),
|
|
|
|
];
|
|
|
|
|
|
|
|
//TODO: MAKE SURE THIS DATA IS FILLED
|
|
|
|
|
|
|
|
if($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){
|
|
|
|
// If it's an activity completion, add the relevant activity
|
|
|
|
//$cm = $this->modinfo->get_cm($criterias->moduleinstance);
|
|
|
|
// retrieve data for this object
|
|
|
|
//$data = $completion->get_data($cm, false, $userid);
|
|
|
|
}
|
|
|
|
else if ($type == COMPLETION_CRITERIA_TYPE_COURSE){
|
|
|
|
// If it's a (sub) course dependency, add the course as a link
|
|
|
|
|
|
|
|
}
|
|
|
|
else if ($type == COMPLETION_CRITERIA_TYPE_ROLE){
|
|
|
|
// If it needs approval by a role, it probably already is in the title
|
|
|
|
|
|
|
|
}
|
|
|
|
// only add the items list if we actually have items...
|
|
|
|
$cinfo["items"][] = $iinfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
$info['conditions'][] = $cinfo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function aggregate_completions($typeaggregation,$completions){
|
|
|
|
$completed = 0;
|
|
|
|
$count = count($completions);
|
|
|
|
foreach($completions as $c){
|
|
|
|
if($c->is_complete()){
|
|
|
|
$completed++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if($typeaggregation == COMPLETION_AGGREGATION_ALL){
|
|
|
|
return $completed >= $count;
|
|
|
|
}
|
|
|
|
else { // COMPLETION_AGGREGATION_ANY
|
|
|
|
return $completed > 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function user_model($userid) {
|
|
|
|
global $DB, $COMPLETION_CRITERIA_TYPES;
|
|
|
|
|
|
|
|
$progress = $this->get_advanced_progress_percentage($userid);
|
|
|
|
$info = [
|
|
|
|
'progress' => $progress->completed,
|
|
|
|
"count" => $progress->count,
|
|
|
|
"conditions" => [],
|
|
|
|
"completed" => $this->completion->is_course_complete($userid),
|
|
|
|
"aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
|
|
|
|
"enabled" => $this->completion->is_enabled(),
|
|
|
|
"tracked" => $this->completion->is_tracked_user($userid),
|
|
|
|
];
|
|
|
|
|
|
|
|
// Check if completion tracking is enabled for this course - otherwise, revert to defaults
|
|
|
|
if($this->completion->is_enabled() && $this->completion->is_tracked_user($userid))
|
|
|
|
{
|
|
|
|
$anypending = false;
|
|
|
|
// Loop through all conditions to see if they are applicable
|
|
|
|
foreach(self::completiontypes() as $type){
|
|
|
|
// Get the main completion for this type
|
|
|
|
$completions = $this->completion->get_completions($userid,$type);
|
|
|
|
if(count($completions) > 0){
|
|
|
|
$typeaggregation = $this->completion->get_aggregation_method($type);
|
|
|
|
$completed = $this->aggregate_completions($typeaggregation,$completions);
|
|
|
|
$cinfo = [
|
|
|
|
"type" => $COMPLETION_CRITERIA_TYPES[$type],
|
|
|
|
"aggregation" => self::aggregation_handle($typeaggregation),
|
|
|
|
"completed" => $completed,
|
2023-05-19 22:12:18 +02:00
|
|
|
"status" => $completed?"complete":"incomplete",
|
2023-05-17 21:19:14 +02:00
|
|
|
"title" => reset($completions)->get_criteria()->get_type_title(),
|
|
|
|
"items" => [],
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach($completions as $completion){
|
|
|
|
$criteria = $completion->get_criteria();
|
|
|
|
|
|
|
|
$iinfo = [
|
|
|
|
"id" => $criteria->id,
|
|
|
|
"title" => $criteria->get_title_detailed(),
|
|
|
|
"details" => $criteria->get_details($completion),
|
|
|
|
"completed" => $completion->is_complete(), // Make sure to override for activi
|
|
|
|
"status" => self::completion_handle($completion->is_complete()?COMPLETION_COMPLETE:COMPLETION_INCOMPLETE),
|
|
|
|
];
|
|
|
|
|
|
|
|
if($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){
|
|
|
|
$cm = $this->modinfo->get_cm($criteria->moduleinstance);
|
|
|
|
// If it's an activity completion, add all the relevant activities as sub-items
|
|
|
|
$completion_status = $this->completion->get_grade_completion($cm,$userid);
|
|
|
|
$iinfo['status'] = self::completion_handle($completion_status);
|
|
|
|
// Re-evaluate the completed value, to make sure COMPLETE_FAIL doesn't creep in as completed
|
|
|
|
$iinfo['completed'] = in_array($completion_status,[COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]);
|
|
|
|
// Determine the grade (retrieve from grade item, not from completion)
|
|
|
|
$grade = $this->get_grade($cm,$userid);
|
|
|
|
$iinfo['grade'] = $grade->grade;
|
|
|
|
$iinfo['feedback'] = $grade->feedback;
|
|
|
|
$iinfo['pending'] = $grade->pending;
|
|
|
|
|
|
|
|
$anypending = $anypending || $grade->pending;
|
|
|
|
// Overwrite the status with progress if something has been graded, or is pending
|
|
|
|
if($completion_status != COMPLETION_INCOMPLETE || $anypending){
|
|
|
|
if($cinfo["status"] == "incomplete"){
|
|
|
|
$cinfo["status"] = "progress";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if ($type == COMPLETION_CRITERIA_TYPE_GRADE){
|
|
|
|
// Make sure we provide the current course grade
|
|
|
|
$iinfo['grade'] = $this->get_course_grade($userid);
|
|
|
|
if($iinfo["grade"] > 0){
|
|
|
|
$iinfo["status"] = $completion->is_complete()?"complete-pass":"complete-fail";
|
|
|
|
if ($cinfo["status"] == "incomplete"){
|
|
|
|
$cinfo["status"] = "progress";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// finally add the item to the items list
|
|
|
|
$cinfo["items"][] = $iinfo;
|
|
|
|
}
|
|
|
|
$info['conditions'][] = $cinfo;
|
|
|
|
$info['pending'] = $anypending;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the grade for a certain course module
|
|
|
|
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute
|
|
|
|
*/
|
|
|
|
private function get_grade($cm,$userid){
|
|
|
|
// TODO: Display grade in the way described in the course setup (with letters if needed)
|
|
|
|
|
|
|
|
$gi= grade_item::fetch(['itemtype' => 'mod',
|
|
|
|
'itemmodule' => $cm->modname,
|
|
|
|
'iteminstance' => $cm->instance,
|
|
|
|
'courseid' => $this->course->id]); // Make sure we only get results relevant to this course
|
|
|
|
|
|
|
|
if($gi)
|
|
|
|
{
|
|
|
|
// Only the following types of grade yield a result
|
|
|
|
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
|
|
|
|
{
|
|
|
|
$scale = $gi->load_scale();
|
|
|
|
|
|
|
|
$grade = $gi->get_final($userid); // Get the grade for the specified user
|
|
|
|
$result = new \stdClass;
|
|
|
|
// Check if the final grade is available and numeric (safety check)
|
|
|
|
if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){
|
|
|
|
// convert scale grades to corresponding scale name
|
|
|
|
if(isset($scale)){
|
|
|
|
// get scale value
|
|
|
|
$result->grade = $scale->get_nearest_item($grade->finalgrade);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// round final grade to 1 decimal point
|
|
|
|
$result->grade = round($grade->finalgrade,1);
|
|
|
|
}
|
|
|
|
|
|
|
|
$result->feedback = trim($grade->feedback);
|
|
|
|
$result->pending = (new gradingscanner($gi))->pending($userid);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$result->grade = "-"; // Activity is gradable, but user did not receive a grade yet
|
|
|
|
$result->feedback = null;
|
|
|
|
$result->pending = false;
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null; // Activity cannot be graded (Shouldn't be happening, but still....)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the grade for a certain course module
|
|
|
|
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute
|
|
|
|
*/
|
|
|
|
private function get_course_grade($userid){
|
|
|
|
// TODO: Display grade in the way described in the course setup (with letters if needed)
|
|
|
|
$gi= grade_item::fetch(['itemtype' => 'course',
|
|
|
|
'iteminstance' => $this->course->id,
|
|
|
|
'courseid' => $this->course->id]);
|
|
|
|
|
|
|
|
if($gi)
|
|
|
|
{
|
|
|
|
// Only the following types of grade yield a result
|
|
|
|
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
|
|
|
|
{
|
|
|
|
$scale = $gi->load_scale();
|
|
|
|
$grade = $gi->get_final($userid); // Get the grade for the specified user
|
|
|
|
// Check if the final grade is available and numeric (safety check)
|
|
|
|
if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){
|
|
|
|
// convert scale grades to corresponding scale name
|
|
|
|
if(isset($scale)){
|
|
|
|
// get scale value
|
|
|
|
return $scale->get_nearest_item($grade->finalgrade);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// round final grade to 1 decimal point
|
|
|
|
return round($grade->finalgrade,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return "-"; // User did not receive a grade yet for this course
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null; // Course cannot be graded (Shouldn't be happening, but still....)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the percentage completed by a certain user, returns null if no completion data is available.
|
|
|
|
*
|
|
|
|
* @param int $userid The id of the user, 0 for the current user
|
|
|
|
* @return null|float The percentage, or null if completion is not supported in the course,
|
|
|
|
* or there are no activities that support completion.
|
|
|
|
*/
|
|
|
|
function get_progress_percentage($userid){
|
|
|
|
|
|
|
|
// First, let's make sure completion is enabled.
|
|
|
|
if (!$this->completion->is_enabled()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$this->completion->is_tracked_user($userid)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$completions = $this->completion->get_completions($userid);
|
|
|
|
$count = count($completions);
|
|
|
|
$completed = 0;
|
|
|
|
|
|
|
|
// Before we check how many modules have been completed see if the course has completed.
|
|
|
|
if ($this->completion->is_course_complete($userid)) {
|
|
|
|
$completed = $count;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// count all completions, but treat
|
|
|
|
foreach($completions as $completion){
|
|
|
|
$crit = $completion->get_criteria();
|
|
|
|
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
|
|
|
|
// get the cm data object
|
|
|
|
$cm = $this->modinfo->get_cm($crit->moduleinstance);
|
|
|
|
// retrieve data for this object
|
|
|
|
$data = $this->completion->get_data($cm, false, $userid);
|
|
|
|
// Count complete, but failed as incomplete too...
|
|
|
|
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
|
|
|
|
$completed += 0;
|
|
|
|
} else {
|
|
|
|
$completed += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if($completion->is_complete()){
|
|
|
|
$completed += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$result = new \stdClass;
|
|
|
|
$result->count = $count;
|
|
|
|
$result->completed = $completed;
|
|
|
|
$result->percentage = ($completed / $count) * 100;
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the percentage completed by a certain user, returns null if no completion data is available.
|
|
|
|
*
|
|
|
|
* @param int $userid The id of the user, 0 for the current user
|
|
|
|
* @return null|float The percentage, or null if completion is not supported in the course,
|
|
|
|
* or there are no activities that support completion.
|
|
|
|
*/
|
|
|
|
function get_advanced_progress_percentage($userid){
|
|
|
|
|
|
|
|
// First, let's make sure completion is enabled.
|
|
|
|
if (!$this->completion->is_enabled()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$this->completion->is_tracked_user($userid)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$completions = $this->completion->get_completions($userid);
|
|
|
|
|
|
|
|
$aggregation = $this->completion->get_aggregation_method();
|
|
|
|
$critcount = [];
|
|
|
|
|
|
|
|
// Before we check how many modules have been completed see if the course has completed.
|
|
|
|
if ($this->completion->is_course_complete($userid)) {
|
|
|
|
$completed = $count;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// count all completions, but treat
|
|
|
|
foreach($completions as $completion){
|
|
|
|
$crit = $completion->get_criteria();
|
|
|
|
|
|
|
|
// Make a new object for the type if it's not already there
|
|
|
|
$type = $crit->criteriatype;
|
|
|
|
if(!array_key_exists($type,$critcount)){
|
|
|
|
$critcount[$type] = new \stdClass;
|
|
|
|
$critcount[$type]->count = 0;
|
|
|
|
$critcount[$type]->completed = 0;
|
|
|
|
$critcount[$type]->aggregation = $this->completion->get_aggregation_method($type);
|
|
|
|
}
|
|
|
|
// Get a reference to the counter object for this type
|
|
|
|
$typecount =& $critcount[$type];
|
|
|
|
|
|
|
|
$typecount->count += 1;
|
|
|
|
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
|
|
|
|
// get the cm data object
|
|
|
|
$cm = $this->modinfo->get_cm($crit->moduleinstance);
|
|
|
|
// retrieve data for this object
|
|
|
|
$data = $this->completion->get_data($cm, false, $userid);
|
|
|
|
// Count complete, but failed as incomplete too...
|
|
|
|
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
|
|
|
|
$typecount->completed += 0;
|
|
|
|
} else {
|
|
|
|
$typecount->completed += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if($completion->is_complete()){
|
|
|
|
$typecount->completed += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we have all completions sorted by type, we can be smart about how to do the count
|
|
|
|
$count = 0;
|
|
|
|
$completed = 0;
|
|
|
|
$completion_percentage = 0;
|
|
|
|
foreach($critcount as $c){
|
|
|
|
// Take only types that are actually present into account
|
|
|
|
if($c->count > 0){
|
|
|
|
// If the aggregation for the type is ANY, reduce the count to 1 for this type
|
|
|
|
// And adjust the progress accordingly (check if any have been completed or not)
|
|
|
|
if($c->aggregation == COMPLETION_AGGREGATION_ALL){
|
|
|
|
$ct = $c->count;
|
|
|
|
$cmpl = $c->completed;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$ct = 1;
|
|
|
|
$cmpl = ($c->completed > 0)?1:0;
|
|
|
|
}
|
|
|
|
// if ANY completion for the types, count only the criteria type with the highest completion percentage -
|
|
|
|
// Overwrite data if current type is more complete
|
|
|
|
if($aggregation == COMPLETION_AGGREGATION_ANY) {
|
|
|
|
$pct = $cmpl/$ct;
|
|
|
|
if($pct > $completion_percentage){
|
|
|
|
$count = $ct;
|
|
|
|
$completed = $cmpl;
|
|
|
|
$completion_percentage = $pct;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if ALL completion for the types, add the count for this type to that of the others
|
|
|
|
else {
|
|
|
|
$count += $ct;
|
|
|
|
$completed += $cmpl;
|
|
|
|
// Don't really care about recalculating completion percentage every round in this case
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = new \stdClass;
|
|
|
|
$result->count = $count;
|
|
|
|
$result->completed = $completed;
|
|
|
|
$result->percentage = ($count > 0)?(($completed / $count) * 100):0;
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|