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(); } }