395 lines
15 KiB
PHP
395 lines
15 KiB
PHP
<?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;
|
|
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". print_r($gi,true));
|
|
}
|
|
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". print_r($gi,true));
|
|
}
|
|
$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){
|
|
$course_id = $item->courseid();
|
|
$gradeitems= grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $course_id]);
|
|
foreach($gradeitems as $gi){
|
|
$gi_name = empty($outcome)?$gi->itemname:$outcome->name;
|
|
$gi_type = $gi->itemmodule;
|
|
|
|
if($gi_name == $model["name"] && $gi_type == $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 $grade_id,int $item_id,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' => $item_id, 'grade_item_id' => $grade_id]);
|
|
if($r){
|
|
$r->include = 1;
|
|
$r->required = boolval($required)?1:0;
|
|
$id = $DB->update_record($table, $r);
|
|
} else {
|
|
$DB->insert_record($table, [
|
|
'studyitem_id' => $item_id,
|
|
'grade_item_id' => $grade_id,
|
|
'include' => 1,
|
|
'required' =>boolval($required)?1:0]
|
|
);
|
|
}
|
|
} else {
|
|
// remove if it should not be included
|
|
$r = $DB->get_record($table,['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]);
|
|
if($r){
|
|
$DB->delete_records($table, ['id' => $r->id]);
|
|
}
|
|
}
|
|
|
|
return success::success();
|
|
}
|
|
|
|
|
|
|
|
}
|