. /** * Handle badge information * @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'); use \core_badges\badge; /** * Handle badge information in the same style as the other classes */ class badgeinfo { /** * Holds database record * @var \core_badges\badge */ private $badge; /** * Maps badge status to strings * @var array */ private const STATUSINFO = [ BADGE_STATUS_INACTIVE => 'inactive', BADGE_STATUS_ACTIVE => 'active', BADGE_STATUS_INACTIVE_LOCKED => 'inactive', BADGE_STATUS_ACTIVE_LOCKED => 'active', BADGE_STATUS_ARCHIVED => 'archived', ]; /** * Maps badge status to locked or not * @var array */ private const LOCKEDINFO = [ BADGE_STATUS_INACTIVE => 0, BADGE_STATUS_ACTIVE => 0, BADGE_STATUS_INACTIVE_LOCKED => 1, BADGE_STATUS_ACTIVE_LOCKED => 1, BADGE_STATUS_ARCHIVED => 1, // We don't want to edit archived badges anyway.... . ]; /** * Construct new badgeinfo object * @param badge $badge Badge object to use */ public function __construct(badge $badge) { $this->badge = $badge; } /** * Return full name * @return string */ public function name() { return $this->badge->name; } /** * Get the badge id from the badge name * @param string $name Badge name * @return int Badge id */ public static function id_from_name($name) { global $DB; return $DB->get_field("badge", "id", ['name' => $name]); } /** * Check if a given badge exists * @param int $id Badge id * @return bool */ public static function exists($id) { global $DB; return is_numeric($id) && $DB->record_exists('badge', array('id' => $id)); } /** * Webservice structure for editor info * @param int $value Webservice requirement constant */ public static function editor_structure($value = VALUE_REQUIRED) : \external_description { return new \external_single_structure([ "id" => new \external_value(PARAM_INT, 'id of badge'), "infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL), "name" => new \external_value(PARAM_TEXT, 'badge name'), "status" => new \external_value(PARAM_TEXT, 'badge status'), "locked" => new \external_value(PARAM_TEXT, 'badge lock status'), "criteria" => new \external_multiple_structure( new \external_value(PARAM_RAW, 'criteria text'), 'badge criteria', VALUE_OPTIONAL), "description" => new \external_value(PARAM_TEXT, 'badge description'), "imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'), "studentcount" => new \external_value(PARAM_INT, 'number of studyplan students that can get this badge', VALUE_OPTIONAL), "issuedcount" => new \external_value(PARAM_INT, 'number of studyplan students that have got this badge', VALUE_OPTIONAL), ], "Badge info", $value); } /** * Webservice model for editor info * @param int[] $studentlist List of user id's to use for checking issueing progress within a study plan * @return array Webservice data model */ public function editor_model(array $studentlist = null) { if ($this->badge->type == BADGE_TYPE_SITE) { $context = \context_system::instance(); } else { $context = \context_course::instance($this->badge->courseid); } // If the user is viewing another user's badge and doesn't have the right capability return only part of the data. $criteria = []; foreach ($this->badge->get_criteria() as $bc) { $criteria[] = $bc->get_title()." ".$bc->get_details(); } $model = [ 'id' => $this->badge->id, 'infolink' => (new \moodle_url('/badges/overview.php', ['id' => $this->badge->id]))->out(false), 'name' => $this->badge->name, 'status' => self::STATUSINFO[$this->badge->status], 'locked' => self::LOCKEDINFO[$this->badge->status], 'criteria' => $criteria, 'description' => $this->badge->description, 'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/', 'f1')->out(false), ]; // Add badge issue stats if a studentlist is attached to the request. if (!empty($studentlist) && is_array($studentlist)) { $model['studentcount'] = count($studentlist); $model['issuedcount'] = $this->count_issued($studentlist); } return $model; } /** * Webservice structure for userinfo * @param int $value Webservice requirement constant */ public static function user_structure($value = VALUE_REQUIRED) : \external_description { return new \external_single_structure([ "id" => new \external_value(PARAM_INT, 'id of badge'), "infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL), "name" => new \external_value(PARAM_TEXT, 'badge name'), "completion" => new \external_multiple_structure(new \external_single_structure([ "types" => new \external_multiple_structure(new \external_single_structure([ 'criteria' => new \external_multiple_structure(new \external_single_structure([ "title" => new \external_value(PARAM_TEXT, 'criterion title'), "details" => new \external_value(PARAM_TEXT, 'criterion details'), "completed" => new \external_value(PARAM_BOOL, 'criterion is completed or not'), ]),'specific criteria'), "aggregation" => new \external_value(PARAM_TEXT, 'any|all'), "count" => new \external_value(PARAM_INT, 'effective number of critera for type progress'), "progress" => new \external_value(PARAM_INT, 'effective number of completed criteria for type progress'), "fraction" => new \external_value(PARAM_FLOAT, 'fraction of completed type criteria as float'), ]), "criteria types"), "aggregation" => new \external_value(PARAM_TEXT, 'any|all'), "count" => new \external_value(PARAM_INT, 'total number of critera for progress'), "progress" => new \external_value(PARAM_INT, 'number of completed criteria for progress'), "fraction" => new \external_value(PARAM_FLOAT, 'fraction of completed criteria as float'), ]),'badge criteria', VALUE_OPTIONAL), "description" => new \external_value(PARAM_TEXT, 'badge description'), "imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'), "issued" => new \external_value(PARAM_BOOL, 'badge is issued'), "dateissued" => new \external_value(PARAM_TEXT, 'date the badge was issued', VALUE_OPTIONAL), "dateexpire" => new \external_value(PARAM_TEXT, 'date the badge will expire', VALUE_OPTIONAL), "uniquehash" => new \external_value(PARAM_TEXT, 'badge issue hash', VALUE_OPTIONAL), "issuedlink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL), ], "Badge info", $value); } /** * Webservice model for user info * @param int $userid ID of user to check specific info for * @return array Webservice data model */ public function user_model($userid) { global $DB; if ($this->badge->type == BADGE_TYPE_SITE) { $context = \context_system::instance(); } else { $context = \context_course::instance($this->badge->courseid); } $issued = $this->badge->is_issued($userid); // If the user is viewing another user's badge and doesn't have the right capability return only part of the data. $badge = [ 'id' => $this->badge->id, 'name' => $this->badge->name, 'description' => $this->badge->description, 'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/', 'f1' )->out(false), 'completion' => $this->badge_completion_data($userid), 'issued' => $issued, 'infolink' => (new \moodle_url('/badges/overview.php', ['id' => $this->badge->id]))->out(false), ]; if ($issued) { $issueinfo = $DB->get_record('badge_issued', array('badgeid' => $this->badge->id, 'userid' => $userid)); $badge['dateissued'] = date("Y-m-d", $issueinfo->dateissued); if ($issueinfo->expiredate) { $badge['dateexpire'] = date("Y-m-d", $issueinfo->dateexpire); } $badge['uniquehash'] = $issueinfo->uniquehash; $badge['issuedlink'] = (new \moodle_url('/badges/badge.php', ['hash' => $issueinfo->uniquehash]))->out(false); } return $badge; } protected function badge_completion_data($userid) { $count = 0; $progress = 0; $fraction = 0; $badgeagg = $this->badge->get_aggregation_method(); $types = []; foreach ($this->badge->criteria as $type => $c) { $typeagg = $this->badge->get_aggregation_method($type); $typecrit = []; $typeprogress = 0; $typecount = count($c); foreach ($c as $bc) { $iscompleted = $bc->review($userid); $typecrit[] = [ "title" => $bc->get_title(), "details" => $bc->get_details(), "completed" => $iscompleted, ]; if ($iscompleted) { $typeprogress++; } } if ($typeagg == BADGE_CRITERIA_AGGREGATION_ANY) { $typecount = 1; $typeprogress = ($typeprogress > 0)?1:0; } if($badgeagg == BADGE_CRITERIA_AGGREGATION_ANY) { /* If ANY completion overall, count only the criteria type with the highest completion percentage -. Overwrite data if current type is more complete */ $typefraction = $typeprogress / $typecount; if ($typefraction > $fraction) { $fraction = $typefraction; $count = $typecount; $progress = $typeprogress; } } else { /* If ALL completion overall, just add it up to the total */ $count += $typecount; $progress += $typeprogress; } $typeinfo = [ 'aggregation' => ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL)?"all":"any", 'criteria' => $typecrit, 'count' => $typecount, 'progress' => $typeprogress, "fraction" => $typefraction, ]; $types[] = $typeinfo; } $c = [ "types" => $types, "aggregation" => ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL)?"all":"any", "count" => $count, "progress" => $progress, "fraction" => $fraction, ]; } /** * Count how many of the students in the array have this badge issued * @param int[] $studentids List of user id's to check * @return int */ public function count_issued(array $studentids) { $issuecount = 0; foreach ($studentids as $userid) { if ($this->badge->is_issued($userid)) { $issuecount++; } } return $issuecount; } }