moodle_local_treestudyplan/classes/courseinfo.php

458 lines
17 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/>.
/**
2023-08-27 22:20:17 +02:00
* Class to process information about a course
2023-08-24 23:02:41 +02:00
* @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;
2023-08-27 22:20:17 +02:00
/**
* Class to process information about a course
*/
class courseinfo {
2023-08-27 22:20:17 +02:00
/**
* Table name used in this class
* @var string */
const TABLE = 'course';
2023-08-24 23:02:41 +02:00
2023-08-27 22:20:17 +02:00
/** @var stdClass */
private $course;
2023-08-27 22:20:17 +02:00
/** @var \context */
private $context;
2023-08-27 22:20:17 +02:00
/** @var \context */
2023-06-30 12:14:11 +02:00
private $coursecontext;
2023-08-27 22:20:17 +02:00
/** @var studyitem */
private $studyitem;
2023-08-27 22:20:17 +02:00
/** @var array */
private static $contentitems = null;
2023-08-31 07:40:55 +02:00
/**
* Cache of enrolled students in a particular course
* @var array
*/
private static $coursestudents = [];
2023-08-27 22:20:17 +02:00
/**
* Return database identifier
* @return int
*/
2023-08-24 23:02:41 +02:00
public function id() {
return $this->course->id;
}
2023-08-27 22:20:17 +02:00
/**
* Return short name
* @return string
*/
2023-08-24 23:02:41 +02:00
public function shortname() {
return $this->course->shortname;
}
2023-08-27 22:20:17 +02:00
/**
* Return course record
* @return stdClass
*/
2023-08-24 23:02:41 +02:00
public function course() {
2023-08-27 22:20:17 +02:00
return $this->course;
}
2023-08-27 22:20:17 +02:00
/**
* Return course context
* @return \context
*/
2023-08-24 23:02:41 +02:00
public function course_context() {
return $this->coursecontext;
}
2023-08-27 22:20:17 +02:00
/**
* Return course's category context
* @return \context
*/
2023-08-24 23:02:41 +02:00
public function category_context() {
return $this->context;
}
2023-08-27 22:20:17 +02:00
/**
* Get content items (activity icons) from the repository
* @return content_item[]
*/
protected 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;
}
2023-08-27 22:20:17 +02:00
/**
* Check if current user is teacher in this course
* @return book
*/
protected function am_teacher() : bool {
global $USER;
return is_enrolled($this->coursecontext, $USER, 'mod/assign:grade');
}
2023-08-27 22:20:17 +02:00
/**
* Check if specified user can select gradables in this course
* @param int $userid User id to check for . Leave empty to check current user
*/
2023-08-25 17:33:20 +02:00
protected function i_can_select_gradables($userid = -1) {
global $USER, $DB;
2023-08-24 23:02:41 +02:00
if ($userid <= 0) {
$usr = $USER;
2023-08-25 09:33:42 +02:00
} else {
$usr = $DB->get_record('user', ['id' => $userid, 'deleted' => 0]);
}
return($usr && is_enrolled($this->coursecontext, $usr, 'local/treestudyplan:selectowngradables'));
2023-08-24 23:02:41 +02:00
}
2023-08-27 22:20:17 +02:00
/**
* Get specific contentitem (activity icons) by name
* @param mixed $name Name of content item
* @return content_item|null
*/
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];
}
}
2023-08-24 23:02:41 +02:00
return null;
}
2023-08-27 22:20:17 +02:00
/**
2023-08-27 23:29:46 +02:00
* Construct courseinfo based on course id and
2023-08-27 22:20:17 +02:00
* @param int $id Course id
* @param studyitem|null $studyitem Studyitem linking this course (if applicable)
*/
2023-08-24 23:02:41 +02:00
public function __construct($id, studyitem $studyitem = null) {
global $DB;
$this->studyitem = $studyitem;
$this->course = \get_course($id);
$this->context = \context_coursecat::instance($this->course->category);
$this->coursecontext = \context_course::instance($this->course->id);
}
2023-08-27 22:20:17 +02:00
/**
* Check if a course with the given ID exists
* @param int $id Course id
* @return bool
*/
2023-08-24 23:02:41 +02:00
public static function exists($id) {
global $DB;
return is_numeric($id) && $DB->record_exists(self::TABLE, ['id' => $id]);
}
2023-08-27 22:20:17 +02:00
/**
* Find course id from shortname
* @param string $shortname Shortname of the course
* @return int Course id
*/
2023-08-24 23:02:41 +02:00
public static function id_from_shortname($shortname) {
global $DB;
2023-08-24 23:02:41 +02:00
return $DB->get_field(self::TABLE, "id", ['shortname' => $shortname]);
}
2023-08-27 23:29:46 +02:00
/**
2023-08-27 22:20:17 +02:00
* Determine course timing [future, present or past] based on a course date
* @param stdClass $course Course database record
* @return string 'future', 'present' or 'past'
*/
2023-08-24 23:02:41 +02:00
public static function coursetiming($course) {
$now = time();
2023-08-24 23:02:41 +02:00
if ($now > $course->startdate) {
if ($course->enddate > 0 && $now > $course->enddate) {
return "past";
2023-08-25 11:52:05 +02:00
} else {
return "present";
}
2023-08-25 10:41:56 +02:00
} else {
return "future";
}
}
2023-08-27 23:29:46 +02:00
/**
2023-08-27 22:20:17 +02:00
* Determine course timing for this course [future, present or past]
* @return string 'future', 'present' or 'past'
*/
2023-08-24 23:02:41 +02:00
public function timing() {
return self::coursetiming($this->course);
}
2023-08-24 23:02:41 +02:00
2023-08-27 22:20:17 +02:00
/**
* Determine proper display name for this course based on config settings, custom fields etc...
* @return string Display name for the course
*/
2023-08-24 23:02:41 +02:00
public function displayname() {
$displayfield = get_config("local_treestudyplan", "display_field");
if ($displayfield == "idnumber") {
2023-08-24 23:02:41 +02:00
$idnumber = trim(preg_replace("/\s+/u", " ", $this->course->idnumber));
if (strlen($idnumber) > 0) {
return $this->course->idnumber;
}
2023-08-24 23:02:41 +02:00
} else if (strpos( $displayfield , "customfield_") === 0) {
$fieldname = substr($displayfield, strlen("customfield_"));
$handler = \core_customfield\handler::get_handler('core_course', 'course');
$datas = $handler->get_instance_data($this->course->id);
2023-08-24 23:02:41 +02:00
foreach ($datas as $data) {
if ($data->get_field()->get('shortname') == $fieldname) {
$value = trim(preg_replace("/\s+/u", " ", $data->get_value()));
if (strlen($value) > 0) {
return $value;
}
}
}
}
2023-08-24 23:02:41 +02:00
// Fallback to shortname when the specified display field fails, since shortname is never empty.
return $this->course->shortname;
}
2023-08-27 22:20:17 +02:00
/**
* Webservice structure for basic info
* @param int $value Webservice requirement constant
*/
2023-08-28 08:51:52 +02:00
public static function simple_structure($value = VALUE_REQUIRED) : \external_description {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'linked course id'),
"fullname" => new \external_value(PARAM_TEXT, 'linked course name'),
"shortname" => new \external_value(PARAM_TEXT, 'linked course shortname'),
"displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'),
"context" => contextinfo::structure(VALUE_OPTIONAL),
2023-08-24 23:02:41 +02:00
], 'referenced course information', $value);
}
2023-08-27 23:29:46 +02:00
/**
2023-08-27 22:20:17 +02:00
* Webservice model for basic info
* @return array Webservice data model
*/
public function simple_model() {
$contextinfo = new contextinfo($this->context);
$info = [
'id' => $this->course->id,
'fullname' => $this->course->fullname,
'shortname' => $this->course->shortname,
'displayname' => $this->displayname(),
'context' => $contextinfo->model()
];
2023-08-24 23:02:41 +02:00
return $info;
}
2023-08-27 21:57:21 +02:00
/**
* Webservice structure for editor info
* @param int $value Webservice requirement constant
*/
2023-08-28 08:51:52 +02:00
public static function editor_structure($value = VALUE_REQUIRED) : \external_description {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'linked course id'),
"fullname" => new \external_value(PARAM_TEXT, 'linked course name'),
"shortname" => new \external_value(PARAM_TEXT, 'linked course shortname'),
"displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'),
"context" => contextinfo::structure(VALUE_OPTIONAL),
"ctxid" => new \external_value(PARAM_INT, 'course context id name'),
2023-08-25 11:52:05 +02:00
"grades" => new \external_multiple_structure(gradeinfo::editor_structure(),
'grade list (legacy list)', VALUE_OPTIONAL),
"completion" => corecompletioninfo::editor_structure(VALUE_OPTIONAL),
"timing" => new \external_value(PARAM_TEXT, '(past|present|future)'),
"startdate" => new \external_value(PARAM_TEXT, 'Course start date'),
"enddate" => new \external_value(PARAM_TEXT, 'Course end date'),
"amteacher" => new \external_value(PARAM_BOOL, 'Requesting user is teacher in this course'),
"canupdatecourse" => new \external_value(PARAM_BOOL, "If the current user can update this course"),
"canselectgradables" => new \external_value(PARAM_BOOL, 'Requesting user can change selected gradables'),
2023-08-31 07:40:55 +02:00
"numenrolled" => new \external_value(PARAM_INT, 'number of students from this studyplan enrolled in the course'),
"tag" => new \external_value(PARAM_TEXT, 'Tag'),
2023-08-24 23:02:41 +02:00
], 'referenced course information', $value);
}
2023-08-27 21:57:21 +02:00
/**
* Webservice model for editor info
2023-08-27 22:20:17 +02:00
* @param studyitem $studyitem Specify a specific study item to check gradable selections for. Leave empty to use default
* @param bool $usecorecompletioninfo Whether to use corecompletion info instead of custom selected gradables
2023-08-27 21:57:21 +02:00
* @return array Webservice data model
*/
2023-08-25 13:04:19 +02:00
public function editor_model(studyitem $studyitem = null, $usecorecompletioninfo = false) {
global $DB;
$contextinfo = new contextinfo($this->context);
2023-08-24 23:02:41 +02:00
$timing = $this->timing();
2023-08-24 23:02:41 +02:00
2023-08-31 07:40:55 +02:00
if (isset($studyitem)) {
$numenrolled = $this->count_enrolled_students($studyitem->studyline()->studyplan()->find_linked_userids());
} else {
$numenrolled = 0;
}
$info = [
'id' => $this->course->id,
'fullname' => $this->course->fullname,
'shortname' => $this->course->shortname,
'displayname' => $this->displayname(),
'context' => $contextinfo->model(),
'ctxid' => $this->coursecontext->id,
'timing' => $timing,
2023-08-24 23:02:41 +02:00
'startdate' => date("Y-m-d", $this->course->startdate, ),
'enddate' => date("Y-m-d", $this->course->enddate),
2023-08-25 17:33:20 +02:00
'amteacher' => $this->am_teacher(),
2023-08-24 23:02:41 +02:00
'canupdatecourse' => \has_capability("moodle/course:update", $this->coursecontext),
2023-08-25 17:33:20 +02:00
'canselectgradables' => $this->i_can_select_gradables(),
'tag' => "Editormodel",
'grades' => [],
2023-08-31 07:40:55 +02:00
'numenrolled' => $numenrolled,
];
2023-08-24 23:02:41 +02:00
if (!$usecorecompletioninfo) {
2023-08-27 22:20:17 +02:00
$gradables = gradeinfo::list_course_gradables($this->course, $studyitem ? $studyitem : $this->studyitem );
2023-08-24 23:02:41 +02:00
foreach ($gradables as $gradable) {
2023-08-27 22:20:17 +02:00
$info['grades'][] = $gradable->editor_model($studyitem ? $studyitem : $this->studyitem);
}
2023-08-31 07:40:55 +02:00
} else if (isset($this->studyitem)) {
$cc = new corecompletioninfo($this->course, $this->studyitem);
$studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
$info['completion'] = $cc->editor_model($studentlist);
}
return $info;
}
2023-08-27 21:57:21 +02:00
/**
* Webservice structure for userinfo
* @param int $value Webservice requirement constant
*/
2023-08-28 08:51:52 +02:00
public static function user_structure($value = VALUE_REQUIRED) : \external_description {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'linked course id'),
"fullname" => new \external_value(PARAM_TEXT, 'linked course name'),
"shortname" => new \external_value(PARAM_TEXT, 'linked course shortname'),
"displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'),
"context" => contextinfo::structure(VALUE_OPTIONAL),
"ctxid" => new \external_value(PARAM_INT, 'course context id name'),
2023-08-24 23:02:41 +02:00
"grades" => new \external_multiple_structure(gradeinfo::user_structure(), 'grade list (legacy list)', VALUE_OPTIONAL),
"completion" => corecompletioninfo::user_structure(VALUE_OPTIONAL),
"timing" => new \external_value(PARAM_TEXT, '(past|present|future)'),
"startdate" => new \external_value(PARAM_TEXT, 'Course start date'),
"enddate" => new \external_value(PARAM_TEXT, 'Course end date'),
2023-08-31 07:40:55 +02:00
"enrolled" => new \external_value(PARAM_BOOL, 'True if student is enrolled as student in this course'),
2023-08-24 23:02:41 +02:00
], 'course information', $value);
}
2023-08-31 07:40:55 +02:00
/**
* List all users enrolled in a course as student by userid
* @param int $courseid Course id of the course to check
* @return int[] Array if user ids
*/
public static function get_course_students($courseid) : array {
global $CFG;
if (!array_key_exists($courseid, self::$coursestudents)) {
$students = [];
$context = \context_course::instance($courseid);
foreach (explode(', ', $CFG->gradebookroles) as $roleid) {
$roleid = trim($roleid);
$students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC'));
}
self::$coursestudents[$courseid] = $students;
}
return self::$coursestudents[$courseid];
}
/**
* Check if a user is enrolled as a student in this course
* (Has a gradebook role)
* @param int $userid The user Id to check
*/
public function is_enrolled_student($userid) : bool {
global $CFG;
foreach (explode(', ', $CFG->gradebookroles) as $roleid) {
if (user_has_role_assignment($userid, $roleid, $this->coursecontext->id)) {
return true;
}
}
return false;
}
/**
* List how many users from a list also enrolled as students
* (Has a gradebook role)
* @param int[] $userids The user Ids to check
*/
private function count_enrolled_students(array $userids) {
$count = 0;
foreach($userids as $userid){
if($this->is_enrolled_student($userid)){
$count++;
}
}
return $count;
}
2023-08-27 21:57:21 +02:00
/**
* Webservice model for user info
* @param int $userid ID of user to check specific info for
2023-08-27 22:20:17 +02:00
* @param bool $usecorecompletioninfo Whether to use corecompletion info instead of custom selected gradables
2023-08-27 21:57:21 +02:00
* @return array Webservice data model
*/
2023-08-25 13:04:19 +02:00
public function user_model($userid, $usecorecompletioninfo = false) {
global $DB;
$contextinfo = new contextinfo($this->context);
$timing = $this->timing();
$info = [
'id' => $this->course->id,
'fullname' => $this->course->fullname,
'shortname' => $this->course->shortname,
'displayname' => $this->displayname(),
'context' => $contextinfo->model(),
'ctxid' => $this->coursecontext->id,
'timing' => $timing,
2023-08-24 23:02:41 +02:00
'startdate' => date("Y-m-d", $this->course->startdate),
'enddate' => date("Y-m-d", $this->course->enddate),
'grades' => [],
2023-08-31 07:40:55 +02:00
'enrolled' => $this->is_enrolled_student($userid),
];
2023-08-24 23:02:41 +02:00
if (!$usecorecompletioninfo) {
$gradables = gradeinfo::list_studyitem_gradables($this->studyitem);
2023-08-24 23:02:41 +02:00
foreach ($gradables as $gi) {
$info['grades'][] = $gi->user_model($userid);
}
2023-08-31 07:40:55 +02:00
} else if (isset($this->studyitem)) {
$cc = new corecompletioninfo($this->course,$this->studyitem);
$info['completion'] = $cc->user_model($userid);
}
return $info;
}
}