. /** * Class to process information about a course * @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; /** * Class to process information about a course */ class courseinfo { /** * Table name used in this class * @var string */ const TABLE = 'course'; /** @var stdClass */ private $course; /** @var \context */ private $context; /** @var \context */ private $coursecontext; /** @var studyitem */ private $studyitem; /** @var array */ private static $contentitems = null; /** * Return database identifier * @return int */ public function id() { return $this->course->id; } /** * Return short name * @return string */ public function shortname() { return $this->course->shortname; } /** * Return course record * @return stdClass */ public function course() { return $this->course; } /** * Return course context * @return \context */ public function course_context() { return $this->coursecontext; } /** * Return course's category context * @return \context */ public function category_context() { return $this->context; } /** * Get content items (activity icons) from the repository * @return content_item[] */ protected 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; } /** * 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'); } /** * Check if specified user can select gradables in this course * @param int $userid User id to check for . Leave empty to check current user */ protected function i_can_select_gradables($userid = -1) { global $USER, $DB; if ($userid <= 0) { $usr = $USER; } else { $usr = $DB->get_record('user', ['id' => $userid, 'deleted' => 0]); } return($usr && is_enrolled($this->coursecontext, $usr, 'local/treestudyplan:selectowngradables')); } /** * 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(); for ($i = 0; $i < count($contentitems); $i++) { if ($contentitems[$i]->get_name() == $name) { return $contentitems[$i]; } } return null; } /** * Construct courseinfo based on course id and * @param int $id Course id * @param studyitem|null $studyitem Studyitem linking this course (if applicable) */ 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); } /** * Check if a course with the given ID exists * @param int $id Course id * @return bool */ public static function exists($id) { global $DB; return is_numeric($id) && $DB->record_exists(self::TABLE, ['id' => $id]); } /** * Find course id from shortname * @param string $shortname Shortname of the course * @return int Course id */ public static function id_from_shortname($shortname) { global $DB; return $DB->get_field(self::TABLE, "id", ['shortname' => $shortname]); } /** * Determine course timing [future, present or past] based on a course date * @param stdClass $course Course database record * @return string 'future', 'present' or 'past' */ public static function coursetiming($course) { $now = time(); if ($now > $course->startdate) { if ($course->enddate > 0 && $now > $course->enddate) { return "past"; } else { return "present"; } } else { return "future"; } } /** * Determine course timing for this course [future, present or past] * @return string 'future', 'present' or 'past' */ public function timing() { return self::coursetiming($this->course); } /** * Determine proper display name for this course based on config settings, custom fields etc... * @return string Display name for the course */ public function displayname() { $displayfield = get_config("local_treestudyplan", "display_field"); if ($displayfield == "idnumber") { $idnumber = trim(preg_replace("/\s+/u", " ", $this->course->idnumber)); if (strlen($idnumber) > 0) { return $this->course->idnumber; } } 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); 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; } } } } // Fallback to shortname when the specified display field fails, since shortname is never empty. return $this->course->shortname; } /** * Webservice structure for basic info * @param int $value Webservice requirement constant * @return \external_description Webservice output structure */ public static function simple_structure($value = VALUE_REQUIRED) { 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), ], 'referenced course information', $value); } /** * 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() ]; return $info; } /** * Webservice structure for editor info * @param int $value Webservice requirement constant * @return \external_single_structure Webservice output structure */ public static function editor_structure($value = VALUE_REQUIRED) { 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'), "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'), "tag" => new \external_value(PARAM_TEXT, 'Tag'), ], 'referenced course information', $value); } /** * Webservice model for editor info * @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 * @return array Webservice data model */ public function editor_model(studyitem $studyitem = null, $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, 'startdate' => date("Y-m-d", $this->course->startdate, ), 'enddate' => date("Y-m-d", $this->course->enddate), 'amteacher' => $this->am_teacher(), 'canupdatecourse' => \has_capability("moodle/course:update", $this->coursecontext), 'canselectgradables' => $this->i_can_select_gradables(), 'tag' => "Editormodel", 'grades' => [], ]; if (!$usecorecompletioninfo) { $gradables = gradeinfo::list_course_gradables($this->course, $studyitem ? $studyitem : $this->studyitem ); foreach ($gradables as $gradable) { $info['grades'][] = $gradable->editor_model($studyitem ? $studyitem : $this->studyitem); } } else { $cc = new corecompletioninfo($this->course); $info['completion'] = $cc->editor_model(); } return $info; } /** * Webservice structure for userinfo * @param int $value Webservice requirement constant * @return \external_single_structure Webservice output structure */ public static function user_structure($value = VALUE_REQUIRED) { 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'), "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'), ], 'course information', $value); } /** * Webservice model for user info * @param int $userid ID of user to check specific info for * @param bool $usecorecompletioninfo Whether to use corecompletion info instead of custom selected gradables * @return array Webservice data model */ 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, 'startdate' => date("Y-m-d", $this->course->startdate), 'enddate' => date("Y-m-d", $this->course->enddate), 'grades' => [], ]; if (!$usecorecompletioninfo) { $gradables = gradeinfo::list_studyitem_gradables($this->studyitem); foreach ($gradables as $gi) { $info['grades'][] = $gi->user_model($userid); } } else { $cc = new corecompletioninfo($this->course); $info['completion'] = $cc->user_model($userid); } return $info; } }