671 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			671 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
// This file is part of the Studyplan plugin for Moodle
 | 
						|
//
 | 
						|
// Moodle is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// Moodle is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU General Public License
 | 
						|
// along with Moodle.  If not, see <https://www.gnu.org/licenses/>.
 | 
						|
/**
 | 
						|
 *
 | 
						|
 * @package    local_treestudyplan
 | 
						|
 * @copyright  2023 P.M. Kuipers
 | 
						|
 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
						|
 */
 | 
						|
 | 
						|
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([
 | 
						|
            "id" => new \external_value(PARAM_INT, 'criteria id', VALUE_OPTIONAL),
 | 
						|
            "title" => new \external_value(PARAM_TEXT, 'name of subitem', VALUE_OPTIONAL),
 | 
						|
            "link" => new \external_value(PARAM_TEXT, 'optional link to more details', 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),
 | 
						|
            ]),
 | 
						|
            "progress" => completionscanner::structure(),
 | 
						|
         ], '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 completion', 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),
 | 
						|
            "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"]'),
 | 
						|
            "progress" => new \external_value(PARAM_INT, 'completed sub-conditions'),
 | 
						|
            "count" => new \external_value(PARAM_INT, 'total number of sub-conditions'),
 | 
						|
         ], '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, $CFG, $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) {
 | 
						|
                        // Unfortunately, we cannot easily get the criteria details with get_details() without having a .
 | 
						|
                        // user completion object involved, so'we'll have to retrieve the details per completion type.
 | 
						|
                        // See moodle/completion/criteria/completion_criteria_*.php::get_details() for the code that is.
 | 
						|
                        // in the code below is based on.
 | 
						|
 | 
						|
                        if ($type == COMPLETION_CRITERIA_TYPE_SELF) {
 | 
						|
                            $details = [
 | 
						|
                                "type" => $criteria->get_title(),
 | 
						|
                                "criteria" => $criteria->get_title(),
 | 
						|
                                "requirement" => get_string('markingyourselfcomplete', 'completion'),
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        } else if ($type == COMPLETION_CRITERIA_TYPE_DATE) {
 | 
						|
                            $details = [
 | 
						|
                                "type" => get_string('datepassed', 'completion'),
 | 
						|
                                "criteria" => get_string('remainingenroleduntildate', 'completion'),
 | 
						|
                                "requirement" => date("Y-m-d", $criteria->timeend),
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        } else if ($type == COMPLETION_CRITERIA_TYPE_UNENROL) {
 | 
						|
                            $details = [
 | 
						|
                                "type" => get_string('unenrolment', 'completion'),
 | 
						|
                                "criteria" => get_string('unenrolment', 'completion'),
 | 
						|
                                "requirement" => get_string('unenrolingfromcourse', 'completion'),
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        } else if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
 | 
						|
                            $cm = $this->modinfo->get_cm($criteria->moduleinstance);
 | 
						|
                            $details = [
 | 
						|
                                "type" => $criteria->get_title(),
 | 
						|
                                "criteria" => "", // Will be built in a moment by code copied from completion_criteria_activity.php.
 | 
						|
                                "requirement" => "", // Will be built momentarily by code copied from completion_criteria_activity.php.
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                            if ($cm->has_view()) {
 | 
						|
                                $details['criteria'] = \html_writer::link($cm->url, $cm->get_formatted_name());
 | 
						|
                            } else {
 | 
						|
                                $details['criteria'] = $cm->get_formatted_name();
 | 
						|
                            }
 | 
						|
                            // Build requirements.
 | 
						|
                            $details['requirement'] = array();
 | 
						|
 | 
						|
                            if ($cm->completion == COMPLETION_TRACKING_MANUAL) {
 | 
						|
                                $details['requirement'][] = get_string('markingyourselfcomplete', 'completion');
 | 
						|
                            } else if ($cm->completion == COMPLETION_TRACKING_AUTOMATIC) {
 | 
						|
                                if ($cm->completionview) {
 | 
						|
                                    $modulename = \core_text::strtolower(get_string('modulename', $criteria->module));
 | 
						|
                                    $details['requirement'][] = get_string('viewingactivity', 'completion', $modulename);
 | 
						|
                                }
 | 
						|
 | 
						|
                                if (!is_null($cm->completiongradeitemnumber)) {
 | 
						|
                                    $details['requirement'][] = get_string('achievinggrade', 'completion');
 | 
						|
                                }
 | 
						|
 | 
						|
                                if ($cm->completionpassgrade) {
 | 
						|
                                    $details['requirement'][] = get_string('achievingpassinggrade', 'completion');
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
 | 
						|
                            $details['requirement'] = implode(', ', $details['requirement']);
 | 
						|
 | 
						|
                        } else if ($type == COMPLETION_CRITERIA_TYPE_DURATION) {
 | 
						|
                            $details = [
 | 
						|
                                "type" => get_string('periodpostenrolment', 'completion'),
 | 
						|
                                "criteria" => get_string('remainingenroledfortime', 'completion'),
 | 
						|
                                "requirement" => get_string('xdays', 'completion', ceil($criteria->enrolperiod / (60*60*24))),
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        } else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) {
 | 
						|
                            $details = [
 | 
						|
                                "type" => get_string('coursegrade', 'completion'),
 | 
						|
                                "criteria" => get_string('graderequired', 'completion'),
 | 
						|
                                // TODO: convert to selected representation (letter, percentage, etc).
 | 
						|
                                "requirement" => get_string('graderequired', 'completion').": ".format_float($criteria->gradepass, 1),
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        } else if ($type == COMPLETION_CRITERIA_TYPE_ROLE) {
 | 
						|
                            $criteria = $criteria->get_title();
 | 
						|
 | 
						|
                            $details = [
 | 
						|
                                "type" => get_string('manualcompletionby', 'completion'),
 | 
						|
                                "criteria" => $criteria,
 | 
						|
                                "requirement" => get_string('markedcompleteby', 'completion', $criteria),
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        } else if ($type == COMPLETION_CRITERIA_TYPE_COURSE) {
 | 
						|
                            $prereq = get_course($criteria->courseinstance);
 | 
						|
                            $coursecontext = \context_course::instance($prereq->id, MUST_EXIST);
 | 
						|
                            $fullname = format_string($prereq->fullname, true, array('context' => $coursecontext));
 | 
						|
                            $details = [
 | 
						|
                                "type" => $criteria->get_title(),
 | 
						|
                                "criteria" => '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$criteria->courseinstance.'">'.s($fullname).'</a>',
 | 
						|
                                "requirement" => get_string('coursecompleted', 'completion'),
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        } else {
 | 
						|
                            // Moodle added a criteria type.
 | 
						|
                            $details = [
 | 
						|
                                "type" => "",
 | 
						|
                                "criteria" => "",
 | 
						|
                                "requirement" => "",
 | 
						|
                                "status" => "",
 | 
						|
                            ];
 | 
						|
                        }
 | 
						|
 | 
						|
                        $scanner = new completionscanner($criteria, $this->course);
 | 
						|
 | 
						|
                        // only add the items list if we actually have items...
 | 
						|
                        $cinfo["items"][] = [
 | 
						|
                            "id" => $criteria->id,
 | 
						|
                            "title" => $criteria->get_title_detailed(),
 | 
						|
                            "details" => $details,
 | 
						|
                            "progress" => $scanner->model(),
 | 
						|
                        ];
 | 
						|
 | 
						|
                    }
 | 
						|
 | 
						|
                    $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,
 | 
						|
                        "status" => $completed?"complete":"incomplete",
 | 
						|
                        "title" => reset($completions)->get_criteria()->get_type_title(),
 | 
						|
                        "items" => [],
 | 
						|
                    ];
 | 
						|
 | 
						|
                    $progress = 0;
 | 
						|
                    foreach ($completions as $completion) {
 | 
						|
                        $criteria = $completion->get_criteria();
 | 
						|
 | 
						|
                        if ($completion->is_complete()) {
 | 
						|
                            $progress += 1; // Add a point to the progress counter.
 | 
						|
                        }
 | 
						|
 | 
						|
                        $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'] = floatval($iinfo['details']['status']);
 | 
						|
                            if ($iinfo["grade"] > 0) {
 | 
						|
                                $iinfo["grade"] = format_float($iinfo["grade"], 1). "/".format_float(floatval($iinfo['details']['requirement']));
 | 
						|
                                $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;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Set the count and progress stats based on the Type's aggregation style.
 | 
						|
                    if ($typeaggregation == COMPLETION_AGGREGATION_ALL) {
 | 
						|
                        // Count and Progress amount to the sum of items.
 | 
						|
                        $cinfo["count"] = count($cinfo["items"]);
 | 
						|
                        $cinfo["progress"] = $progress;
 | 
						|
                    } else { //$typeaggregation == COMPLETION_AGGREGATION_ANY.
 | 
						|
                        // Count and progress are either 1 or 0, since any of the items.
 | 
						|
                        // complete's the type.
 | 
						|
                        $cinfo["count"] = (count($cinfo["items"]) > 0)?1:0;
 | 
						|
                        $cinfo["progress"] = ($progress>0)?1:0;
 | 
						|
                    }
 | 
						|
 | 
						|
                    $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 = (object)$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|\stdClass 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):\stdClass {
 | 
						|
 | 
						|
        // 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. .
 | 
						|
        // 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;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
}
 |