959 lines
41 KiB
PHP
959 lines
41 KiB
PHP
<?php
|
|
// 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/>.
|
|
/**
|
|
* 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 <plugin>_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
|
|
<plugin>_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;
|
|
}
|
|
|
|
}
|