moodle_local_treestudyplan/classes/gradeinfo.php

411 lines
16 KiB
PHP
Raw Normal View History

<?php
2023-08-24 23:02:41 +02:00
// 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;
2023-08-25 12:04:27 +02:00
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;
2023-08-24 23:02:41 +02:00
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 = [];
2023-08-25 17:33:20 +02:00
protected static function get_sectionsequence($sectionid) {
global $DB;
2023-08-24 23:02:41 +02:00
if (!array_key_exists($sectionid, self::$sections)) {
2023-08-25 10:41:56 +02:00
self::$sections[$sectionid] = explode(", ", $DB->get_field("course_sections", "sequence", ["id" => $sectionid]));
}
return self::$sections[$sectionid];
}
2023-08-25 17:33:20 +02:00
public function get_gradeitem() {
return $this->gradeitem;
}
2023-08-25 17:33:20 +02:00
public function get_gradingscanner() {
return $this->gradingscanner;
}
2023-08-25 17:33:20 +02:00
public function get_scale() {
return $this->scale;
}
protected static function get_contentitems() {
global $PAGE;
2023-08-24 23:02:41 +02:00
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();
2023-08-25 10:41:56 +02:00
for ($i = 0; $i < count($contentitems); $i++) {
2023-08-24 23:02:41 +02:00
if ($contentitems[$i]->get_name() == $name) {
return $contentitems[$i];
}
}
return null;
}
2023-08-25 17:33:20 +02:00
public static function get_coursecontext_by_id($id) {
$gi = grade_item::fetch(["id" => $id]);
2023-08-24 23:02:41 +02:00
if (!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) {
2023-08-24 23:09:20 +02:00
throw new \InvalidArgumentException ("Grade {$id} not found in database");
}
return \context_course::instance($gi->courseid);;
}
2023-08-24 23:02:41 +02:00
public function __construct($id, studyitem $studyitem = null) {
global $DB;
$this->studyitem = $studyitem;
$gi = grade_item::fetch(["id" => $id]);
2023-08-24 23:02:41 +02:00
if (!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) {
2023-08-24 23:09:20 +02:00
throw new \InvalidArgumentException ("Grade {$id} not found in database");
}
$this->id = $id;
$this->gradeitem = $gi;
2023-08-24 23:02:41 +02:00
// Determine the icon for the associated activity.
$contentitem = static::get_contentitem($gi->itemmodule);
2023-08-25 10:41:56 +02:00
$this->icon = empty($contentitem) ? "" : $contentitem->get_icon();
2023-08-24 23:02:41 +02:00
// 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;
2023-08-24 23:09:20 +02:00
} else {
2023-08-24 23:02:41 +02:00
list($c, $cminfo) = get_course_and_cm_from_instance($gi->iteminstance, $gi->itemmodule);
$this->cmid = $cminfo->id;
2023-08-25 09:44:34 +02:00
// Sort by position in course.
2023-08-24 23:02:41 +02:00
// .
$this->section = $cminfo->sectionnum;
2023-08-25 17:33:20 +02:00
$ssequence = self::get_sectionsequence($cminfo->section);
2023-08-24 23:02:41 +02:00
$this->sectionorder = array_search($cminfo->id, $ssequence);
$this->link = "/mod/{$gi->itemmodule}/view.php?id={$cminfo->id}";
2023-08-24 23:02:41 +02:00
if ($gi->itemmodule == 'quiz') {
$this->gradinglink = "/mod/{$gi->itemmodule}/report.php?id={$cminfo->id}&mode=grading";
2023-08-24 23:09:20 +02:00
} else if ($gi->itemmodule == "assign") {
$this->gradinglink = $this->link ."&action=grading";
2023-08-24 23:09:20 +02:00
} else {
$this->gradinglink = $this->link;
}
}
$this->scale = $gi->load_scale();
$this->outcome = $gi->load_outcome();
2023-08-25 10:41:56 +02:00
$this->hidden = ($gi->hidden || (!empty($outcome) && $outcome->hidden)) ? true : false;
2023-08-25 10:41:56 +02:00
$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;
}
2023-08-24 23:02:41 +02:00
public function is_selected() {
global $DB;
2023-08-24 23:02:41 +02:00
if ($this->studyitem) {
// Check if selected for this studyitem.
2023-08-25 11:52:05 +02:00
$r = $DB->get_record('local_treestudyplan_gradeinc',
['studyitem_id' => $this->studyitem->id(), 'grade_item_id' => $this->gradeitem->id]);
2023-08-24 23:02:41 +02:00
if ($r && $r->include) {
return(true);
}
}
return(false);
}
2023-08-24 23:02:41 +02:00
public function is_required() {
global $DB;
2023-08-24 23:02:41 +02:00
if ($this->studyitem) {
// Check if selected for this studyitem.
2023-08-25 11:52:05 +02:00
$r = $DB->get_record('local_treestudyplan_gradeinc',
['studyitem_id' => $this->studyitem->id(), 'grade_item_id' => $this->gradeitem->id]);
2023-08-24 23:02:41 +02:00
if ($r && $r->include && $r->required) {
return(true);
}
}
return(false);
}
2023-08-25 13:04:19 +02:00
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'),
2023-08-25 10:41:56 +02:00
"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(),
2023-08-25 10:41:56 +02:00
"required" => new \external_value(PARAM_BOOL, 'is required for current studyitem'),
2023-08-24 23:02:41 +02:00
], '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,
2023-08-25 10:41:56 +02:00
"required" => $this->is_required(),
];
// Unfortunately, lazy loading of the completion data is off, since we need the data to show study item completion...
2023-08-25 11:52:05 +02:00
if ($studyitem !== null
&& $this->is_selected()
&& has_capability('local/treestudyplan:viewuserreports', $studyitem->studyline()->studyplan()->context())
2023-08-24 23:02:41 +02:00
&& $this->gradingscanner->is_available()) {
$model['grading'] = $this->gradingscanner->model();
}
return $model;
}
2023-08-25 13:04:19 +02:00
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'),
2023-08-25 10:41:56 +02:00
"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'),
2023-08-25 10:41:56 +02:00
"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'),
2023-08-25 10:41:56 +02:00
"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'),
2023-08-24 23:02:41 +02:00
], 'referenced course information', $value);
}
public function user_model($userid) {
global $DB;
$grade = $this->gradeitem->get_final($userid);
2023-08-25 09:44:34 +02:00
// Convert scale grades to corresponding scale name.
2023-08-24 23:02:41 +02:00
if (!empty($grade)) {
if (!is_numeric($grade->finalgrade) && empty($grade->finalgrade)) {
$finalgrade = "-";
2023-08-24 23:09:20 +02:00
} else if (isset($this->scale)) {
$finalgrade = $this->scale->get_nearest_item($grade->finalgrade);
2023-08-25 09:33:42 +02:00
} else {
2023-08-24 23:02:41 +02:00
$finalgrade = round($grade->finalgrade, 1);
}
2023-08-25 09:33:42 +02:00
} else {
$finalgrade = "-";
}
2023-08-25 09:44:34 +02:00
// Retrieve the aggregator and determine completion.
2023-08-24 23:02:41 +02:00
if (!isset($this->studyitem)) {
throw new \UnexpectedValueException("Study item not set (null) for gradeinfo in report mode");
}
$aggregator = $this->studyitem->studyline()->studyplan()->aggregator();
2023-08-24 23:02:41 +02:00
$completion = $aggregator->grade_completion($this, $userid);
$model = [
"id" => $this->id,
"cmid" => $this->cmid,
"name" => $this->name,
"typename" => $this->typename,
"grade" => $finalgrade,
2023-08-25 10:41:56 +02:00
"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),
2023-08-25 10:41:56 +02:00
"required" => $this->is_required(),
"selected" => $this->is_selected(),
];
return $model;
}
2023-08-24 23:02:41 +02:00
public function export_model() {
return [
"name" => $this->name,
"type" => $this->gradeitem->itemmodule,
"selected" => $this->is_selected(),
2023-08-25 10:41:56 +02:00
"required" => $this->is_required(),
];
}
2023-08-24 23:02:41 +02:00
public static function import(studyitem $item, array $model) {
if ($item->type() == studyitem::COURSE) {
2023-08-25 09:33:42 +02:00
$courseid = $item->courseid();
2023-08-25 13:04:19 +02:00
$gradeitems = grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $courseid]);
2023-08-24 23:02:41 +02:00
foreach ($gradeitems as $gi) {
2023-08-25 10:41:56 +02:00
$giname = empty($outcome) ? $gi->itemname : $outcome->name;
2023-08-25 09:33:42 +02:00
$gitype = $gi->itemmodule;
2023-08-25 09:33:42 +02:00
if ($giname == $model["name"] && $gitype == $model["type"]) {
2023-08-25 09:44:34 +02:00
// We have a match.
2023-08-25 10:41:56 +02:00
if (!isset($model["selected"])) {
$model["selected"] = true;
}
if (!isset($model["required"])) {
$model["required"] = false;
}
2023-08-24 23:02:41 +02:00
if ($model["selected"] || $model["required"]) {
static::include_grade($gi->id, $item->id(), $model["selected"], $model["required"]);
}
}
}
2023-08-24 23:02:41 +02:00
}
}
2023-08-25 13:04:19 +02:00
public static function list_course_gradables($course, studyitem $studyitem = null) {
$list = [];
2023-08-24 23:02:41 +02:00
if (method_exists("\course_modinfo", "get_array_of_activities")) {
$activities = \course_modinfo::get_array_of_activities($course);
} else {
2023-08-24 23:02:41 +02:00
// Deprecated in Moodle 4.0+, but not yet available in Moodle 3.11.
$activities = get_array_of_activities($course->id);
}
2023-08-24 23:02:41 +02:00
foreach ($activities as $act) {
if ($act->visible) {
2023-08-25 13:04:19 +02:00
$gradeitems = grade_item::fetch_all(['itemtype' => 'mod',
2023-08-25 11:52:05 +02:00
'itemmodule' => $act->mod,
'iteminstance' => $act->id,
'courseid' => $course->id]);
2023-08-24 23:02:41 +02:00
if (!empty($gradeitems)) {
foreach ($gradeitems as $gi) {
if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
try {
2023-08-24 23:02:41 +02:00
$gradable = new static($gi->id, $studyitem);
$list[] = $gradable;
2023-08-25 10:41:56 +02:00
} catch (\InvalidArgumentException $x) {
2023-08-25 17:33:20 +02:00
// Pass and do not add to the list if an error occurs.
$gradable = null; // Clean up gradable variable.
}
}
}
}
}
}
2023-08-24 23:02:41 +02:00
usort($list, function($a, $b) {
$course = $a->coursesort <=> $b->coursesort;
2023-08-25 10:41:56 +02:00
return ($course != 0) ? $course : $a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
});
return $list;
}
2023-08-24 23:02:41 +02:00
public static function list_studyitem_gradables(studyitem $studyitem) {
global $DB;
$table = 'local_treestudyplan_gradeinc';
$list = [];
2023-08-24 23:02:41 +02:00
$records = $DB->get_records($table, ['studyitem_id' => $studyitem->id()]);
foreach ($records as $r) {
if (isset($r->grade_item_id)) {
try {
2023-08-24 23:02:41 +02:00
if ($r->include || $r->required) {
$list[] = new static($r->grade_item_id, $studyitem);
}
2023-08-25 10:41:56 +02:00
} catch (\InvalidArgumentException $x) {
2023-08-25 09:44:34 +02:00
// On InvalidArgumentException, the grade_item id can no longer be found.
2023-08-24 23:02:41 +02:00
// Remove the link to avoid database record hogging.
$DB->delete_records($table, ['id' => $r->id]);
}
}
}
2023-08-24 23:02:41 +02:00
usort($list, function($a, $b) {
$course = $a->coursesort <=> $b->coursesort;
2023-08-25 10:41:56 +02:00
return ($course != 0) ? $course : $a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
});
return $list;
}
2023-08-25 13:04:19 +02:00
public static function include_grade(int $gradeid, int $itemid, bool $include, bool $required = false) {
global $DB;
$table = 'local_treestudyplan_gradeinc';
2023-08-24 23:02:41 +02:00
if ($include) {
2023-08-25 09:44:34 +02:00
// Make sure a record exits.
2023-08-25 09:33:42 +02:00
$r = $DB->get_record($table, ['studyitem_id' => $itemid, 'grade_item_id' => $gradeid]);
2023-08-24 23:02:41 +02:00
if ($r) {
$r->include = 1;
2023-08-25 12:16:51 +02:00
$r->required = boolval($required) ? 1 : 0;
$id = $DB->update_record($table, $r);
} else {
$DB->insert_record($table, [
2023-08-25 09:33:42 +02:00
'studyitem_id' => $itemid,
'grade_item_id' => $gradeid,
2023-08-24 23:02:41 +02:00
'include' => 1,
2023-08-25 17:33:20 +02:00
'required' => boolval($required) ? 1 : 0 ]
);
}
} else {
2023-08-25 09:44:34 +02:00
// Remove if it should not be included.
2023-08-25 09:33:42 +02:00
$r = $DB->get_record($table, ['studyitem_id' => $itemid, 'grade_item_id' => $gradeid]);
2023-08-24 23:02:41 +02:00
if ($r) {
$DB->delete_records($table, ['id' => $r->id]);
}
}
return success::success();
}
}