. /** * 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 award_criteria; use core_badges\badge; use moodle_url; /** * 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', ['id' => $id]); } /** * Webservice structure for editor info * @param int $value Webservice requirement constant */ public static function simple_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_RAW, 'badge issue information link', VALUE_OPTIONAL), "name" => new \external_value(PARAM_RAW, 'badge name'), "status" => new \external_value(PARAM_TEXT, 'badge status'), "locked" => new \external_value(PARAM_TEXT, 'badge lock status'), "description" => new \external_value(PARAM_RAW, 'badge description'), "imageurl" => new \external_value(PARAM_RAW, 'url of badge image'), "active" => new \external_value(PARAM_BOOL, 'badge is available'), ], "Badge basic 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 simple_model() { if ($this->badge->type == BADGE_TYPE_SITE) { $context = \context_system::instance(); } else { $context = \context_course::instance($this->badge->courseid); } $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], 'description' => $this->badge->description, 'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/', 'f1')->out(false), "active" => $this->badge->is_active(), ]; return $model; } /** * 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_RAW, 'badge issue information link', VALUE_OPTIONAL), "name" => new \external_value(PARAM_RAW, '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_RAW, 'badge description'), "imageurl" => new \external_value(PARAM_RAW, '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), "active" => new \external_value(PARAM_BOOL, 'badge is available'), ], "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), "active" => $this->badge->is_active(), ]; // 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" => self::badge_completion_structure(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), "active" => new \external_value(PARAM_BOOL, 'badge is available'), ], "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), "active" => $this->badge->is_active(), ]; if ($issued) { $issueinfo = $DB->get_record('badge_issued', ['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; } /** * Define structure for badge completion parts * @param object $value VALUE_OPTIONAL or VALUE_REQUIRED */ protected static function badge_completion_structure($value) { return 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_RAW, 'criterion title'), "description" => new \external_value(PARAM_RAW, 'criterion description'), "link" => new \external_value(PARAM_RAW, 'link to criterion resource', VALUE_OPTIONAL), "requirements" => new \external_multiple_structure(new \external_single_structure([ "title" => new \external_value(PARAM_RAW, 'requirment title'), "completed" => new \external_value(PARAM_BOOL, 'criterion is completed or not', VALUE_OPTIONAL), ]), "criterion specific requirements", VALUE_OPTIONAL), "completed" => new \external_value(PARAM_BOOL, 'criterion is completed or not'), ]), 'specific criteria'), "title" => new \external_value(PARAM_RAW, 'type title'), "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", VALUE_OPTIONAL), "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'), "title" => new \external_value(PARAM_RAW, 'completion title'), ], 'badge completion information', $value); } /** * Generate completion model of this badge for a given user * @param int $userid id of user to check for */ protected function badge_completion_data($userid): array { $count = 0; $progress = 0; $fraction = 0; $badgeagg = $this->badge->get_aggregation_method(); $types = []; foreach ($this->badge->criteria as $type => $bc) { if ($type != BADGE_CRITERIA_TYPE_OVERALL) { $typeagg = $this->badge->get_aggregation_method($type); $typecrit = $this->get_award_subcriteria($bc, $userid); $typecount = count($typecrit); $typeprogress = 0; foreach ($typecrit as $subcrit) { // Quickly check if the subcriteria are completed. if ($subcrit["completed"]) { $typeprogress++; } } // Determine how to process the progress data, depending on the TYPE's aggregation. if ($typeagg == BADGE_CRITERIA_AGGREGATION_ANY) { $typecount = 1; $typeprogress = ($typeprogress > 0) ? 1 : 0; } // Determine how to patch this data into the overall progress numbers, depending on the OVERALL aggregation. 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 = ($typecount > 0) ? ($typeprogress / $typecount) : ($typeprogress / $typecount); if ($typefraction > $fraction || ($fraction == 0 && $typecount > $count)) { $fraction = $typefraction; $count = $typecount; $progress = $typeprogress; } } else { /* If ALL completion overall, just add it up to the total */ $count += $typecount; $progress += $typeprogress; } $aggrgationhandle = ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL) ? "all" : "any"; $typeinfo = [ 'title' => ucfirst(get_string("criteria_descr_$type", "badges", get_string($aggrgationhandle, "core"))), 'aggregation' => $aggrgationhandle, 'criteria' => $typecrit, 'count' => $typecount, 'progress' => $typeprogress, "fraction" => $typefraction, ]; $types[] = $typeinfo; } } $aggrgationhandle = ($badgeagg == BADGE_CRITERIA_AGGREGATION_ALL) ? "all" : "any"; return [ "types" => $types, "title" => ucfirst(get_string("criteria_descr_0", "badges", mb_strtolower(get_string($aggrgationhandle, "core")))), "aggregation" => $aggrgationhandle, "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; } /** * Gets the module instance from the database and returns it. * If no module instance exists this function returns false. * * @return stdClass|bool */ private static function get_mod_instance($cmid) { global $DB; $rec = $DB->get_record_sql("SELECT md.name FROM {course_modules} cm, {modules} md WHERE cm.id = ? AND md.id = cm.module", [$cmid]); if ($rec) { return get_coursemodule_from_id($rec->name, $cmid); } else { return null; } } /** * Gets role name. * If no such role exists this function returns null. * * @return string|null */ private static function get_role_name($rid) { global $DB, $PAGE; $rec = $DB->get_record('role', ['id' => $rid]); if ($rec) { return role_get_name($rec, \context_system::instance(), ROLENAME_BOTH); } else { return null; } } /** * [Description for get_award_subcriteria] * * @param award_criteria $crit * * @return array * */ protected function get_award_subcriteria(award_criteria $crit, $userid = null): array { global $DB, $CFG; $list = []; if ($crit->criteriatype == BADGE_CRITERIA_TYPE_ACTIVITY) { foreach ($crit->params as $p) { $mod = self::get_mod_instance($p["module"]); if (!$mod) { $title = get_string('error:nosuchmod', 'badges'); $description = get_string('error:nosuchmod', 'badges'); } else { $title = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"');; $description = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"'); if (isset($p['bydate'])) { $description .= get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))); } } $subcrit = [ "title" => $title, "description" => $description, "requirements" => [ 'completion' => [ 'title' => get_string('completeactivity', 'core'). get_string('modulename', $mod->modname) . ' - ' . $mod->name, ], ], ]; if (isset($p["bydate"])) { $subcrit["requirements"]["bydate"] = [ 'title' => get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))), ]; } if (isset($userid)) { $info = new \completion_info($crit->course); $cm = new \stdClass(); $cm->id = $p['module']; $data = $info->get_data($cm, false, $userid); // Successfull completion states depend on the completion settings. if (isset($data->passgrade)) { // Passing grade is required. Don't issue a badge when state is COMPLETION_COMPLETE_FAIL. $completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]; } else { // Any grade is required. Issue a badge even when state is COMPLETION_COMPLETE_FAIL. $completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL]; } $modcompleted = in_array($data->completionstate, $completionstates); $subcrit["requirements"]["completion"]["completed"] = $modcompleted; $checkdate = true; if (isset($p["bydate"])) { $date = $data->timemodified; $checkdate = ($date <= $p['bydate']); $subcrit["requirements"]["bydate"]["completed"] = $checkdate; } $subcrit["completed"] = $modcompleted && $checkdate; } $list[] = $subcrit; } } else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) { foreach ($crit->params as $p) { $role = self::get_role_name($p['role']); if (!$role) { $title = get_string('error:nosuchrole', 'badges'); $description = get_string('error:nosuchrole', 'badges'); } else { $title = $role; $description = $role; } $subcrit = [ "title" => $title, "description" => $description, "requirements" => [], ]; if (isset($userid)) { $crit = $DB->get_record('badge_manual_award', ['issuerrole' => $p['role'], 'recipientid' => $userid, 'badgeid' => $crit->badgeid]); $subcrit["completed"] = $crit !== false; } $list[] = $subcrit; } } else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE || $crit->criteriatype == BADGE_CRITERIA_TYPE_COURSESET) { if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE) { $params = [reset($crit->params)]; // Only use the first parameter. } else { $params = $crit->params; } foreach ($params as $p) { $course = get_course($p["course"]); if (!$course) { $description = get_string('error:nosuchcourse', "badges"); } else { $description = \html_writer::tag('b', '"' . $course->fullname . '"'); if (isset($p['bydate'])) { $description .= get_string( 'criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))); } if (isset($p['grade'])) { $description .= get_string('criteria_descr_grade', 'badges', $p['grade']); } } $subcrit = [ "title" => $course->fullname, "link" => (new moodle_url($CFG->wwwroot."/course/view.php", ["id" => $course->id]))->out(), "description" => $description, "requirements" => [ 'completion' => [ 'title' => get_string('coursecompleted', 'completion'), ], ], ]; if (isset($p["grade"])) { $subcrit["requirements"]["grade"] = [ 'title' => get_string('criteria_descr_grade', 'badges', $p["grade"]), ]; } if (isset($p["bydate"])) { $subcrit["requirements"]["bydate"] = [ 'title' => get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))), ]; } if (isset($userid)) { $coursecompletion = new \completion_completion(["userid" => $userid, "course" => $course->id]); $coursecompleted = $coursecompletion->is_complete(); $subcrit["requirements"]["completion"]["completed"] = (bool) $coursecompleted; $checkgrade = true; if (isset($p["grade"])) { $grade = \grade_get_course_grade($userid, $course->id); $checkgrade = ($grade->grade >= $p['grade']); $subcrit["requirements"]["grade"]["completed"] = (bool) $checkgrade; } $checkdate = true; if (isset($p["bydate"])) { $checkdate = ((bool) $coursecompletion->timecompleted) && ($coursecompletion->timecompleted <= $p["bydate"]); $subcrit["requirements"]["bydate"]["completed"] = (bool) $checkdate; } $subcrit["completed"] = $coursecompleted && $checkgrade && $checkdate; } $list[] = $subcrit; } } else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_PROFILE) { foreach ($crit->params as $p) { if (is_numeric($p['field'])) { $fields = profile_get_custom_fields(); // Get formatted field name if such field exists. $fieldname = isset($fields[$p['field']]->name) ? format_string($fields[$p['field']]->name) : null; } else { $fieldname = \core_user\fields::get_display_name($p['field']); } if (!$fieldname) { $title = get_string('error:nosuchfield', 'badges'); $description = get_string('error:nosuchfield', 'badges'); } else { $title = $fieldname; $description = $fieldname; } $subcrit = [ "title" => $title, "description" => $description, "requirements" => [], ]; if (isset($userid)) { $join = ''; $where = ''; $sqlparams = [ 'userid' => $userid]; if (is_numeric($p['field'])) { // This is a custom field. $join .= " LEFT JOIN {user_info_data} uid ON uid.userid = u.id AND uid.fieldid = :fieldid "; $sqlparams["fieldid"] = $p['field']; $where = "uid.id IS NOT NULL"; } else if (in_array($p['field'], $this->allowed_default_fields)) { // This is a valid field from {user} table. if ($p['field'] == 'picture') { // The picture field is numeric and requires special handling. $where = "u.{$p['field']} != 0"; } else { $where = $DB->sql_isnotempty('u', "u.{$p['field']}", false, true); } } $sql = "SELECT 1 FROM {user} u " . $join . " WHERE u.id = :userid $where"; $completed = $DB->record_exists_sql($sql, $sqlparams); $subcrit["completed"] = (bool) $completed; } $list[] = $subcrit; } } else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_BADGE) { foreach ($crit->params as $p) { $badgename = $DB->get_field('badge', 'name', ['id' => $p['badge']]); if (!$badgename) { $title = get_string('error:nosuchbadge', 'badges'); $description = get_string('error:nosuchbadge', 'badges'); } else { $title = $badgename; $description = \html_writer::tag('b', '"' . $badgename . '"');; } $subcrit = [ "title" => $title, "description" => $description, "link" => (new \moodle_url($CFG->wwwroot."/badges/overview.php", ["id" => $p["badge"]]))->out(), "requirements" => [], ]; if (isset($userid)) { $badge = $DB->get_record('badge', ['id' => $p['badge']]); // See if the user has earned this badge. if ($badge) { $awarded = $DB->get_record('badge_issued', ['badgeid' => $p['badge'], 'userid' => $userid]); $awarded = isset($awarded); } else { $awarded = false; } $subcrit["completed"] = (bool)$awarded; } $list[] = $subcrit; } } else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COHORT) { foreach ($crit->params as $p) { $cohortname = $DB->get_field('cohort', 'name', ['id' => $p['cohort']]); if (!$cohortname) { $title = get_string('error:nosuchcohort', 'badges'); $description = get_string('error:nosuchcohort', 'badges'); } else { $title = \html_writer::tag('b', '"' . format_string($cohortname, true) . '"'); $description = \html_writer::tag('b', '"' . format_string($cohortname, true) . '"'); } $subcrit = [ "title" => $title, "description" => $description, "requirements" => [], ]; if (isset($userid)) { $cohort = $DB->get_record('cohort', ['id' => $p['cohort']]); $ismember = (bool) \cohort_is_member($cohort->id, $userid); $subcrit["completed"] = $ismember; } $list[] = $subcrit; } } else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COMPETENCY) { foreach ($crit->params as $p) { $competency = new \core_competency\competency($p['competency']); $competency->set('description', ''); // Will use 'short' description. /* Below must be one of the most convoluted ways of calling tool_lp_render_competency_summary. Since I'm pretty sure it's not an actually good idea to implement _render_competency_summary in a custom plugin, even though the system appearently allows you to add competency descriptions, it's probably a case of premature optimization. Still, since this is the way that 'badges/criteria/award_criteria_competency.php does it, we'll copy its example, to avoid differences in display.... */ $output = []; // Render the competency even if competencies are not currently enabled. \core_competency\api::skip_enabled(); if ($pluginsfunction = get_plugins_with_function('render_competency_summary')) { foreach ($pluginsfunction as $plugintype => $plugins) { foreach ($plugins as $pluginfunction) { $output[] = $pluginfunction($competency, $competency->get_framework(), false, false, true); } } } \core_competency\api::check_enabled(); if (count($output) < 1) { $title = get_string('error:nosuchcompetency', 'local_treestudyplan'); $description = get_string('error:nosuchcompetency', 'local_treestudyplan'); } else { /* Might as wel implode the output, just in case someone was actually stupid enough to implement _render_competency_summary in a custom plugin... */ $title = implode(" ", $output); $description = implode(" ", $output); } $subcrit = [ "title" => $title, "description" => $description, "requirements" => [], ]; if (isset($userid)) { /* Slightly modified from award_criteria_competency.php::review() to use criteria api class instead of direct calls.... */ $proficiency = false; $badge = $DB->get_record('badge', ['id' => $crit->badgeid]); if ($badge->type == BADGE_TYPE_SITE) { $uc = \core_competency\api::get_user_competency($userid, $p['competency']); $proficiency = $uc->get('proficiency'); } else if ($badge->type == BADGE_TYPE_COURSE) { $uc = \core_competency\api::get_user_competency_in_course($badge->courseid, $userid, $p['competency']); $proficiency = $uc->get('proficiency'); } $subcrit["completed"] = (bool) $proficiency; } $list[] = $subcrit; } } return $list; } /** * Find all badges that have a relation to a certain course * @param int|stdClass $course The course to relate to or it's id * @param string $search An optional search string to filter by * @param bool $active Only list active badges * @return array of int */ public static function find_badges_by_course_relation($course, $search='', $active=false) { global $DB; if (is_int($course)) { $courseid = $course; } else if (is_object($course)) { $courseid = $course->id; } else { throw new \moodle_exception("\$course argument must be course id or course object", "local_treestudyplan"); } $search = trim($search); $conditions = ""; $basesqlparams = ['courseid' => $courseid]; if (strlen($search) > 0) { $conditions .= " AND ".$DB->sql_like('b.name', ':search'); $basesqlparams["search"] = "%$search%"; } if ($active) { [$insql, $inparams] = $DB->get_in_or_equal([BADGE_STATUS_ACTIVE, BADGE_STATUS_ACTIVE_LOCKED], SQL_PARAMS_NAMED); $conditions .= " AND b.status $insql"; $basesqlparams = array_merge($basesqlparams, $inparams); } [$ctypesql, $ctypeinparams] = $DB->get_in_or_equal([BADGE_CRITERIA_TYPE_COURSESET, BADGE_CRITERIA_TYPE_COURSE], SQL_PARAMS_NAMED); $sqlparams = array_merge($basesqlparams, $ctypeinparams); $sql = "SELECT DISTINCT b.id from {badge} b INNER JOIN {badge_criteria} crit ON b.id = crit.badgeid INNER JOIN {badge_criteria_param} p on p.critid = crit.id WHERE p.value = :courseid AND crit.criteriatype $ctypesql $conditions"; $courserelids = $DB->get_fieldset_sql($sql, $sqlparams); [$ctypesql, $ctypeinparams] = $DB->get_in_or_equal([BADGE_CRITERIA_TYPE_COMPETENCY], SQL_PARAMS_NAMED); $sqlparams = array_merge($basesqlparams, $ctypeinparams); $sql = "SELECT DISTINCT b.id from {badge} b INNER JOIN {badge_criteria} crit ON b.id = crit.badgeid INNER JOIN {badge_criteria_param} p on p.critid = crit.id INNER JOIN {competency_coursecomp} cc on cc.competencyid = p.value WHERE cc.courseid = :courseid AND crit.criteriatype $ctypesql $conditions"; $competencyrelids = $DB->get_fieldset_sql($sql, $sqlparams); $badgeids = []; foreach ([$courserelids, $competencyrelids] as $list) { foreach ($list as $id) { $badgeids[] = $id; } } return $badgeids; } /** * Find course badges * @param int|stdClass $course The course to relate to or it's id * @param string $search An optional search string to filter by * @param bool $active Only list active badges * @return array of int */ public static function find_course_badges($course, $search='', $active=false) { global $DB; if (is_int($course)) { $courseid = $course; } else if (is_object($course)) { $courseid = $course->id; } else { throw new \moodle_exception("\$course argument must be course id or course object", "local_treestudyplan"); } $search = trim($search); $conditions = ""; $basesqlparams = ['courseid' => $courseid]; if (strlen($search) > 0) { $conditions .= " AND ".$DB->sql_like('b.name', ':search'); $basesqlparams["search"] = "%$search%"; } if ($active) { [$insql, $inparams] = $DB->get_in_or_equal([BADGE_STATUS_ACTIVE, BADGE_STATUS_ACTIVE_LOCKED]); $conditions .= " AND b.status $insql"; $basesqlparams = array_merge($basesqlparams, $inparams); } // Get all course badges for this course. $badgesids = []; $sql = "SELECT DISTINCT b.id from {badge} b WHERE b.courseid = :courseid AND $conditions"; $badgesids = $DB->get_fieldset_sql($sql, $basesqlparams); return $badgesids; } /** * [Description for find_page_related_badges] * * @param studyplanpage $page * @param mixed $search="" * @param mixed $active=false * @param mixed $includecoursebadges=true * * @return array of badgeinfo * */ public static function find_page_related_badges(studyplanpage $page, $search="", $active=false, $includecoursebadges=true) { $badgeids = []; foreach (studyline::find_page_children($page) as $line) { foreach (studyitem::find_studyline_children($line) as $item) { if ($item->type() == studyitem::COURSE && $item->courseid()) { $courseid = $item->courseid(); $relatedbadges = self::find_badges_by_course_relation($courseid, $search, $active); foreach ($relatedbadges as $id) { $badgeids[] = $id; } if ($includecoursebadges) { $coursebadges = self::find_course_badges($courseid, $search, $active); foreach ($coursebadges as $id) { $badgeids[] = $id; } } } } } $badges = []; foreach (array_unique($badgeids) as $id) { $badges[] = new self(new badge($id)); } usort($badges, function($a, $b) { return $a->name() <=> $b->name(); }); return $badges; } /** * Search site badges * @param string $search An optional search string to filter by * @param bool $active Only list active badges * @return array of badge */ public static function search_site_badges($search='', $active=false) { global $DB; $search = trim($search); $conditions = "TRUE"; $basesqlparams = ['type' => \BADGE_TYPE_SITE]; if (strlen($search) > 0) { $conditions .= " AND ".$DB->sql_like('b.name', ':search'); $basesqlparams["search"] = "%$search%"; } if ($active) { [$insql, $inparams] = $DB->get_in_or_equal([BADGE_STATUS_ACTIVE, BADGE_STATUS_ACTIVE_LOCKED]); $conditions .= " AND b.status $insql"; $basesqlparams = array_merge($basesqlparams, $inparams); } // Get all course badges for this course. $sql = "SELECT DISTINCT b.id from {badge} b WHERE $conditions"; $badgeids = $DB->get_fieldset_sql($sql, $basesqlparams); $badges = []; foreach (array_unique($badgeids) as $id) { $badges[] = new self(new badge($id)); } usort($badges, function($a, $b) { return $a->name() <=> $b->name(); }); return $badges; } }