Commit | Line | Data |
---|---|---|
27806552 YB |
1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * Contains classes, functions and constants used in badges. | |
19 | * | |
20 | * @package core | |
21 | * @subpackage badges | |
22 | * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
24 | * @author Yuliya Bozhko <yuliya.bozhko@totaralms.com> | |
25 | */ | |
26 | ||
27 | defined('MOODLE_INTERNAL') || die(); | |
28 | ||
29 | /* Include required award criteria library. */ | |
30 | require_once($CFG->dirroot . '/badges/criteria/award_criteria.php'); | |
31 | ||
32 | /* | |
33 | * Number of records per page. | |
34 | */ | |
35 | define('BADGE_PERPAGE', 50); | |
36 | ||
37 | /* | |
38 | * Badge award criteria aggregation method. | |
39 | */ | |
40 | define('BADGE_CRITERIA_AGGREGATION_ALL', 1); | |
41 | ||
42 | /* | |
43 | * Badge award criteria aggregation method. | |
44 | */ | |
45 | define('BADGE_CRITERIA_AGGREGATION_ANY', 2); | |
46 | ||
47 | /* | |
48 | * Inactive badge means that this badge cannot be earned and has not been awarded | |
49 | * yet. Its award criteria can be changed. | |
50 | */ | |
51 | define('BADGE_STATUS_INACTIVE', 0); | |
52 | ||
53 | /* | |
54 | * Active badge means that this badge can we earned, but it has not been awarded | |
55 | * yet. Can be deactivated for the purpose of changing its criteria. | |
56 | */ | |
57 | define('BADGE_STATUS_ACTIVE', 1); | |
58 | ||
59 | /* | |
60 | * Inactive badge can no longer be earned, but it has been awarded in the past and | |
61 | * therefore its criteria cannot be changed. | |
62 | */ | |
63 | define('BADGE_STATUS_INACTIVE_LOCKED', 2); | |
64 | ||
65 | /* | |
66 | * Active badge means that it can be earned and has already been awarded to users. | |
67 | * Its criteria cannot be changed any more. | |
68 | */ | |
69 | define('BADGE_STATUS_ACTIVE_LOCKED', 3); | |
70 | ||
71 | /* | |
72 | * Archived badge is considered deleted and can no longer be earned and is not | |
73 | * displayed in the list of all badges. | |
74 | */ | |
75 | define('BADGE_STATUS_ARCHIVED', 4); | |
76 | ||
77 | /* | |
78 | * Badge type for site badges. | |
79 | */ | |
80 | define('BADGE_TYPE_SITE', 1); | |
81 | ||
82 | /* | |
83 | * Badge type for course badges. | |
84 | */ | |
85 | define('BADGE_TYPE_COURSE', 2); | |
86 | ||
87 | /* | |
88 | * Badge messaging schedule options. | |
89 | */ | |
90 | define('BADGE_MESSAGE_NEVER', 0); | |
91 | define('BADGE_MESSAGE_ALWAYS', 1); | |
92 | define('BADGE_MESSAGE_DAILY', 2); | |
93 | define('BADGE_MESSAGE_WEEKLY', 3); | |
94 | define('BADGE_MESSAGE_MONTHLY', 4); | |
95 | ||
343534a8 | 96 | /* |
aae219ac | 97 | * URL of backpack. Custom ones can be added. |
343534a8 | 98 | */ |
aae219ac DW |
99 | define('BADGRIO_BACKPACKAPIURL', 'https://api.badgr.io/v2'); |
100 | define('BADGRIO_BACKPACKWEBURL', 'https://badgr.io'); | |
343534a8 | 101 | |
7444ba74 DW |
102 | /* |
103 | * @deprecated since 3.7. Use the urls in the badge_external_backpack table instead. | |
104 | */ | |
105 | define('BADGE_BACKPACKURL', 'https://backpack.openbadges.org'); | |
106 | ||
f958f5c1 SA |
107 | /* |
108 | * @deprecated since 3.9 (MDL-66357). | |
109 | */ | |
110 | define('BADGE_BACKPACKAPIURL', 'https://backpack.openbadges.org'); | |
111 | define('BADGE_BACKPACKWEBURL', 'https://backpack.openbadges.org'); | |
112 | ||
5a1ea828 SA |
113 | /* |
114 | * Open Badges specifications. | |
115 | */ | |
116 | define('OPEN_BADGES_V1', 1); | |
117 | define('OPEN_BADGES_V2', 2); | |
15a00bea | 118 | define('OPEN_BADGES_V2P1', 2.1); |
5a1ea828 | 119 | |
d363a5c2 TT |
120 | /* |
121 | * Only use for Open Badges 2.0 specification | |
122 | */ | |
123 | define('OPEN_BADGES_V2_CONTEXT', 'https://w3id.org/openbadges/v2'); | |
124 | define('OPEN_BADGES_V2_TYPE_ASSERTION', 'Assertion'); | |
125 | define('OPEN_BADGES_V2_TYPE_BADGE', 'BadgeClass'); | |
126 | define('OPEN_BADGES_V2_TYPE_ISSUER', 'Issuer'); | |
127 | define('OPEN_BADGES_V2_TYPE_ENDORSEMENT', 'Endorsement'); | |
128 | define('OPEN_BADGES_V2_TYPE_AUTHOR', 'Author'); | |
129 | ||
aae219ac DW |
130 | // Global badge class has been moved to the component namespace. |
131 | class_alias('\core_badges\badge', 'badge'); | |
27806552 YB |
132 | |
133 | /** | |
134 | * Sends notifications to users about awarded badges. | |
135 | * | |
136 | * @param badge $badge Badge that was issued | |
137 | * @param int $userid Recipient ID | |
138 | * @param string $issued Unique hash of an issued badge | |
139 | * @param string $filepathhash File path hash of an issued badge for attachments | |
140 | */ | |
141 | function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash) { | |
142 | global $CFG, $DB; | |
143 | ||
144 | $admin = get_admin(); | |
145 | $userfrom = new stdClass(); | |
146 | $userfrom->id = $admin->id; | |
147 | $userfrom->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : $admin->email; | |
a327f25e AG |
148 | foreach (get_all_user_name_fields() as $addname) { |
149 | $userfrom->$addname = !empty($CFG->badges_defaultissuername) ? '' : $admin->$addname; | |
150 | } | |
27806552 | 151 | $userfrom->firstname = !empty($CFG->badges_defaultissuername) ? $CFG->badges_defaultissuername : $admin->firstname; |
27806552 YB |
152 | $userfrom->maildisplay = true; |
153 | ||
154 | $issuedlink = html_writer::link(new moodle_url('/badges/badge.php', array('hash' => $issued)), $badge->name); | |
155 | $userto = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); | |
156 | ||
157 | $params = new stdClass(); | |
158 | $params->badgename = $badge->name; | |
159 | $params->username = fullname($userto); | |
160 | $params->badgelink = $issuedlink; | |
161 | $message = badge_message_from_template($badge->message, $params); | |
1f258cd8 | 162 | $plaintext = html_to_text($message); |
27806552 | 163 | |
f5d17c14 | 164 | // Notify recipient. |
cc350fd9 | 165 | $eventdata = new \core\message\message(); |
f593e827 | 166 | $eventdata->courseid = is_null($badge->courseid) ? SITEID : $badge->courseid; // Profile/site come with no courseid. |
f5d17c14 YB |
167 | $eventdata->component = 'moodle'; |
168 | $eventdata->name = 'badgerecipientnotice'; | |
169 | $eventdata->userfrom = $userfrom; | |
170 | $eventdata->userto = $userto; | |
171 | $eventdata->notification = 1; | |
172 | $eventdata->subject = $badge->messagesubject; | |
173 | $eventdata->fullmessage = $plaintext; | |
1f258cd8 | 174 | $eventdata->fullmessageformat = FORMAT_HTML; |
f5d17c14 | 175 | $eventdata->fullmessagehtml = $message; |
1f258cd8 | 176 | $eventdata->smallmessage = ''; |
36fa0ec9 JL |
177 | $eventdata->customdata = [ |
178 | 'notificationiconurl' => moodle_url::make_pluginfile_url( | |
179 | $badge->get_context()->id, 'badges', 'badgeimage', $badge->id, '/', 'f1')->out(), | |
180 | 'hash' => $issued, | |
181 | ]; | |
f5d17c14 YB |
182 | |
183 | // Attach badge image if possible. | |
184 | if (!empty($CFG->allowattachments) && $badge->attachment && is_string($filepathhash)) { | |
27806552 YB |
185 | $fs = get_file_storage(); |
186 | $file = $fs->get_file_by_hash($filepathhash); | |
f5d17c14 YB |
187 | $eventdata->attachment = $file; |
188 | $eventdata->attachname = str_replace(' ', '_', $badge->name) . ".png"; | |
189 | ||
190 | message_send($eventdata); | |
27806552 | 191 | } else { |
f5d17c14 | 192 | message_send($eventdata); |
27806552 YB |
193 | } |
194 | ||
195 | // Notify badge creator about the award if they receive notifications every time. | |
196 | if ($badge->notification == 1) { | |
f5d17c14 YB |
197 | $userfrom = core_user::get_noreply_user(); |
198 | $userfrom->maildisplay = true; | |
199 | ||
27806552 YB |
200 | $creator = $DB->get_record('user', array('id' => $badge->usercreated), '*', MUST_EXIST); |
201 | $a = new stdClass(); | |
202 | $a->user = fullname($userto); | |
203 | $a->link = $issuedlink; | |
204 | $creatormessage = get_string('creatorbody', 'badges', $a); | |
205 | $creatorsubject = get_string('creatorsubject', 'badges', $badge->name); | |
206 | ||
cc350fd9 AD |
207 | $eventdata = new \core\message\message(); |
208 | $eventdata->courseid = $badge->courseid; | |
27806552 | 209 | $eventdata->component = 'moodle'; |
f5d17c14 | 210 | $eventdata->name = 'badgecreatornotice'; |
27806552 YB |
211 | $eventdata->userfrom = $userfrom; |
212 | $eventdata->userto = $creator; | |
213 | $eventdata->notification = 1; | |
214 | $eventdata->subject = $creatorsubject; | |
1f258cd8 YB |
215 | $eventdata->fullmessage = html_to_text($creatormessage); |
216 | $eventdata->fullmessageformat = FORMAT_HTML; | |
f5d17c14 | 217 | $eventdata->fullmessagehtml = $creatormessage; |
1f258cd8 | 218 | $eventdata->smallmessage = ''; |
36fa0ec9 JL |
219 | $eventdata->customdata = [ |
220 | 'notificationiconurl' => moodle_url::make_pluginfile_url( | |
221 | $badge->get_context()->id, 'badges', 'badgeimage', $badge->id, '/', 'f1')->out(), | |
222 | 'hash' => $issued, | |
223 | ]; | |
27806552 YB |
224 | |
225 | message_send($eventdata); | |
226 | $DB->set_field('badge_issued', 'issuernotified', time(), array('badgeid' => $badge->id, 'userid' => $userid)); | |
227 | } | |
228 | } | |
229 | ||
230 | /** | |
231 | * Caclulates date for the next message digest to badge creators. | |
232 | * | |
233 | * @param in $schedule Type of message schedule BADGE_MESSAGE_DAILY|BADGE_MESSAGE_WEEKLY|BADGE_MESSAGE_MONTHLY. | |
234 | * @return int Timestamp for next cron | |
235 | */ | |
236 | function badges_calculate_message_schedule($schedule) { | |
237 | $nextcron = 0; | |
238 | ||
239 | switch ($schedule) { | |
240 | case BADGE_MESSAGE_DAILY: | |
b0213770 DW |
241 | $tomorrow = new DateTime("1 day", core_date::get_server_timezone_object()); |
242 | $nextcron = $tomorrow->getTimestamp(); | |
27806552 YB |
243 | break; |
244 | case BADGE_MESSAGE_WEEKLY: | |
b0213770 DW |
245 | $nextweek = new DateTime("1 week", core_date::get_server_timezone_object()); |
246 | $nextcron = $nextweek->getTimestamp(); | |
27806552 YB |
247 | break; |
248 | case BADGE_MESSAGE_MONTHLY: | |
b0213770 DW |
249 | $nextmonth = new DateTime("1 month", core_date::get_server_timezone_object()); |
250 | $nextcron = $nextmonth->getTimestamp(); | |
27806552 YB |
251 | break; |
252 | } | |
253 | ||
254 | return $nextcron; | |
255 | } | |
256 | ||
257 | /** | |
258 | * Replaces variables in a message template and returns text ready to be emailed to a user. | |
259 | * | |
260 | * @param string $message Message body. | |
261 | * @return string Message with replaced values | |
262 | */ | |
263 | function badge_message_from_template($message, $params) { | |
264 | $msg = $message; | |
265 | foreach ($params as $key => $value) { | |
266 | $msg = str_replace("%$key%", $value, $msg); | |
267 | } | |
268 | ||
269 | return $msg; | |
270 | } | |
271 | ||
272 | /** | |
273 | * Get all badges. | |
274 | * | |
275 | * @param int Type of badges to return | |
276 | * @param int Course ID for course badges | |
277 | * @param string $sort An SQL field to sort by | |
278 | * @param string $dir The sort direction ASC|DESC | |
279 | * @param int $page The page or records to return | |
280 | * @param int $perpage The number of records to return per page | |
281 | * @param int $user User specific search | |
282 | * @return array $badge Array of records matching criteria | |
283 | */ | |
284 | function badges_get_badges($type, $courseid = 0, $sort = '', $dir = '', $page = 0, $perpage = BADGE_PERPAGE, $user = 0) { | |
285 | global $DB; | |
286 | $records = array(); | |
287 | $params = array(); | |
288 | $where = "b.status != :deleted AND b.type = :type "; | |
289 | $params['deleted'] = BADGE_STATUS_ARCHIVED; | |
290 | ||
291 | $userfields = array('b.id, b.name, b.status'); | |
292 | $usersql = ""; | |
293 | if ($user != 0) { | |
294 | $userfields[] = 'bi.dateissued'; | |
295 | $userfields[] = 'bi.uniquehash'; | |
296 | $usersql = " LEFT JOIN {badge_issued} bi ON b.id = bi.badgeid AND bi.userid = :userid "; | |
297 | $params['userid'] = $user; | |
298 | $where .= " AND (b.status = 1 OR b.status = 3) "; | |
299 | } | |
300 | $fields = implode(', ', $userfields); | |
301 | ||
302 | if ($courseid != 0 ) { | |
303 | $where .= "AND b.courseid = :courseid "; | |
304 | $params['courseid'] = $courseid; | |
305 | } | |
306 | ||
307 | $sorting = (($sort != '' && $dir != '') ? 'ORDER BY ' . $sort . ' ' . $dir : ''); | |
308 | $params['type'] = $type; | |
309 | ||
310 | $sql = "SELECT $fields FROM {badge} b $usersql WHERE $where $sorting"; | |
311 | $records = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage); | |
312 | ||
313 | $badges = array(); | |
314 | foreach ($records as $r) { | |
315 | $badge = new badge($r->id); | |
316 | $badges[$r->id] = $badge; | |
317 | if ($user != 0) { | |
318 | $badges[$r->id]->dateissued = $r->dateissued; | |
319 | $badges[$r->id]->uniquehash = $r->uniquehash; | |
320 | } else { | |
7f964cfd YB |
321 | $badges[$r->id]->awards = $DB->count_records_sql('SELECT COUNT(b.userid) |
322 | FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id | |
323 | WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $badge->id)); | |
27806552 YB |
324 | $badges[$r->id]->statstring = $badge->get_status_name(); |
325 | } | |
326 | } | |
327 | return $badges; | |
328 | } | |
329 | ||
330 | /** | |
331 | * Get badges for a specific user. | |
332 | * | |
333 | * @param int $userid User ID | |
334 | * @param int $courseid Badges earned by a user in a specific course | |
335 | * @param int $page The page or records to return | |
336 | * @param int $perpage The number of records to return per page | |
337 | * @param string $search A simple string to search for | |
338 | * @param bool $onlypublic Return only public badges | |
339 | * @return array of badges ordered by decreasing date of issue | |
340 | */ | |
341 | function badges_get_user_badges($userid, $courseid = 0, $page = 0, $perpage = 0, $search = '', $onlypublic = false) { | |
b4846b88 | 342 | global $CFG, $DB; |
27806552 | 343 | |
d237385e SH |
344 | $params = array( |
345 | 'userid' => $userid | |
346 | ); | |
27806552 YB |
347 | $sql = 'SELECT |
348 | bi.uniquehash, | |
349 | bi.dateissued, | |
350 | bi.dateexpire, | |
351 | bi.id as issuedid, | |
352 | bi.visible, | |
353 | u.email, | |
354 | b.* | |
355 | FROM | |
356 | {badge} b, | |
357 | {badge_issued} bi, | |
358 | {user} u | |
359 | WHERE b.id = bi.badgeid | |
360 | AND u.id = bi.userid | |
d237385e | 361 | AND bi.userid = :userid'; |
27806552 YB |
362 | |
363 | if (!empty($search)) { | |
d237385e SH |
364 | $sql .= ' AND (' . $DB->sql_like('b.name', ':search', false) . ') '; |
365 | $params['search'] = '%'.$DB->sql_like_escape($search).'%'; | |
27806552 YB |
366 | } |
367 | if ($onlypublic) { | |
368 | $sql .= ' AND (bi.visible = 1) '; | |
369 | } | |
370 | ||
b4846b88 AO |
371 | if (empty($CFG->badges_allowcoursebadges)) { |
372 | $sql .= ' AND b.courseid IS NULL'; | |
373 | } else if ($courseid != 0) { | |
d237385e SH |
374 | $sql .= ' AND (b.courseid = :courseid) '; |
375 | $params['courseid'] = $courseid; | |
27806552 YB |
376 | } |
377 | $sql .= ' ORDER BY bi.dateissued DESC'; | |
378 | $badges = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage); | |
379 | ||
380 | return $badges; | |
381 | } | |
382 | ||
27806552 YB |
383 | /** |
384 | * Extends the course administration navigation with the Badges page | |
385 | * | |
386 | * @param navigation_node $coursenode | |
387 | * @param object $course | |
388 | */ | |
389 | function badges_add_course_navigation(navigation_node $coursenode, stdClass $course) { | |
390 | global $CFG, $SITE; | |
391 | ||
392 | $coursecontext = context_course::instance($course->id); | |
393 | $isfrontpage = (!$coursecontext || $course->id == $SITE->id); | |
a72c2cd6 YB |
394 | $canmanage = has_any_capability(array('moodle/badges:viewawarded', |
395 | 'moodle/badges:createbadge', | |
396 | 'moodle/badges:awardbadge', | |
397 | 'moodle/badges:configurecriteria', | |
398 | 'moodle/badges:configuremessages', | |
399 | 'moodle/badges:configuredetails', | |
400 | 'moodle/badges:deletebadge'), $coursecontext); | |
27806552 | 401 | |
a72c2cd6 YB |
402 | if (!empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) && !$isfrontpage && $canmanage) { |
403 | $coursenode->add(get_string('coursebadges', 'badges'), null, | |
404 | navigation_node::TYPE_CONTAINER, null, 'coursebadges', | |
405 | new pix_icon('i/badge', get_string('coursebadges', 'badges'))); | |
27806552 | 406 | |
a72c2cd6 | 407 | $url = new moodle_url('/badges/index.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id)); |
27806552 | 408 | |
a72c2cd6 YB |
409 | $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url, |
410 | navigation_node::TYPE_SETTING, null, 'coursebadges'); | |
19a9f2ea | 411 | |
a72c2cd6 YB |
412 | if (has_capability('moodle/badges:createbadge', $coursecontext)) { |
413 | $url = new moodle_url('/badges/newbadge.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id)); | |
19a9f2ea | 414 | |
a72c2cd6 YB |
415 | $coursenode->get('coursebadges')->add(get_string('newbadge', 'badges'), $url, |
416 | navigation_node::TYPE_SETTING, null, 'newbadge'); | |
27806552 YB |
417 | } |
418 | } | |
419 | } | |
420 | ||
27806552 YB |
421 | /** |
422 | * Triggered when badge is manually awarded. | |
423 | * | |
424 | * @param object $data | |
425 | * @return boolean | |
426 | */ | |
427 | function badges_award_handle_manual_criteria_review(stdClass $data) { | |
428 | $criteria = $data->crit; | |
429 | $userid = $data->userid; | |
430 | $badge = new badge($criteria->badgeid); | |
431 | ||
432 | if (!$badge->is_active() || $badge->is_issued($userid)) { | |
433 | return true; | |
434 | } | |
435 | ||
436 | if ($criteria->review($userid)) { | |
437 | $criteria->mark_complete($userid); | |
438 | ||
439 | if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) { | |
440 | $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid); | |
441 | $badge->issue($userid); | |
442 | } | |
443 | } | |
444 | ||
445 | return true; | |
446 | } | |
447 | ||
448 | /** | |
449 | * Process badge image from form data | |
450 | * | |
451 | * @param badge $badge Badge object | |
452 | * @param string $iconfile Original file | |
453 | */ | |
454 | function badges_process_badge_image(badge $badge, $iconfile) { | |
455 | global $CFG, $USER; | |
456 | require_once($CFG->libdir. '/gdlib.php'); | |
457 | ||
458 | if (!empty($CFG->gdversion)) { | |
cda2a827 | 459 | process_new_icon($badge->get_context(), 'badges', 'badgeimage', $badge->id, $iconfile, true); |
27806552 YB |
460 | @unlink($iconfile); |
461 | ||
462 | // Clean up file draft area after badge image has been saved. | |
463 | $context = context_user::instance($USER->id, MUST_EXIST); | |
464 | $fs = get_file_storage(); | |
465 | $fs->delete_area_files($context->id, 'user', 'draft'); | |
466 | } | |
467 | } | |
468 | ||
469 | /** | |
470 | * Print badge image. | |
471 | * | |
472 | * @param badge $badge Badge object | |
473 | * @param stdClass $context | |
474 | * @param string $size | |
475 | */ | |
476 | function print_badge_image(badge $badge, stdClass $context, $size = 'small') { | |
477 | $fsize = ($size == 'small') ? 'f2' : 'f1'; | |
478 | ||
479 | $imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', $fsize, false); | |
480 | // Appending a random parameter to image link to forse browser reload the image. | |
b143259e YB |
481 | $imageurl->param('refresh', rand(1, 10000)); |
482 | $attributes = array('src' => $imageurl, 'alt' => s($badge->name), 'class' => 'activatebadge'); | |
27806552 YB |
483 | |
484 | return html_writer::empty_tag('img', $attributes); | |
485 | } | |
486 | ||
487 | /** | |
488 | * Bake issued badge. | |
489 | * | |
490 | * @param string $hash Unique hash of an issued badge. | |
491 | * @param int $badgeid ID of the original badge. | |
492 | * @param int $userid ID of badge recipient (optional). | |
493 | * @param boolean $pathhash Return file pathhash instead of image url (optional). | |
494 | * @return string|url Returns either new file path hash or new file URL | |
495 | */ | |
496 | function badges_bake($hash, $badgeid, $userid = 0, $pathhash = false) { | |
497 | global $CFG, $USER; | |
1fcf0ca8 | 498 | require_once(__DIR__ . '/../badges/lib/bakerlib.php'); |
27806552 YB |
499 | |
500 | $badge = new badge($badgeid); | |
501 | $badge_context = $badge->get_context(); | |
502 | $userid = ($userid) ? $userid : $USER->id; | |
503 | $user_context = context_user::instance($userid); | |
504 | ||
505 | $fs = get_file_storage(); | |
506 | if (!$fs->file_exists($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash . '.png')) { | |
49b464f8 | 507 | if ($file = $fs->get_file($badge_context->id, 'badges', 'badgeimage', $badge->id, '/', 'f3.png')) { |
27806552 YB |
508 | $contents = $file->get_content(); |
509 | ||
510 | $filehandler = new PNG_MetaDataHandler($contents); | |
f8d9b1eb SA |
511 | // For now, the site backpack OB version will be used as default. |
512 | $obversion = badges_open_badges_backpack_api(); | |
513 | $assertion = new core_badges_assertion($hash, $obversion); | |
514 | $assertionjson = json_encode($assertion->get_badge_assertion()); | |
515 | if ($filehandler->check_chunks("iTXt", "openbadges")) { | |
516 | // Add assertion URL iTXt chunk. | |
517 | $newcontents = $filehandler->add_chunks("iTXt", "openbadges", $assertionjson); | |
27806552 YB |
518 | $fileinfo = array( |
519 | 'contextid' => $user_context->id, | |
520 | 'component' => 'badges', | |
521 | 'filearea' => 'userbadge', | |
522 | 'itemid' => $badge->id, | |
523 | 'filepath' => '/', | |
524 | 'filename' => $hash . '.png', | |
525 | ); | |
526 | ||
527 | // Create a file with added contents. | |
528 | $newfile = $fs->create_file_from_string($fileinfo, $newcontents); | |
529 | if ($pathhash) { | |
530 | return $newfile->get_pathnamehash(); | |
531 | } | |
532 | } | |
533 | } else { | |
be2b37cf Y |
534 | debugging('Error baking badge image!', DEBUG_DEVELOPER); |
535 | return; | |
27806552 YB |
536 | } |
537 | } | |
538 | ||
f5d17c14 YB |
539 | // If file exists and we just need its path hash, return it. |
540 | if ($pathhash) { | |
541 | $file = $fs->get_file($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash . '.png'); | |
542 | return $file->get_pathnamehash(); | |
543 | } | |
544 | ||
27806552 YB |
545 | $fileurl = moodle_url::make_pluginfile_url($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash, true); |
546 | return $fileurl; | |
547 | } | |
548 | ||
549 | /** | |
e2805314 | 550 | * Returns external backpack settings and badges from this backpack. |
27806552 | 551 | * |
2d3c0fae YB |
552 | * This function first checks if badges for the user are cached and |
553 | * tries to retrieve them from the cache. Otherwise, badges are obtained | |
554 | * through curl request to the backpack. | |
555 | * | |
27806552 | 556 | * @param int $userid Backpack user ID. |
2d3c0fae | 557 | * @param boolean $refresh Refresh badges collection in cache. |
27806552 YB |
558 | * @return null|object Returns null is there is no backpack or object with backpack settings. |
559 | */ | |
2d3c0fae | 560 | function get_backpack_settings($userid, $refresh = false) { |
27806552 | 561 | global $DB; |
27806552 | 562 | |
2d3c0fae YB |
563 | // Try to get badges from cache first. |
564 | $badgescache = cache::make('core', 'externalbadges'); | |
565 | $out = $badgescache->get($userid); | |
566 | if ($out !== false && !$refresh) { | |
567 | return $out; | |
568 | } | |
569 | // Get badges through curl request to the backpack. | |
e2805314 | 570 | $record = $DB->get_record('badge_backpack', array('userid' => $userid)); |
27806552 | 571 | if ($record) { |
7444ba74 | 572 | $sitebackpack = badges_get_site_backpack($record->externalbackpackid); |
aae219ac | 573 | $backpack = new \core_badges\backpack_api($sitebackpack, $record); |
27806552 | 574 | $out = new stdClass(); |
aae219ac | 575 | $out->backpackid = $sitebackpack->id; |
e2805314 YB |
576 | |
577 | if ($collections = $DB->get_records('badge_external', array('backpackid' => $record->id))) { | |
578 | $out->totalcollections = count($collections); | |
579 | $out->totalbadges = 0; | |
580 | $out->badges = array(); | |
581 | foreach ($collections as $collection) { | |
aae219ac DW |
582 | $badges = $backpack->get_badges($collection, true); |
583 | if (!empty($badges)) { | |
584 | $out->badges = array_merge($out->badges, $badges); | |
585 | $out->totalbadges += count($badges); | |
e2805314 YB |
586 | } else { |
587 | $out->badges = array_merge($out->badges, array()); | |
588 | } | |
589 | } | |
590 | } else { | |
591 | $out->totalbadges = 0; | |
592 | $out->totalcollections = 0; | |
593 | } | |
594 | ||
2d3c0fae | 595 | $badgescache->set($userid, $out); |
27806552 YB |
596 | return $out; |
597 | } | |
598 | ||
599 | return null; | |
600 | } | |
601 | ||
602 | /** | |
603 | * Download all user badges in zip archive. | |
604 | * | |
605 | * @param int $userid ID of badge owner. | |
606 | */ | |
607 | function badges_download($userid) { | |
608 | global $CFG, $DB; | |
609 | $context = context_user::instance($userid); | |
610 | $records = $DB->get_records('badge_issued', array('userid' => $userid)); | |
611 | ||
612 | // Get list of files to download. | |
613 | $fs = get_file_storage(); | |
614 | $filelist = array(); | |
615 | foreach ($records as $issued) { | |
616 | $badge = new badge($issued->badgeid); | |
617 | // Need to make image name user-readable and unique using filename safe characters. | |
618 | $name = $badge->name . ' ' . userdate($issued->dateissued, '%d %b %Y') . ' ' . hash('crc32', $badge->id); | |
619 | $name = str_replace(' ', '_', $name); | |
d77a6026 | 620 | $name = clean_param($name, PARAM_FILE); |
27806552 YB |
621 | if ($file = $fs->get_file($context->id, 'badges', 'userbadge', $issued->badgeid, '/', $issued->uniquehash . '.png')) { |
622 | $filelist[$name . '.png'] = $file; | |
623 | } | |
624 | } | |
625 | ||
626 | // Zip files and sent them to a user. | |
627 | $tempzip = tempnam($CFG->tempdir.'/', 'mybadges'); | |
628 | $zipper = new zip_packer(); | |
629 | if ($zipper->archive_to_pathname($filelist, $tempzip)) { | |
630 | send_temp_file($tempzip, 'badges.zip'); | |
631 | } else { | |
62fef3cf YB |
632 | debugging("Problems with archiving the files.", DEBUG_DEVELOPER); |
633 | die; | |
27806552 YB |
634 | } |
635 | } | |
636 | ||
27806552 YB |
637 | /** |
638 | * Checks if badges can be pushed to external backpack. | |
639 | * | |
640 | * @return string Code of backpack accessibility status. | |
641 | */ | |
642 | function badges_check_backpack_accessibility() { | |
389aad6e AN |
643 | if (defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING) { |
644 | // For behat sites, do not poll the remote badge site. | |
645 | // Behat sites should not be available, but we should pretend as though they are. | |
646 | return 'available'; | |
647 | } | |
648 | ||
aae219ac DW |
649 | if (badges_open_badges_backpack_api() == OPEN_BADGES_V2) { |
650 | return 'available'; | |
651 | } | |
652 | ||
27806552 YB |
653 | global $CFG; |
654 | include_once $CFG->libdir . '/filelib.php'; | |
655 | ||
656 | // Using fake assertion url to check whether backpack can access the web site. | |
657 | $fakeassertion = new moodle_url('/badges/assertion.php', array('b' => 'abcd1234567890')); | |
658 | ||
343534a8 | 659 | // Curl request to backpack baker. |
27806552 YB |
660 | $curl = new curl(); |
661 | $options = array( | |
662 | 'FRESH_CONNECT' => true, | |
663 | 'RETURNTRANSFER' => true, | |
27806552 | 664 | 'HEADER' => 0, |
dd4a197e | 665 | 'CONNECTTIMEOUT' => 2, |
27806552 | 666 | ); |
7444ba74 | 667 | // BADGE_BACKPACKURL and the "baker" API is deprecated and should never be used in future. |
4d0ebdeb | 668 | $location = BADGE_BACKPACKURL . '/baker'; |
27806552 YB |
669 | $out = $curl->get($location, array('assertion' => $fakeassertion->out(false)), $options); |
670 | ||
671 | $data = json_decode($out); | |
672 | if (!empty($curl->error)) { | |
673 | return 'curl-request-timeout'; | |
674 | } else { | |
675 | if (isset($data->code) && $data->code == 'http-unreachable') { | |
676 | return 'http-unreachable'; | |
677 | } else { | |
678 | return 'available'; | |
679 | } | |
680 | } | |
681 | ||
682 | return false; | |
683 | } | |
e2805314 YB |
684 | |
685 | /** | |
686 | * Checks if user has external backpack connected. | |
687 | * | |
688 | * @param int $userid ID of a user. | |
689 | * @return bool True|False whether backpack connection exists. | |
690 | */ | |
691 | function badges_user_has_backpack($userid) { | |
692 | global $DB; | |
693 | return $DB->record_exists('badge_backpack', array('userid' => $userid)); | |
694 | } | |
7deff81f YB |
695 | |
696 | /** | |
697 | * Handles what happens to the course badges when a course is deleted. | |
698 | * | |
699 | * @param int $courseid course ID. | |
700 | * @return void. | |
701 | */ | |
702 | function badges_handle_course_deletion($courseid) { | |
703 | global $CFG, $DB; | |
704 | include_once $CFG->libdir . '/filelib.php'; | |
705 | ||
706 | $systemcontext = context_system::instance(); | |
707 | $coursecontext = context_course::instance($courseid); | |
708 | $fs = get_file_storage(); | |
709 | ||
710 | // Move badges images to the system context. | |
711 | $fs->move_area_files_to_new_context($coursecontext->id, $systemcontext->id, 'badges', 'badgeimage'); | |
712 | ||
713 | // Get all course badges. | |
714 | $badges = $DB->get_records('badge', array('type' => BADGE_TYPE_COURSE, 'courseid' => $courseid)); | |
715 | foreach ($badges as $badge) { | |
716 | // Archive badges in this course. | |
717 | $toupdate = new stdClass(); | |
718 | $toupdate->id = $badge->id; | |
719 | $toupdate->type = BADGE_TYPE_SITE; | |
720 | $toupdate->courseid = null; | |
721 | $toupdate->status = BADGE_STATUS_ARCHIVED; | |
722 | $DB->update_record('badge', $toupdate); | |
723 | } | |
724 | } | |
2c910861 YB |
725 | |
726 | /** | |
727 | * Loads JS files required for backpack support. | |
728 | * | |
729 | * @uses $CFG, $PAGE | |
730 | * @return void | |
731 | */ | |
732 | function badges_setup_backpack_js() { | |
733 | global $CFG, $PAGE; | |
734 | if (!empty($CFG->badges_allowexternalbackpack)) { | |
aae219ac DW |
735 | if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) { |
736 | $PAGE->requires->string_for_js('error:backpackproblem', 'badges'); | |
7444ba74 | 737 | // The issuer.js API is deprecated and should not be used in future. |
aae219ac | 738 | $PAGE->requires->js(new moodle_url(BADGE_BACKPACKURL . '/issuer.js'), true); |
7444ba74 | 739 | // The backpack.js file is deprecated and should not be used in future. |
aae219ac DW |
740 | $PAGE->requires->js('/badges/backpack.js', true); |
741 | } | |
2c910861 YB |
742 | } |
743 | } | |
43f1c8e2 | 744 | |
aae219ac DW |
745 | /** |
746 | * No js files are required for backpack support. | |
747 | * This only exists to directly support the custom V1 backpack api. | |
748 | * | |
749 | * @param boolean $checksite Call check site function. | |
750 | * @return void | |
751 | */ | |
752 | function badges_local_backpack_js($checksite = false) { | |
753 | global $CFG, $PAGE; | |
754 | if (!empty($CFG->badges_allowexternalbackpack)) { | |
755 | if (badges_open_badges_backpack_api() == OPEN_BADGES_V1) { | |
756 | $PAGE->requires->js('/badges/backpack.js', true); | |
757 | if ($checksite) { | |
758 | $PAGE->requires->js_init_call('check_site_access', null, false); | |
759 | } | |
760 | } | |
761 | } | |
762 | } | |
763 | ||
764 | /** | |
1c4620bc | 765 | * Create the site backpack with this data. |
aae219ac DW |
766 | * |
767 | * @param stdClass $data The new backpack data. | |
768 | * @return boolean | |
769 | */ | |
770 | function badges_create_site_backpack($data) { | |
771 | global $DB; | |
772 | $context = context_system::instance(); | |
773 | require_capability('moodle/badges:manageglobalsettings', $context); | |
774 | ||
775 | $count = $DB->count_records('badge_external_backpack'); | |
1c4620bc P |
776 | $data->sortorder = $count; |
777 | return badges_save_external_backpack($data); | |
aae219ac DW |
778 | } |
779 | ||
780 | /** | |
781 | * Update the backpack with this id. | |
782 | * | |
783 | * @param integer $id The backpack to edit | |
784 | * @param stdClass $data The new backpack data. | |
785 | * @return boolean | |
786 | */ | |
787 | function badges_update_site_backpack($id, $data) { | |
788 | global $DB; | |
789 | $context = context_system::instance(); | |
790 | require_capability('moodle/badges:manageglobalsettings', $context); | |
791 | ||
792 | if ($backpack = badges_get_site_backpack($id)) { | |
1c4620bc P |
793 | $data->id = $id; |
794 | return badges_save_external_backpack($data); | |
aae219ac DW |
795 | } |
796 | return false; | |
797 | } | |
798 | ||
a80541ea SA |
799 | |
800 | /** | |
801 | * Delete the backpack with this id. | |
802 | * | |
803 | * @param integer $id The backpack to delete. | |
804 | * @return boolean | |
805 | */ | |
806 | function badges_delete_site_backpack($id) { | |
807 | global $DB, $CFG; | |
808 | ||
809 | $context = context_system::instance(); | |
810 | require_capability('moodle/badges:manageglobalsettings', $context); | |
811 | ||
812 | // Only remove site backpack if it's not the default one. | |
813 | if ($CFG->badges_site_backpack != $id && $DB->record_exists('badge_external_backpack', ['id' => $id])) { | |
814 | $transaction = $DB->start_delegated_transaction(); | |
815 | ||
816 | // Remove connections for users to this backpack. | |
817 | $sql = "SELECT DISTINCT bb.id | |
818 | FROM {badge_backpack} bb | |
819 | WHERE bb.externalbackpackid = :backpackid"; | |
820 | $params = ['backpackid' => $id]; | |
821 | $userbackpacks = $DB->get_fieldset_sql($sql, $params); | |
822 | if ($userbackpacks) { | |
823 | // Delete user external collections references to this backpack. | |
824 | list($insql, $params) = $DB->get_in_or_equal($userbackpacks); | |
825 | $DB->delete_records_select('badge_external', "backpackid $insql", $params); | |
826 | } | |
827 | $DB->delete_records('badge_backpack', ['externalbackpackid' => $id]); | |
828 | ||
829 | // Delete backpack entry. | |
830 | $result = $DB->delete_records('badge_external_backpack', ['id' => $id]); | |
831 | ||
832 | $transaction->allow_commit(); | |
833 | ||
834 | return $result; | |
835 | } | |
836 | ||
837 | return false; | |
838 | } | |
839 | ||
1c4620bc P |
840 | /** |
841 | * Perform the actual create/update of external bakpacks. Any checks on the validity of the id will need to be | |
842 | * performed before it reaches this function. | |
843 | * | |
844 | * @param stdClass $data The backpack data we are updating/inserting | |
845 | * @return int Returns the id of the new/updated record | |
846 | */ | |
847 | function badges_save_external_backpack(stdClass $data) { | |
848 | global $DB; | |
849 | $backpack = new stdClass(); | |
850 | ||
851 | $backpack->apiversion = $data->apiversion; | |
852 | $backpack->backpackweburl = $data->backpackweburl; | |
853 | $backpack->backpackapiurl = $data->backpackapiurl; | |
3cae9421 | 854 | $backpack->oauth2_issuerid = $data->oauth2_issuerid ?? ''; |
1c4620bc P |
855 | if (isset($data->sortorder)) { |
856 | $backpack->sortorder = $data->sortorder; | |
857 | } | |
858 | ||
ee0c9d2e SA |
859 | if (empty($data->id)) { |
860 | $backpack->id = $DB->insert_record('badge_external_backpack', $backpack); | |
861 | } else { | |
1c4620bc | 862 | $backpack->id = $data->id; |
ee0c9d2e | 863 | $DB->update_record('badge_external_backpack', $backpack); |
1c4620bc | 864 | } |
ee0c9d2e | 865 | $data->externalbackpackid = $backpack->id; |
1c4620bc P |
866 | |
867 | unset($data->id); | |
868 | badges_save_backpack_credentials($data); | |
1c4620bc P |
869 | return $data->externalbackpackid; |
870 | } | |
871 | ||
872 | /** | |
873 | * Create a backpack with the provided details. Stores the auth details of the backpack | |
874 | * | |
875 | * @param stdClass $data Backpack specific data. | |
876 | * @return int The id of the external backpack that the credentials correspond to | |
877 | */ | |
878 | function badges_save_backpack_credentials(stdClass $data) { | |
879 | global $DB; | |
880 | ||
881 | if (isset($data->backpackemail) && isset($data->password)) { | |
882 | $backpack = new stdClass(); | |
883 | ||
884 | $backpack->email = $data->backpackemail; | |
885 | $backpack->password = !empty($data->password) ? $data->password : ''; | |
886 | $backpack->externalbackpackid = $data->externalbackpackid; | |
887 | $backpack->userid = $data->userid ?? 0; | |
888 | $backpack->backpackuid = $data->backpackuid ?? 0; | |
889 | $backpack->autosync = $data->autosync ?? 0; | |
890 | ||
ee0c9d2e SA |
891 | if (!empty($data->badgebackpack)) { |
892 | $backpack->id = $data->badgebackpack; | |
893 | } else if (!empty($data->id)) { | |
894 | $backpack->id = $data->id; | |
1c4620bc P |
895 | } |
896 | ||
ee0c9d2e SA |
897 | if (empty($backpack->id)) { |
898 | $backpack->id = $DB->insert_record('badge_backpack', $backpack); | |
899 | } else { | |
900 | $DB->update_record('badge_backpack', $backpack); | |
1c4620bc P |
901 | } |
902 | ||
1c4620bc P |
903 | return $backpack->externalbackpackid; |
904 | } | |
905 | ||
906 | return $data->externalbackpackid ?? 0; | |
907 | } | |
908 | ||
aae219ac DW |
909 | /** |
910 | * Is any backpack enabled that supports open badges V1? | |
d3af9592 | 911 | * @param int|null $backpackid Check the version of the given id OR if null the sitewide backpack |
aae219ac DW |
912 | * @return boolean |
913 | */ | |
d3af9592 PD |
914 | function badges_open_badges_backpack_api(?int $backpackid = null) { |
915 | if (!$backpackid) { | |
916 | global $CFG; | |
917 | $backpackid = $CFG->badges_site_backpack; | |
918 | } | |
aae219ac | 919 | |
d3af9592 | 920 | $backpack = badges_get_site_backpack($backpackid); |
aae219ac DW |
921 | if (empty($backpack->apiversion)) { |
922 | return OPEN_BADGES_V2; | |
923 | } | |
924 | return $backpack->apiversion; | |
925 | } | |
926 | ||
927 | /** | |
96ec45cb | 928 | * Get a site backpacks by id for a particular user or site (if userid is 0) |
aae219ac DW |
929 | * |
930 | * @param int $id The backpack id. | |
96ec45cb | 931 | * @param int $userid The owner of the backpack, 0 if it's a sitewide backpack else a user's site backpack |
aae219ac DW |
932 | * @return array(stdClass) |
933 | */ | |
96ec45cb | 934 | function badges_get_site_backpack($id, int $userid = 0) { |
aae219ac DW |
935 | global $DB; |
936 | ||
3cae9421 PD |
937 | $sql = "SELECT beb.*, bb.id AS badgebackpack, bb.password, bb.email AS backpackemail |
938 | FROM {badge_external_backpack} beb | |
939 | LEFT JOIN {badge_backpack} bb ON bb.externalbackpackid = beb.id AND bb.userid=:userid | |
940 | WHERE beb.id=:id"; | |
941 | ||
96ec45cb | 942 | return $DB->get_record_sql($sql, ['id' => $id, 'userid' => $userid]); |
aae219ac DW |
943 | } |
944 | ||
d3af9592 PD |
945 | /** |
946 | * Get the user backpack for the currently logged in user OR the provided user | |
947 | * | |
948 | * @param int|null $userid The user whose backpack you're requesting for. If null, get the logged in user's backpack | |
949 | * @return mixed The user's backpack or none. | |
950 | * @throws dml_exception | |
951 | */ | |
952 | function badges_get_user_backpack(?int $userid = 0) { | |
953 | global $DB; | |
954 | ||
955 | if (!$userid) { | |
956 | global $USER; | |
957 | $userid = $USER->id; | |
958 | } | |
959 | ||
960 | $sql = "SELECT beb.*, bb.id AS badgebackpack, bb.password, bb.email AS backpackemail | |
961 | FROM {badge_external_backpack} beb | |
962 | JOIN {badge_backpack} bb ON bb.externalbackpackid = beb.id AND bb.userid=:userid"; | |
963 | ||
964 | return $DB->get_record_sql($sql, ['userid' => $userid]); | |
965 | } | |
966 | ||
b6435e09 P |
967 | /** |
968 | * Get the primary backpack for the site | |
969 | * | |
970 | * @return array(stdClass) | |
971 | */ | |
972 | function badges_get_site_primary_backpack() { | |
973 | global $CFG; | |
974 | ||
975 | return badges_get_site_backpack($CFG->badges_site_backpack); | |
976 | } | |
977 | ||
aae219ac DW |
978 | /** |
979 | * List the backpacks at site level. | |
980 | * | |
981 | * @return array(stdClass) | |
982 | */ | |
983 | function badges_get_site_backpacks() { | |
984 | global $DB, $CFG; | |
985 | ||
96ec45cb | 986 | $all = $DB->get_records('badge_external_backpack'); |
aae219ac DW |
987 | |
988 | foreach ($all as $key => $bp) { | |
989 | if ($bp->id == $CFG->badges_site_backpack) { | |
990 | $all[$key]->sitebackpack = true; | |
991 | } else { | |
992 | $all[$key]->sitebackpack = false; | |
993 | } | |
994 | } | |
995 | return $all; | |
996 | } | |
997 | ||
998 | /** | |
999 | * List the supported badges api versions. | |
1000 | * | |
1001 | * @return array(version) | |
1002 | */ | |
1003 | function badges_get_badge_api_versions() { | |
1004 | return [ | |
15a00bea TT |
1005 | (string)OPEN_BADGES_V1 => get_string('openbadgesv1', 'badges'), |
1006 | (string)OPEN_BADGES_V2 => get_string('openbadgesv2', 'badges'), | |
1007 | (string)OPEN_BADGES_V2P1 => get_string('openbadgesv2p1', 'badges') | |
aae219ac DW |
1008 | ]; |
1009 | } | |
1010 | ||
aae219ac DW |
1011 | /** |
1012 | * Get the default issuer for a badge from this site. | |
1013 | * | |
1014 | * @return array | |
1015 | */ | |
1016 | function badges_get_default_issuer() { | |
c659b1c5 | 1017 | global $CFG, $SITE; |
aae219ac | 1018 | |
96ec45cb | 1019 | $sitebackpack = badges_get_site_primary_backpack(); |
aae219ac | 1020 | $issuer = array(); |
2f45b6f7 | 1021 | $issuerurl = new moodle_url('/'); |
aae219ac | 1022 | $issuer['name'] = $CFG->badges_defaultissuername; |
c659b1c5 DW |
1023 | if (empty($issuer['name'])) { |
1024 | $issuer['name'] = $SITE->fullname ? $SITE->fullname : $SITE->shortname; | |
1025 | } | |
7444ba74 | 1026 | $issuer['url'] = $issuerurl->out(false); |
96ec45cb | 1027 | $issuer['email'] = $sitebackpack->backpackemail ?? $CFG->badges_defaultissuercontact; |
aae219ac | 1028 | $issuer['@context'] = OPEN_BADGES_V2_CONTEXT; |
2f45b6f7 SA |
1029 | $issuerid = new moodle_url('/badges/issuer_json.php'); |
1030 | $issuer['id'] = $issuerid->out(false); | |
aae219ac DW |
1031 | $issuer['type'] = OPEN_BADGES_V2_TYPE_ISSUER; |
1032 | return $issuer; | |
1033 | } | |
1034 | ||
1035 | /** | |
1036 | * Disconnect from the user backpack by deleting the user preferences. | |
1037 | * | |
1038 | * @param integer $userid The user to diconnect. | |
1039 | * @return boolean | |
1040 | */ | |
1041 | function badges_disconnect_user_backpack($userid) { | |
1042 | global $USER; | |
1043 | ||
1044 | // We can only change backpack settings for our own real backpack. | |
1045 | if ($USER->id != $userid || | |
1046 | \core\session\manager::is_loggedinas()) { | |
1047 | ||
1048 | return false; | |
1049 | } | |
1050 | ||
1051 | unset_user_preference('badges_email_verify_secret'); | |
1052 | unset_user_preference('badges_email_verify_address'); | |
1053 | unset_user_preference('badges_email_verify_backpackid'); | |
1054 | unset_user_preference('badges_email_verify_password'); | |
1055 | ||
1056 | return true; | |
1057 | } | |
1058 | ||
1059 | /** | |
1060 | * Used to remember which objects we connected with a backpack before. | |
1061 | * | |
1062 | * @param integer $sitebackpackid The site backpack to connect to. | |
1063 | * @param string $type The type of this remote object. | |
1064 | * @param string $internalid The id for this object on the Moodle site. | |
b6435e09 | 1065 | * @param string $param The param we need to return. Defaults to the externalid. |
aae219ac DW |
1066 | * @return mixed The id or false if it doesn't exist. |
1067 | */ | |
b6435e09 | 1068 | function badges_external_get_mapping($sitebackpackid, $type, $internalid, $param = 'externalid') { |
aae219ac DW |
1069 | global $DB; |
1070 | // Return externalid if it exists. | |
1071 | $params = [ | |
1072 | 'sitebackpackid' => $sitebackpackid, | |
1073 | 'type' => $type, | |
1074 | 'internalid' => $internalid | |
1075 | ]; | |
1076 | ||
b6435e09 | 1077 | $record = $DB->get_record('badge_external_identifier', $params, $param, IGNORE_MISSING); |
aae219ac | 1078 | if ($record) { |
b6435e09 | 1079 | return $record->$param; |
aae219ac DW |
1080 | } |
1081 | return false; | |
1082 | } | |
1083 | ||
1084 | /** | |
1085 | * Save the info about which objects we connected with a backpack before. | |
1086 | * | |
1087 | * @param integer $sitebackpackid The site backpack to connect to. | |
1088 | * @param string $type The type of this remote object. | |
1089 | * @param string $internalid The id for this object on the Moodle site. | |
1090 | * @param string $externalid The id of this object on the remote site. | |
1091 | * @return boolean | |
1092 | */ | |
1093 | function badges_external_create_mapping($sitebackpackid, $type, $internalid, $externalid) { | |
1094 | global $DB; | |
1095 | ||
1096 | $params = [ | |
1097 | 'sitebackpackid' => $sitebackpackid, | |
1098 | 'type' => $type, | |
1099 | 'internalid' => $internalid, | |
1100 | 'externalid' => $externalid | |
1101 | ]; | |
1102 | ||
1103 | return $DB->insert_record('badge_external_identifier', $params); | |
1104 | } | |
1105 | ||
1106 | /** | |
1107 | * Delete all external mapping information for a backpack. | |
1108 | * | |
1109 | * @param integer $sitebackpackid The site backpack to connect to. | |
1110 | * @return boolean | |
1111 | */ | |
1112 | function badges_external_delete_mappings($sitebackpackid) { | |
1113 | global $DB; | |
1114 | ||
1115 | $params = ['sitebackpackid' => $sitebackpackid]; | |
1116 | ||
1117 | return $DB->delete_records('badge_external_identifier', $params); | |
1118 | } | |
1119 | ||
1120 | /** | |
1121 | * Delete a specific external mapping information for a backpack. | |
1122 | * | |
1123 | * @param integer $sitebackpackid The site backpack to connect to. | |
1124 | * @param string $type The type of this remote object. | |
1125 | * @param string $internalid The id for this object on the Moodle site. | |
1126 | * @return boolean | |
1127 | */ | |
1128 | function badges_external_delete_mapping($sitebackpackid, $type, $internalid) { | |
1129 | global $DB; | |
1130 | ||
1131 | $params = [ | |
1132 | 'sitebackpackid' => $sitebackpackid, | |
1133 | 'type' => $type, | |
1134 | 'internalid' => $internalid | |
1135 | ]; | |
1136 | ||
1137 | $DB->delete_record('badge_external_identifier', $params); | |
1138 | } | |
1139 | ||
1140 | /** | |
1141 | * Create and send a verification email to the email address supplied. | |
1142 | * | |
1143 | * Since we're not sending this email to a user, email_to_user can't be used | |
1144 | * but this function borrows largely the code from that process. | |
1145 | * | |
1146 | * @param string $email the email address to send the verification email to. | |
1147 | * @param int $backpackid the id of the backpack to connect to | |
1837b1d5 | 1148 | * @param string $backpackpassword the user entered password to connect to this backpack |
aae219ac DW |
1149 | * @return true if the email was sent successfully, false otherwise. |
1150 | */ | |
1151 | function badges_send_verification_email($email, $backpackid, $backpackpassword) { | |
1152 | global $DB, $USER; | |
1153 | ||
1154 | // Store a user secret (badges_email_verify_secret) and the address (badges_email_verify_address) as users prefs. | |
1155 | // The address will be used by edit_backpack_form for display during verification and to facilitate the resending | |
1156 | // of verification emails to said address. | |
1157 | $secret = random_string(15); | |
1158 | set_user_preference('badges_email_verify_secret', $secret); | |
1159 | set_user_preference('badges_email_verify_address', $email); | |
1160 | set_user_preference('badges_email_verify_backpackid', $backpackid); | |
1161 | set_user_preference('badges_email_verify_password', $backpackpassword); | |
1162 | ||
1163 | // To, from. | |
1164 | $tempuser = $DB->get_record('user', array('id' => $USER->id), '*', MUST_EXIST); | |
1165 | $tempuser->email = $email; | |
1166 | $noreplyuser = core_user::get_noreply_user(); | |
1167 | ||
1168 | // Generate the verification email body. | |
1169 | $verificationurl = '/badges/backpackemailverify.php'; | |
1170 | $verificationurl = new moodle_url($verificationurl); | |
1171 | $verificationpath = $verificationurl->out(false); | |
1172 | ||
1173 | $site = get_site(); | |
1174 | $args = new stdClass(); | |
1175 | $args->link = $verificationpath . '?data='. $secret; | |
1176 | $args->sitename = $site->fullname; | |
1177 | $args->admin = generate_email_signoff(); | |
1178 | ||
1179 | $messagesubject = get_string('backpackemailverifyemailsubject', 'badges', $site->fullname); | |
1180 | $messagetext = get_string('backpackemailverifyemailbody', 'badges', $args); | |
1181 | $messagehtml = text_to_html($messagetext, false, false, true); | |
1182 | ||
1183 | return email_to_user($tempuser, $noreplyuser, $messagesubject, $messagetext, $messagehtml); | |
1184 | } | |
1185 | ||
43f1c8e2 DW |
1186 | /** |
1187 | * Return all the enabled criteria types for this site. | |
1188 | * | |
8aff6f6f | 1189 | * @param boolean $enabled |
43f1c8e2 DW |
1190 | * @return array |
1191 | */ | |
1192 | function badges_list_criteria($enabled = true) { | |
1193 | global $CFG; | |
1194 | ||
1195 | $types = array( | |
1196 | BADGE_CRITERIA_TYPE_OVERALL => 'overall', | |
1197 | BADGE_CRITERIA_TYPE_ACTIVITY => 'activity', | |
1198 | BADGE_CRITERIA_TYPE_MANUAL => 'manual', | |
1199 | BADGE_CRITERIA_TYPE_SOCIAL => 'social', | |
1200 | BADGE_CRITERIA_TYPE_COURSE => 'course', | |
1201 | BADGE_CRITERIA_TYPE_COURSESET => 'courseset', | |
1202 | BADGE_CRITERIA_TYPE_PROFILE => 'profile', | |
1203 | BADGE_CRITERIA_TYPE_BADGE => 'badge', | |
1204 | BADGE_CRITERIA_TYPE_COHORT => 'cohort', | |
1205 | BADGE_CRITERIA_TYPE_COMPETENCY => 'competency', | |
1206 | ); | |
1207 | if ($enabled) { | |
1208 | foreach ($types as $key => $type) { | |
1209 | $class = 'award_criteria_' . $type; | |
1210 | $file = $CFG->dirroot . '/badges/criteria/' . $class . '.php'; | |
1211 | if (file_exists($file)) { | |
1212 | require_once($file); | |
1213 | ||
1214 | if (!$class::is_enabled()) { | |
1215 | unset($types[$key]); | |
1216 | } | |
1217 | } | |
1218 | } | |
1219 | } | |
1220 | return $types; | |
1221 | } | |
f44557d0 DW |
1222 | |
1223 | /** | |
1224 | * Check if any badge has records for competencies. | |
1225 | * | |
1226 | * @param array $competencyids Array of competencies ids. | |
1227 | * @return boolean Return true if competencies were found in any badge. | |
1228 | */ | |
1229 | function badge_award_criteria_competency_has_records_for_competencies($competencyids) { | |
1230 | global $DB; | |
1231 | ||
1232 | list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED); | |
1233 | ||
1234 | $sql = "SELECT DISTINCT bc.badgeid | |
1235 | FROM {badge_criteria} bc | |
1236 | JOIN {badge_criteria_param} bcp ON bc.id = bcp.critid | |
1237 | WHERE bc.criteriatype = :criteriatype AND bcp.value $insql"; | |
1238 | $params['criteriatype'] = BADGE_CRITERIA_TYPE_COMPETENCY; | |
1239 | ||
1240 | return $DB->record_exists_sql($sql, $params); | |
1241 | } | |
dabc69af | 1242 | |
7c36d669 SL |
1243 | /** |
1244 | * Creates single message for all notification and sends it out | |
1245 | * | |
1246 | * @param object $badge A badge which is notified about. | |
1247 | */ | |
1248 | function badge_assemble_notification(stdClass $badge) { | |
1249 | global $DB; | |
1250 | ||
1251 | $userfrom = core_user::get_noreply_user(); | |
1252 | $userfrom->maildisplay = true; | |
1253 | ||
1254 | if ($msgs = $DB->get_records_select('badge_issued', 'issuernotified IS NULL AND badgeid = ?', array($badge->id))) { | |
1255 | // Get badge creator. | |
1256 | $creator = $DB->get_record('user', array('id' => $badge->creator), '*', MUST_EXIST); | |
1257 | $creatorsubject = get_string('creatorsubject', 'badges', $badge->name); | |
1258 | $creatormessage = ''; | |
1259 | ||
1260 | // Put all messages in one digest. | |
1261 | foreach ($msgs as $msg) { | |
1262 | $issuedlink = html_writer::link(new moodle_url('/badges/badge.php', array('hash' => $msg->uniquehash)), $badge->name); | |
1263 | $recipient = $DB->get_record('user', array('id' => $msg->userid), '*', MUST_EXIST); | |
1264 | ||
1265 | $a = new stdClass(); | |
1266 | $a->user = fullname($recipient); | |
1267 | $a->link = $issuedlink; | |
1268 | $creatormessage .= get_string('creatorbody', 'badges', $a); | |
1269 | $DB->set_field('badge_issued', 'issuernotified', time(), array('badgeid' => $msg->badgeid, 'userid' => $msg->userid)); | |
1270 | } | |
1271 | ||
1272 | // Create a message object. | |
1273 | $eventdata = new \core\message\message(); | |
1274 | $eventdata->courseid = SITEID; | |
1275 | $eventdata->component = 'moodle'; | |
1276 | $eventdata->name = 'badgecreatornotice'; | |
1277 | $eventdata->userfrom = $userfrom; | |
1278 | $eventdata->userto = $creator; | |
1279 | $eventdata->notification = 1; | |
1280 | $eventdata->subject = $creatorsubject; | |
1281 | $eventdata->fullmessage = format_text_email($creatormessage, FORMAT_HTML); | |
1282 | $eventdata->fullmessageformat = FORMAT_PLAIN; | |
1283 | $eventdata->fullmessagehtml = $creatormessage; | |
1284 | $eventdata->smallmessage = $creatorsubject; | |
1285 | ||
1286 | message_send($eventdata); | |
1287 | } | |
1288 | } | |
d48a52dd DW |
1289 | |
1290 | /** | |
1291 | * Attempt to authenticate with the site backpack credentials and return an error | |
2fe34536 DW |
1292 | * if the authentication fails. If external backpacks are not enabled, this will |
1293 | * not perform any test. | |
d48a52dd DW |
1294 | * |
1295 | * @return string | |
1296 | */ | |
1297 | function badges_verify_site_backpack() { | |
3e567085 SA |
1298 | global $CFG; |
1299 | ||
1300 | return badges_verify_backpack($CFG->badges_site_backpack); | |
1301 | } | |
1302 | ||
1303 | /** | |
1304 | * Attempt to authenticate with a backpack credentials and return an error | |
1305 | * if the authentication fails. | |
1306 | * If external backpacks are not enabled or the backpack version is different | |
1307 | * from OBv2, this will not perform any test. | |
1308 | * | |
1309 | * @param int $backpackid Backpack identifier to verify. | |
1310 | * @return string The result of the verification process. | |
1311 | */ | |
1312 | function badges_verify_backpack(int $backpackid) { | |
d48a52dd DW |
1313 | global $OUTPUT, $CFG; |
1314 | ||
1315 | if (empty($CFG->badges_allowexternalbackpack)) { | |
1316 | return ''; | |
1317 | } | |
1318 | ||
3e567085 | 1319 | $backpack = badges_get_site_backpack($backpackid); |
d48a52dd DW |
1320 | if (empty($backpack->apiversion) || ($backpack->apiversion == OPEN_BADGES_V2)) { |
1321 | $backpackapi = new \core_badges\backpack_api($backpack); | |
1322 | ||
2fe34536 DW |
1323 | // Clear any cached access tokens in the session. |
1324 | $backpackapi->clear_system_user_session(); | |
1325 | ||
1326 | // Now attempt a login with these credentials. | |
d48a52dd | 1327 | $result = $backpackapi->authenticate(); |
084a42f9 | 1328 | if (empty($result) || !empty($result->error)) { |
d48a52dd DW |
1329 | $warning = $backpackapi->get_authentication_error(); |
1330 | ||
1331 | $params = ['id' => $backpack->id, 'action' => 'edit']; | |
1332 | $backpackurl = (new moodle_url('/badges/backpacks.php', $params))->out(false); | |
1333 | ||
1334 | $message = get_string('sitebackpackwarning', 'badges', ['url' => $backpackurl, 'warning' => $warning]); | |
1335 | $icon = $OUTPUT->pix_icon('i/warning', get_string('warning', 'moodle')); | |
4394f9e3 | 1336 | return $OUTPUT->container($icon . $message, 'text-danger'); |
d48a52dd DW |
1337 | } |
1338 | } | |
3e567085 | 1339 | |
d48a52dd DW |
1340 | return ''; |
1341 | } | |
15a00bea TT |
1342 | |
1343 | /** | |
1344 | * Get OAuth2 services for the external backpack. | |
1345 | * | |
1346 | * @return array | |
1347 | * @throws coding_exception | |
1348 | */ | |
1349 | function badges_get_oauth2_service_options() { | |
1350 | global $DB; | |
1351 | ||
1352 | $issuers = core\oauth2\api::get_all_issuers(); | |
1353 | $options = ['' => 'None']; | |
1354 | foreach ($issuers as $issuer) { | |
1355 | $options[$issuer->get('id')] = $issuer->get('name'); | |
1356 | } | |
1357 | ||
1358 | return $options; | |
1359 | } | |
b6435e09 P |
1360 | |
1361 | /** | |
1362 | * Generate a public badgr URL that conforms to OBv2. This is done because badgr responses do not currently conform to | |
1363 | * the spec. | |
1364 | * | |
1365 | * WARNING: This is an extremely hacky way of implementing this and should be removed once the standards are conformed to. | |
1366 | * | |
1367 | * @param stdClass $backpack The Badgr backpack we are pushing to | |
1368 | * @param string $type The type of object we are dealing with either Issuer, Assertion OR Badge. | |
1369 | * @param string $externalid The externalid as provided by the backpack | |
1370 | * @return string The public URL to access Badgr objects | |
1371 | */ | |
1372 | function badges_generate_badgr_open_url($backpack, $type, $externalid) { | |
1373 | if (badges_open_badges_backpack_api($backpack->id) == OPEN_BADGES_V2) { | |
1374 | $entity = strtolower($type); | |
1375 | if ($type == OPEN_BADGES_V2_TYPE_BADGE) { | |
1376 | $entity = "badge"; | |
1377 | } | |
1378 | $url = new moodle_url($backpack->backpackapiurl); | |
1379 | return "{$url->get_scheme()}://{$url->get_host()}/public/{$entity}s/$externalid"; | |
1380 | ||
1381 | } | |
1382 | } |