408 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			408 lines
		
	
	
	
		
			16 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;
 | 
						|
defined('MOODLE_INTERNAL') || die();
 | 
						|
 | 
						|
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;
 | 
						|
use \core_plugin_manager;
 | 
						|
 | 
						|
class gradeinfo {
 | 
						|
    private $studyitem = null;
 | 
						|
    private $id;
 | 
						|
    private $gradeitem;
 | 
						|
 | 
						|
    private $icon;
 | 
						|
    private $link;
 | 
						|
    private $gradinglink;
 | 
						|
    private $scale;
 | 
						|
    private $outcome;
 | 
						|
    private $hidden = false;
 | 
						|
    private $name;
 | 
						|
    private $typename;
 | 
						|
    private $section;
 | 
						|
    private $sectionorder;
 | 
						|
    private $cmid;
 | 
						|
    private $coursesort;
 | 
						|
 | 
						|
    private static $contentitems = null;
 | 
						|
    private $gradingscanner;
 | 
						|
 | 
						|
    private static $sections = [];
 | 
						|
 | 
						|
    protected static function getSectionSequence($sectionid) {
 | 
						|
        global $DB;
 | 
						|
        if (!array_key_exists($sectionid, self::$sections)) {
 | 
						|
            self::$sections[$sectionid] = explode(", ", $DB->get_field("course_sections", "sequence", ["id" => $sectionid]));
 | 
						|
        }
 | 
						|
        return self::$sections[$sectionid];
 | 
						|
    }
 | 
						|
 | 
						|
    public function getGradeitem() {
 | 
						|
        return $this->gradeitem;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getGradingscanner() {
 | 
						|
        return $this->gradingscanner;
 | 
						|
    }
 | 
						|
 | 
						|
    public function getScale() {
 | 
						|
        return $this->scale;
 | 
						|
    }
 | 
						|
 | 
						|
    protected static function get_contentitems() {
 | 
						|
        global $PAGE;
 | 
						|
        if (empty(static::$contentitems)) {
 | 
						|
            $PAGE->set_context(\context_system::instance());
 | 
						|
            static::$contentitems = (new content_item_readonly_repository())->find_all();
 | 
						|
        }
 | 
						|
        return static::$contentitems;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function get_contentitem($name) {
 | 
						|
        $contentitems = static::get_contentitems();
 | 
						|
        for ($i = 0; $i < count($contentitems); $i++) {
 | 
						|
            if ($contentitems[$i]->get_name() == $name) {
 | 
						|
                return $contentitems[$i];
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function getCourseContextById($id) {
 | 
						|
        $gi = grade_item::fetch(["id" => $id]);
 | 
						|
        if (!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) {
 | 
						|
            throw new \InvalidArgumentException ("Grade {$id} not found in database");
 | 
						|
        }
 | 
						|
        return \context_course::instance($gi->courseid);;
 | 
						|
    }
 | 
						|
 | 
						|
    public function __construct($id, studyitem $studyitem = null) {
 | 
						|
        global $DB;
 | 
						|
        $this->studyitem = $studyitem;
 | 
						|
 | 
						|
        $gi = grade_item::fetch(["id" => $id]);
 | 
						|
        if (!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) {
 | 
						|
            throw new \InvalidArgumentException ("Grade {$id} not found in database");
 | 
						|
        }
 | 
						|
        $this->id = $id;
 | 
						|
        $this->gradeitem = $gi;
 | 
						|
 | 
						|
        // Determine the icon for the associated activity.
 | 
						|
        $contentitem = static::get_contentitem($gi->itemmodule);
 | 
						|
        $this->icon = empty($contentitem) ? "" : $contentitem->get_icon();
 | 
						|
 | 
						|
        // Determine a link to the associated activity.
 | 
						|
        if ($gi->itemtype != "mod" || empty($gi->itemmodule) || empty($gi->iteminstance)) {
 | 
						|
            $this->link = "";
 | 
						|
            $this->cmid = 0;
 | 
						|
            $this->section = 0;
 | 
						|
            $this->sectionorder = 0;
 | 
						|
        } else {
 | 
						|
            list($c, $cminfo) = get_course_and_cm_from_instance($gi->iteminstance, $gi->itemmodule);
 | 
						|
            $this->cmid = $cminfo->id;
 | 
						|
            // Sort by position in course.
 | 
						|
            // .
 | 
						|
            $this->section = $cminfo->sectionnum;
 | 
						|
            $ssequence = self::getSectionSequence($cminfo->section);
 | 
						|
            $this->sectionorder = array_search($cminfo->id, $ssequence);
 | 
						|
 | 
						|
            $this->link = "/mod/{$gi->itemmodule}/view.php?id={$cminfo->id}";
 | 
						|
            if ($gi->itemmodule == 'quiz') {
 | 
						|
                $this->gradinglink = "/mod/{$gi->itemmodule}/report.php?id={$cminfo->id}&mode=grading";
 | 
						|
            } else if ($gi->itemmodule == "assign") {
 | 
						|
                $this->gradinglink = $this->link ."&action=grading";
 | 
						|
            } else {
 | 
						|
                $this->gradinglink = $this->link;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $this->scale = $gi->load_scale();
 | 
						|
        $this->outcome = $gi->load_outcome();
 | 
						|
 | 
						|
        $this->hidden = ($gi->hidden || (!empty($outcome) && $outcome->hidden)) ? true : false;
 | 
						|
 | 
						|
        $this->name = empty($outcome) ? $gi->itemname : $outcome->name;
 | 
						|
        $this->typename = empty($contentitem) ? $gi->itemmodule : $contentitem->get_title()->get_value();
 | 
						|
        $this->gradingscanner = new gradingscanner($gi);
 | 
						|
 | 
						|
        $this->coursesort = $this->section * 1000 + $this->sectionorder;
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    public function is_selected() {
 | 
						|
        global $DB;
 | 
						|
        if ($this->studyitem) {
 | 
						|
            // Check if selected for this studyitem.
 | 
						|
            $r = $DB->get_record('local_treestudyplan_gradeinc',
 | 
						|
                                 ['studyitem_id' => $this->studyitem->id(), 'grade_item_id' => $this->gradeitem->id]);
 | 
						|
            if ($r && $r->include) {
 | 
						|
                return(true);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return(false);
 | 
						|
    }
 | 
						|
 | 
						|
    public function is_required() {
 | 
						|
        global $DB;
 | 
						|
        if ($this->studyitem) {
 | 
						|
            // Check if selected for this studyitem.
 | 
						|
            $r = $DB->get_record('local_treestudyplan_gradeinc',
 | 
						|
                                 ['studyitem_id' => $this->studyitem->id(), 'grade_item_id' => $this->gradeitem->id]);
 | 
						|
            if ($r && $r->include && $r->required) {
 | 
						|
                return(true);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return(false);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function editor_structure($value=VALUE_REQUIRED) {
 | 
						|
        return new \external_single_structure([
 | 
						|
            "id" => new \external_value(PARAM_INT, 'grade_item id'),
 | 
						|
            "cmid" => new \external_value(PARAM_INT, 'course module id'),
 | 
						|
            "name" => new \external_value(PARAM_TEXT, 'grade item name'),
 | 
						|
            "typename" => new \external_value(PARAM_TEXT, 'grade item type name'),
 | 
						|
            "outcome" => new \external_value(PARAM_BOOL, 'is outcome'),
 | 
						|
            "selected" => new \external_value(PARAM_BOOL, 'is selected for current studyitem'),
 | 
						|
            "icon" => new \external_value(PARAM_RAW, 'html for icon of related activity'),
 | 
						|
            "link" => new \external_value(PARAM_TEXT, 'link to related activity'),
 | 
						|
            "gradinglink" => new \external_value(PARAM_TEXT, 'link to related activity'),
 | 
						|
            "grading" => gradingscanner::structure(),
 | 
						|
            "required" => new \external_value(PARAM_BOOL, 'is required for current studyitem'),
 | 
						|
        ], 'referenced course information', $value);
 | 
						|
    }
 | 
						|
 | 
						|
    public function editor_model(studyitem $studyitem = null) {
 | 
						|
        $model = [
 | 
						|
            "id" => $this->id,
 | 
						|
            "cmid" => $this->cmid,
 | 
						|
            "name" => $this->name,
 | 
						|
            "typename" => $this->typename,
 | 
						|
            "outcome" => isset($this->outcome),
 | 
						|
            "selected" => $this->is_selected(),
 | 
						|
            "icon" => $this->icon,
 | 
						|
            "link" => $this->link,
 | 
						|
            "gradinglink" => $this->gradinglink,
 | 
						|
            "required" => $this->is_required(),
 | 
						|
        ];
 | 
						|
        // Unfortunately, lazy loading of the completion data is off, since we need the data to show study item completion...
 | 
						|
        if ($studyitem !== null
 | 
						|
            && $this->is_selected()
 | 
						|
            && has_capability('local/treestudyplan:viewuserreports', $studyitem->studyline()->studyplan()->context())
 | 
						|
            && $this->gradingscanner->is_available()) {
 | 
						|
            $model['grading'] = $this->gradingscanner->model();
 | 
						|
        }
 | 
						|
        return $model;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function user_structure($value=VALUE_REQUIRED) {
 | 
						|
        return new \external_single_structure([
 | 
						|
            "id" => new \external_value(PARAM_INT, 'grade_item id'),
 | 
						|
            "cmid" => new \external_value(PARAM_INT, 'course module id'),
 | 
						|
            "name" => new \external_value(PARAM_TEXT, 'grade item name'),
 | 
						|
            "typename" => new \external_value(PARAM_TEXT, 'grade item type name'),
 | 
						|
            "grade" => new \external_value(PARAM_TEXT, 'is outcome'),
 | 
						|
            "gradetype" => new \external_value(PARAM_TEXT, 'grade type (completion|grade)'),
 | 
						|
            "feedback" => new \external_value(PARAM_RAW, 'html for feedback'),
 | 
						|
            "completion" => new \external_value(PARAM_TEXT, 'completion state (incomplete|progress|completed|excellent)'),
 | 
						|
            "icon" => new \external_value(PARAM_RAW, 'html for icon of related activity'),
 | 
						|
            "link" => new \external_value(PARAM_TEXT, 'link to related activity'),
 | 
						|
            "pendingsubmission" => new \external_value(PARAM_BOOL, 'is selected for current studyitem', VALUE_OPTIONAL),
 | 
						|
            "required" => new \external_value(PARAM_BOOL, 'is required for current studyitem'),
 | 
						|
            "selected" => new \external_value(PARAM_BOOL, 'is selected for current studyitem'),
 | 
						|
        ], 'referenced course information', $value);
 | 
						|
    }
 | 
						|
 | 
						|
    public function user_model($userid) {
 | 
						|
        global $DB;
 | 
						|
        $grade = $this->gradeitem->get_final($userid);
 | 
						|
        // Convert scale grades to corresponding scale name.
 | 
						|
        if (!empty($grade)) {
 | 
						|
            if (!is_numeric($grade->finalgrade) && empty($grade->finalgrade)) {
 | 
						|
                $finalgrade = "-";
 | 
						|
            } else if (isset($this->scale)) {
 | 
						|
                $finalgrade = $this->scale->get_nearest_item($grade->finalgrade);
 | 
						|
            } else {
 | 
						|
                $finalgrade = round($grade->finalgrade, 1);
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            $finalgrade = "-";
 | 
						|
        }
 | 
						|
 | 
						|
        // Retrieve the aggregator and determine completion.
 | 
						|
        if (!isset($this->studyitem)) {
 | 
						|
            throw new \UnexpectedValueException("Study item not set (null) for gradeinfo in report mode");
 | 
						|
        }
 | 
						|
        $aggregator = $this->studyitem->studyline()->studyplan()->aggregator();
 | 
						|
        $completion = $aggregator->grade_completion($this, $userid);
 | 
						|
 | 
						|
        $model = [
 | 
						|
            "id" => $this->id,
 | 
						|
            "cmid" => $this->cmid,
 | 
						|
            "name" => $this->name,
 | 
						|
            "typename" => $this->typename,
 | 
						|
            "grade" => $finalgrade,
 | 
						|
            "gradetype" => isset($this->scale) ? "completion" : "grade",
 | 
						|
            "feedback" => empty($grade) ? null : $grade->feedback,
 | 
						|
            "completion" => completion::label($completion),
 | 
						|
            "icon" => $this->icon,
 | 
						|
            "link" => $this->link,
 | 
						|
            "pendingsubmission" => $this->gradingscanner->pending($userid),
 | 
						|
            "required" => $this->is_required(),
 | 
						|
            "selected" => $this->is_selected(),
 | 
						|
        ];
 | 
						|
 | 
						|
        return $model;
 | 
						|
    }
 | 
						|
 | 
						|
    public function export_model() {
 | 
						|
        return [
 | 
						|
            "name" => $this->name,
 | 
						|
            "type" => $this->gradeitem->itemmodule,
 | 
						|
            "selected" => $this->is_selected(),
 | 
						|
            "required" => $this->is_required(),
 | 
						|
        ];
 | 
						|
    }
 | 
						|
    public static function import(studyitem $item, array $model) {
 | 
						|
        if ($item->type() == studyitem::COURSE) {
 | 
						|
            $courseid = $item->courseid();
 | 
						|
            $gradeitems= grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $courseid]);
 | 
						|
            foreach ($gradeitems as $gi) {
 | 
						|
                $giname = empty($outcome) ? $gi->itemname : $outcome->name;
 | 
						|
                $gitype = $gi->itemmodule;
 | 
						|
 | 
						|
                if ($giname == $model["name"] && $gitype == $model["type"]) {
 | 
						|
                    // We have a match.
 | 
						|
                    if (!isset($model["selected"])) {
 | 
						|
                        $model["selected"] = true;
 | 
						|
                    }
 | 
						|
                    if (!isset($model["required"])) {
 | 
						|
                        $model["required"] = false;
 | 
						|
                    }
 | 
						|
                    if ($model["selected"] || $model["required"]) {
 | 
						|
                        static::include_grade($gi->id, $item->id(), $model["selected"], $model["required"]);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public static function list_course_gradables($course, studyitem $studyitem=null) {
 | 
						|
        $list = [];
 | 
						|
 | 
						|
        if (method_exists("\course_modinfo", "get_array_of_activities")) {
 | 
						|
            $activities = \course_modinfo::get_array_of_activities($course);
 | 
						|
        } else {
 | 
						|
            // Deprecated in Moodle 4.0+, but not yet available in Moodle 3.11.
 | 
						|
            $activities = get_array_of_activities($course->id);
 | 
						|
        }
 | 
						|
        foreach ($activities as $act) {
 | 
						|
            if ($act->visible) {
 | 
						|
                $gradeitems= grade_item::fetch_all(['itemtype' => 'mod',
 | 
						|
                                                    'itemmodule' => $act->mod,
 | 
						|
                                                    'iteminstance' => $act->id,
 | 
						|
                                                    'courseid' => $course->id]);
 | 
						|
                if (!empty($gradeitems)) {
 | 
						|
                    foreach ($gradeitems as $gi) {
 | 
						|
                        if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
 | 
						|
                            try {
 | 
						|
                                $gradable = new static($gi->id, $studyitem);
 | 
						|
                                $list[] = $gradable;
 | 
						|
                            } catch (\InvalidArgumentException $x) {
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        usort($list, function($a, $b) {
 | 
						|
            $course = $a->coursesort <=> $b->coursesort;
 | 
						|
            return ($course != 0) ? $course : $a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
 | 
						|
        });
 | 
						|
        return $list;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function list_studyitem_gradables(studyitem $studyitem) {
 | 
						|
        global $DB;
 | 
						|
        $table = 'local_treestudyplan_gradeinc';
 | 
						|
        $list = [];
 | 
						|
        $records = $DB->get_records($table, ['studyitem_id' => $studyitem->id()]);
 | 
						|
        foreach ($records as $r) {
 | 
						|
            if (isset($r->grade_item_id)) {
 | 
						|
                try {
 | 
						|
                    if ($r->include || $r->required) {
 | 
						|
                        $list[] = new static($r->grade_item_id, $studyitem);
 | 
						|
                    }
 | 
						|
                } catch (\InvalidArgumentException $x) {
 | 
						|
                    // On InvalidArgumentException, the grade_item id can no longer be found.
 | 
						|
                    // Remove the link to avoid database record hogging.
 | 
						|
                    $DB->delete_records($table, ['id' => $r->id]);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        usort($list, function($a, $b) {
 | 
						|
            $course = $a->coursesort <=> $b->coursesort;
 | 
						|
            return ($course != 0) ? $course : $a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
 | 
						|
        });
 | 
						|
        return $list;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function include_grade(int $gradeid, int $itemid, bool $include, bool $required=false) {
 | 
						|
        global $DB;
 | 
						|
        $table = 'local_treestudyplan_gradeinc';
 | 
						|
        if ($include) {
 | 
						|
            // Make sure a record exits.
 | 
						|
            $r = $DB->get_record($table, ['studyitem_id' => $itemid, 'grade_item_id' => $gradeid]);
 | 
						|
            if ($r) {
 | 
						|
                $r->include = 1;
 | 
						|
                $r->required = boolval($required)?1:0;
 | 
						|
                $id = $DB->update_record($table, $r);
 | 
						|
            } else {
 | 
						|
                $DB->insert_record($table, [
 | 
						|
                    'studyitem_id' => $itemid,
 | 
						|
                    'grade_item_id' => $gradeid,
 | 
						|
                    'include' => 1,
 | 
						|
                    'required' => boolval($required)?1:0]
 | 
						|
                );
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            // Remove if it should not be included.
 | 
						|
            $r = $DB->get_record($table, ['studyitem_id' => $itemid, 'grade_item_id' => $gradeid]);
 | 
						|
            if ($r) {
 | 
						|
                $DB->delete_records($table, ['id' => $r->id]);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return success::success();
 | 
						|
    }
 | 
						|
 | 
						|
}
 |