Merge branch 'MDL-66734-37' of https://github.com/snake/moodle into MOODLE_37_STABLE
[moodle.git] / badges / classes / badge.php
blob7334681ac296731dab97587b55ea6d95b8827391
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.
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/>.
17 /**
18 * Badge assertion library.
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>
27 namespace core_badges;
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->dirroot.'/lib/badgeslib.php');
33 use context_system;
34 use context_course;
35 use context_user;
36 use moodle_exception;
37 use moodle_url;
38 use core_text;
39 use award_criteria;
40 use core_php_time_limit;
41 use html_writer;
42 use stdClass;
44 /**
45 * Class that represents badge.
47 * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50 class badge {
51 /** @var int Badge id */
52 public $id;
54 /** @var string Badge name */
55 public $name;
57 /** @var string Badge description */
58 public $description;
60 /** @var integer Timestamp this badge was created */
61 public $timecreated;
63 /** @var integer Timestamp this badge was modified */
64 public $timemodified;
66 /** @var int The user who created this badge */
67 public $usercreated;
69 /** @var int The user who modified this badge */
70 public $usermodified;
72 /** @var string The name of the issuer of this badge */
73 public $issuername;
75 /** @var string The url of the issuer of this badge */
76 public $issuerurl;
78 /** @var string The email of the issuer of this badge */
79 public $issuercontact;
81 /** @var integer Timestamp this badge will expire */
82 public $expiredate;
84 /** @var integer Duration this badge is valid for */
85 public $expireperiod;
87 /** @var integer Site or course badge */
88 public $type;
90 /** @var integer The course this badge belongs to */
91 public $courseid;
93 /** @var string The message this badge includes. */
94 public $message;
96 /** @var string The subject of the message for this badge */
97 public $messagesubject;
99 /** @var int Is this badge image baked. */
100 public $attachment;
102 /** @var int Send a message when this badge is awarded. */
103 public $notification;
105 /** @var int Lifecycle status for this badge. */
106 public $status = 0;
108 /** @var int Timestamp to next run cron for this badge. */
109 public $nextcron;
111 /** @var int What backpack api version to use for this badge. */
112 public $version;
114 /** @var string What language is this badge written in. */
115 public $language;
117 /** @var string The author of the image for this badge. */
118 public $imageauthorname;
120 /** @var string The email of the author of the image for this badge. */
121 public $imageauthoremail;
123 /** @var string The url of the author of the image for this badge. */
124 public $imageauthorurl;
126 /** @var string The caption of the image for this badge. */
127 public $imagecaption;
129 /** @var array Badge criteria */
130 public $criteria = array();
133 * Constructs with badge details.
135 * @param int $badgeid badge ID.
137 public function __construct($badgeid) {
138 global $DB;
139 $this->id = $badgeid;
141 $data = $DB->get_record('badge', array('id' => $badgeid));
143 if (empty($data)) {
144 print_error('error:nosuchbadge', 'badges', $badgeid);
147 foreach ((array)$data as $field => $value) {
148 if (property_exists($this, $field)) {
149 $this->{$field} = $value;
153 if (badges_open_badges_backpack_api() != OPEN_BADGES_V1) {
154 // For Open Badges 2 we need to use a single site issuer with no exceptions.
155 $issuer = badges_get_default_issuer();
156 $this->issuername = $issuer['name'];
157 $this->issuercontact = $issuer['email'];
158 $this->issuerurl = $issuer['url'];
161 $this->criteria = self::get_criteria();
165 * Use to get context instance of a badge.
167 * @return context instance.
169 public function get_context() {
170 if ($this->type == BADGE_TYPE_SITE) {
171 return context_system::instance();
172 } else if ($this->type == BADGE_TYPE_COURSE) {
173 return context_course::instance($this->courseid);
174 } else {
175 debugging('Something is wrong...');
180 * Return array of aggregation methods
182 * @return array
184 public static function get_aggregation_methods() {
185 return array(
186 BADGE_CRITERIA_AGGREGATION_ALL => get_string('all', 'badges'),
187 BADGE_CRITERIA_AGGREGATION_ANY => get_string('any', 'badges'),
192 * Return array of accepted criteria types for this badge
194 * @return array
196 public function get_accepted_criteria() {
197 global $CFG;
198 $criteriatypes = array();
200 if ($this->type == BADGE_TYPE_COURSE) {
201 $criteriatypes = array(
202 BADGE_CRITERIA_TYPE_OVERALL,
203 BADGE_CRITERIA_TYPE_MANUAL,
204 BADGE_CRITERIA_TYPE_COURSE,
205 BADGE_CRITERIA_TYPE_BADGE,
206 BADGE_CRITERIA_TYPE_ACTIVITY,
207 BADGE_CRITERIA_TYPE_COMPETENCY
209 } else if ($this->type == BADGE_TYPE_SITE) {
210 $criteriatypes = array(
211 BADGE_CRITERIA_TYPE_OVERALL,
212 BADGE_CRITERIA_TYPE_MANUAL,
213 BADGE_CRITERIA_TYPE_COURSESET,
214 BADGE_CRITERIA_TYPE_BADGE,
215 BADGE_CRITERIA_TYPE_PROFILE,
216 BADGE_CRITERIA_TYPE_COHORT,
217 BADGE_CRITERIA_TYPE_COMPETENCY
220 $alltypes = badges_list_criteria();
221 foreach ($criteriatypes as $index => $type) {
222 if (!isset($alltypes[$type])) {
223 unset($criteriatypes[$index]);
227 return $criteriatypes;
231 * Save/update badge information in 'badge' table only.
232 * Cannot be used for updating awards and criteria settings.
234 * @return boolean Returns true on success.
236 public function save() {
237 global $DB;
239 $fordb = new stdClass();
240 foreach (get_object_vars($this) as $k => $v) {
241 $fordb->{$k} = $v;
243 unset($fordb->criteria);
245 $fordb->timemodified = time();
246 if ($DB->update_record_raw('badge', $fordb)) {
247 // Trigger event, badge updated.
248 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
249 $event = \core\event\badge_updated::create($eventparams);
250 $event->trigger();
251 return true;
252 } else {
253 throw new moodle_exception('error:save', 'badges');
254 return false;
259 * Creates and saves a clone of badge with all its properties.
260 * Clone is not active by default and has 'Copy of' attached to its name.
262 * @return int ID of new badge.
264 public function make_clone() {
265 global $DB, $USER, $PAGE;
267 $fordb = new stdClass();
268 foreach (get_object_vars($this) as $k => $v) {
269 $fordb->{$k} = $v;
272 $fordb->name = get_string('copyof', 'badges', $this->name);
273 $fordb->status = BADGE_STATUS_INACTIVE;
274 $fordb->usercreated = $USER->id;
275 $fordb->usermodified = $USER->id;
276 $fordb->timecreated = time();
277 $fordb->timemodified = time();
278 unset($fordb->id);
280 if ($fordb->notification > 1) {
281 $fordb->nextcron = badges_calculate_message_schedule($fordb->notification);
284 $criteria = $fordb->criteria;
285 unset($fordb->criteria);
287 if ($new = $DB->insert_record('badge', $fordb, true)) {
288 $newbadge = new badge($new);
290 // Copy badge image.
291 $fs = get_file_storage();
292 if ($file = $fs->get_file($this->get_context()->id, 'badges', 'badgeimage', $this->id, '/', 'f1.png')) {
293 if ($imagefile = $file->copy_content_to_temp()) {
294 badges_process_badge_image($newbadge, $imagefile);
298 // Copy badge criteria.
299 foreach ($this->criteria as $crit) {
300 $crit->make_clone($new);
303 // Trigger event, badge duplicated.
304 $eventparams = array('objectid' => $new, 'context' => $PAGE->context);
305 $event = \core\event\badge_duplicated::create($eventparams);
306 $event->trigger();
308 return $new;
309 } else {
310 throw new moodle_exception('error:clone', 'badges');
311 return false;
316 * Checks if badges is active.
317 * Used in badge award.
319 * @return boolean A status indicating badge is active
321 public function is_active() {
322 if (($this->status == BADGE_STATUS_ACTIVE) ||
323 ($this->status == BADGE_STATUS_ACTIVE_LOCKED)) {
324 return true;
326 return false;
330 * Use to get the name of badge status.
332 * @return string
334 public function get_status_name() {
335 return get_string('badgestatus_' . $this->status, 'badges');
339 * Use to set badge status.
340 * Only active badges can be earned/awarded/issued.
342 * @param int $status Status from BADGE_STATUS constants
344 public function set_status($status = 0) {
345 $this->status = $status;
346 $this->save();
347 if ($status == BADGE_STATUS_ACTIVE) {
348 // Trigger event, badge enabled.
349 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
350 $event = \core\event\badge_enabled::create($eventparams);
351 $event->trigger();
352 } else if ($status == BADGE_STATUS_INACTIVE) {
353 // Trigger event, badge disabled.
354 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
355 $event = \core\event\badge_disabled::create($eventparams);
356 $event->trigger();
361 * Checks if badges is locked.
362 * Used in badge award and editing.
364 * @return boolean A status indicating badge is locked
366 public function is_locked() {
367 if (($this->status == BADGE_STATUS_ACTIVE_LOCKED) ||
368 ($this->status == BADGE_STATUS_INACTIVE_LOCKED)) {
369 return true;
371 return false;
375 * Checks if badge has been awarded to users.
376 * Used in badge editing.
378 * @return boolean A status indicating badge has been awarded at least once
380 public function has_awards() {
381 global $DB;
382 $awarded = $DB->record_exists_sql('SELECT b.uniquehash
383 FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
384 WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
386 return $awarded;
390 * Gets list of users who have earned an instance of this badge.
392 * @return array An array of objects with information about badge awards.
394 public function get_awards() {
395 global $DB;
397 $awards = $DB->get_records_sql(
398 'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname
399 FROM {badge_issued} b INNER JOIN {user} u
400 ON b.userid = u.id
401 WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
403 return $awards;
407 * Indicates whether badge has already been issued to a user.
409 * @param int $userid User to check
410 * @return boolean
412 public function is_issued($userid) {
413 global $DB;
414 return $DB->record_exists('badge_issued', array('badgeid' => $this->id, 'userid' => $userid));
418 * Issue a badge to user.
420 * @param int $userid User who earned the badge
421 * @param boolean $nobake Not baking actual badges (for testing purposes)
423 public function issue($userid, $nobake = false) {
424 global $DB, $CFG;
426 $now = time();
427 $issued = new stdClass();
428 $issued->badgeid = $this->id;
429 $issued->userid = $userid;
430 $issued->uniquehash = sha1(rand() . $userid . $this->id . $now);
431 $issued->dateissued = $now;
433 if ($this->can_expire()) {
434 $issued->dateexpire = $this->calculate_expiry($now);
435 } else {
436 $issued->dateexpire = null;
439 // Take into account user badges privacy settings.
440 // If none set, badges default visibility is set to public.
441 $issued->visible = get_user_preferences('badgeprivacysetting', 1, $userid);
443 $result = $DB->insert_record('badge_issued', $issued, true);
445 if ($result) {
446 // Trigger badge awarded event.
447 $eventdata = array (
448 'context' => $this->get_context(),
449 'objectid' => $this->id,
450 'relateduserid' => $userid,
451 'other' => array('dateexpire' => $issued->dateexpire, 'badgeissuedid' => $result)
453 \core\event\badge_awarded::create($eventdata)->trigger();
455 // Lock the badge, so that its criteria could not be changed any more.
456 if ($this->status == BADGE_STATUS_ACTIVE) {
457 $this->set_status(BADGE_STATUS_ACTIVE_LOCKED);
460 // Update details in criteria_met table.
461 $compl = $this->get_criteria_completions($userid);
462 foreach ($compl as $c) {
463 $obj = new stdClass();
464 $obj->id = $c->id;
465 $obj->issuedid = $result;
466 $DB->update_record('badge_criteria_met', $obj, true);
469 if (!$nobake) {
470 // Bake a badge image.
471 $pathhash = badges_bake($issued->uniquehash, $this->id, $userid, true);
473 // Notify recipients and badge creators.
474 badges_notify_badge_award($this, $userid, $issued->uniquehash, $pathhash);
480 * Reviews all badge criteria and checks if badge can be instantly awarded.
482 * @return int Number of awards
484 public function review_all_criteria() {
485 global $DB, $CFG;
486 $awards = 0;
488 // Raise timelimit as this could take a while for big web sites.
489 core_php_time_limit::raise();
490 raise_memory_limit(MEMORY_HUGE);
492 foreach ($this->criteria as $crit) {
493 // Overall criterion is decided when other criteria are reviewed.
494 if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) {
495 continue;
498 list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql();
499 // For site level badges, get all active site users who can earn this badge and haven't got it yet.
500 if ($this->type == BADGE_TYPE_SITE) {
501 $sql = "SELECT DISTINCT u.id, bi.badgeid
502 FROM {user} u
503 {$extrajoin}
504 LEFT JOIN {badge_issued} bi
505 ON u.id = bi.userid AND bi.badgeid = :badgeid
506 WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0 " . $extrawhere;
507 $params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams);
508 $toearn = $DB->get_fieldset_sql($sql, $params);
509 } else {
510 // For course level badges, get all users who already earned the badge in this course.
511 // Then find the ones who are enrolled in the course and don't have a badge yet.
512 $earned = $DB->get_fieldset_select(
513 'badge_issued',
514 'userid AS id',
515 'badgeid = :badgeid',
516 array('badgeid' => $this->id)
519 $wheresql = '';
520 $earnedparams = array();
521 if (!empty($earned)) {
522 list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false);
523 $wheresql = ' WHERE u.id ' . $earnedsql;
525 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true);
526 $sql = "SELECT DISTINCT u.id
527 FROM {user} u
528 {$extrajoin}
529 JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere;
530 $params = array_merge($enrolledparams, $earnedparams, $extraparams);
531 $toearn = $DB->get_fieldset_sql($sql, $params);
534 foreach ($toearn as $uid) {
535 $reviewoverall = false;
536 if ($crit->review($uid, true)) {
537 $crit->mark_complete($uid);
538 if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
539 $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
540 $this->issue($uid);
541 $awards++;
542 } else {
543 $reviewoverall = true;
545 } else {
546 // Will be reviewed some other time.
547 $reviewoverall = false;
549 // Review overall if it is required.
550 if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
551 $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
552 $this->issue($uid);
553 $awards++;
558 return $awards;
562 * Gets an array of completed criteria from 'badge_criteria_met' table.
564 * @param int $userid Completions for a user
565 * @return array Records of criteria completions
567 public function get_criteria_completions($userid) {
568 global $DB;
569 $completions = array();
570 $sql = "SELECT bcm.id, bcm.critid
571 FROM {badge_criteria_met} bcm
572 INNER JOIN {badge_criteria} bc ON bcm.critid = bc.id
573 WHERE bc.badgeid = :badgeid AND bcm.userid = :userid ";
574 $completions = $DB->get_records_sql($sql, array('badgeid' => $this->id, 'userid' => $userid));
576 return $completions;
580 * Checks if badges has award criteria set up.
582 * @return boolean A status indicating badge has at least one criterion
584 public function has_criteria() {
585 if (count($this->criteria) > 0) {
586 return true;
588 return false;
592 * Returns badge award criteria
594 * @return array An array of badge criteria
596 public function get_criteria() {
597 global $DB;
598 $criteria = array();
600 if ($records = (array)$DB->get_records('badge_criteria', array('badgeid' => $this->id))) {
601 foreach ($records as $record) {
602 $criteria[$record->criteriatype] = award_criteria::build((array)$record);
606 return $criteria;
610 * Get aggregation method for badge criteria
612 * @param int $criteriatype If none supplied, get overall aggregation method (optional)
613 * @return int One of BADGE_CRITERIA_AGGREGATION_ALL or BADGE_CRITERIA_AGGREGATION_ANY
615 public function get_aggregation_method($criteriatype = 0) {
616 global $DB;
617 $params = array('badgeid' => $this->id, 'criteriatype' => $criteriatype);
618 $aggregation = $DB->get_field('badge_criteria', 'method', $params, IGNORE_MULTIPLE);
620 if (!$aggregation) {
621 return BADGE_CRITERIA_AGGREGATION_ALL;
624 return $aggregation;
628 * Checks if badge has expiry period or date set up.
630 * @return boolean A status indicating badge can expire
632 public function can_expire() {
633 if ($this->expireperiod || $this->expiredate) {
634 return true;
636 return false;
640 * Calculates badge expiry date based on either expirydate or expiryperiod.
642 * @param int $timestamp Time of badge issue
643 * @return int A timestamp
645 public function calculate_expiry($timestamp) {
646 $expiry = null;
648 if (isset($this->expiredate)) {
649 $expiry = $this->expiredate;
650 } else if (isset($this->expireperiod)) {
651 $expiry = $timestamp + $this->expireperiod;
654 return $expiry;
658 * Checks if badge has manual award criteria set.
660 * @return boolean A status indicating badge can be awarded manually
662 public function has_manual_award_criteria() {
663 foreach ($this->criteria as $criterion) {
664 if ($criterion->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) {
665 return true;
668 return false;
672 * Fully deletes the badge or marks it as archived.
674 * @param boolean $archive Achive a badge without actual deleting of any data.
676 public function delete($archive = true) {
677 global $DB;
679 if ($archive) {
680 $this->status = BADGE_STATUS_ARCHIVED;
681 $this->save();
683 // Trigger event, badge archived.
684 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
685 $event = \core\event\badge_archived::create($eventparams);
686 $event->trigger();
687 return;
690 $fs = get_file_storage();
692 // Remove all issued badge image files and badge awards.
693 // Cannot bulk remove area files here because they are issued in user context.
694 $awards = $this->get_awards();
695 foreach ($awards as $award) {
696 $usercontext = context_user::instance($award->userid);
697 $fs->delete_area_files($usercontext->id, 'badges', 'userbadge', $this->id);
699 $DB->delete_records('badge_issued', array('badgeid' => $this->id));
701 // Remove all badge criteria.
702 $criteria = $this->get_criteria();
703 foreach ($criteria as $criterion) {
704 $criterion->delete();
707 // Delete badge images.
708 $badgecontext = $this->get_context();
709 $fs->delete_area_files($badgecontext->id, 'badges', 'badgeimage', $this->id);
711 // Delete endorsements, competencies and related badges.
712 $DB->delete_records('badge_endorsement', array('badgeid' => $this->id));
713 $relatedsql = 'badgeid = :badgeid OR relatedbadgeid = :relatedbadgeid';
714 $relatedparams = array(
715 'badgeid' => $this->id,
716 'relatedbadgeid' => $this->id
718 $DB->delete_records_select('badge_related', $relatedsql, $relatedparams);
719 $DB->delete_records('badge_alignment', array('badgeid' => $this->id));
721 // Finally, remove badge itself.
722 $DB->delete_records('badge', array('id' => $this->id));
724 // Trigger event, badge deleted.
725 $eventparams = array('objectid' => $this->id,
726 'context' => $this->get_context(),
727 'other' => array('badgetype' => $this->type, 'courseid' => $this->courseid)
729 $event = \core\event\badge_deleted::create($eventparams);
730 $event->trigger();
734 * Add multiple related badges.
736 * @param array $relatedids Id of badges.
738 public function add_related_badges($relatedids) {
739 global $DB;
740 $relatedbadges = array();
741 foreach ($relatedids as $relatedid) {
742 $relatedbadge = new stdClass();
743 $relatedbadge->badgeid = $this->id;
744 $relatedbadge->relatedbadgeid = $relatedid;
745 $relatedbadges[] = $relatedbadge;
747 $DB->insert_records('badge_related', $relatedbadges);
751 * Delete an related badge.
753 * @param int $relatedid Id related badge.
754 * @return boolean A status for delete an related badge.
756 public function delete_related_badge($relatedid) {
757 global $DB;
758 $sql = "(badgeid = :badgeid AND relatedbadgeid = :relatedid) OR " .
759 "(badgeid = :relatedid2 AND relatedbadgeid = :badgeid2)";
760 $params = ['badgeid' => $this->id, 'badgeid2' => $this->id, 'relatedid' => $relatedid, 'relatedid2' => $relatedid];
761 return $DB->delete_records_select('badge_related', $sql, $params);
765 * Checks if badge has related badges.
767 * @return boolean A status related badge.
769 public function has_related() {
770 global $DB;
771 $sql = "SELECT DISTINCT b.id
772 FROM {badge_related} br
773 JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id)
774 WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3";
775 return $DB->record_exists_sql($sql, ['badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id]);
779 * Get related badges of badge.
781 * @param boolean $activeonly Do not get the inactive badges when is true.
782 * @return array Related badges information.
784 public function get_related_badges($activeonly = false) {
785 global $DB;
787 $params = array('badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id);
788 $query = "SELECT DISTINCT b.id, b.name, b.version, b.language, b.type
789 FROM {badge_related} br
790 JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id)
791 WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3";
792 if ($activeonly) {
793 $query .= " AND b.status <> :status";
794 $params['status'] = BADGE_STATUS_INACTIVE;
796 $relatedbadges = $DB->get_records_sql($query, $params);
797 return $relatedbadges;
801 * Insert/update alignment information of badge.
803 * @param stdClass $alignment Data of a alignment.
804 * @param int $alignmentid ID alignment.
805 * @return bool|int A status/ID when insert or update data.
807 public function save_alignment($alignment, $alignmentid = 0) {
808 global $DB;
810 $record = $DB->record_exists('badge_alignment', array('id' => $alignmentid));
811 if ($record) {
812 $alignment->id = $alignmentid;
813 return $DB->update_record('badge_alignment', $alignment);
814 } else {
815 return $DB->insert_record('badge_alignment', $alignment, true);
820 * Delete a alignment of badge.
822 * @param int $alignmentid ID alignment.
823 * @return boolean A status for delete a alignment.
825 public function delete_alignment($alignmentid) {
826 global $DB;
827 return $DB->delete_records('badge_alignment', array('badgeid' => $this->id, 'id' => $alignmentid));
831 * Get alignments of badge.
833 * @return array List content alignments.
835 public function get_alignments() {
836 global $DB;
837 return $DB->get_records('badge_alignment', array('badgeid' => $this->id));
841 * Insert/update Endorsement information of badge.
843 * @param stdClass $endorsement Data of an endorsement.
844 * @return bool|int A status/ID when insert or update data.
846 public function save_endorsement($endorsement) {
847 global $DB;
848 $record = $DB->get_record('badge_endorsement', array('badgeid' => $this->id));
849 if ($record) {
850 $endorsement->id = $record->id;
851 return $DB->update_record('badge_endorsement', $endorsement);
852 } else {
853 return $DB->insert_record('badge_endorsement', $endorsement, true);
858 * Get endorsement of badge.
860 * @return array|stdClass Endorsement information.
862 public function get_endorsement() {
863 global $DB;
864 return $DB->get_record('badge_endorsement', array('badgeid' => $this->id));
868 * Markdown language support for criteria.
870 * @return string $output Markdown content to output.
872 public function markdown_badge_criteria() {
873 $agg = $this->get_aggregation_methods();
874 if (empty($this->criteria)) {
875 return get_string('nocriteria', 'badges');
877 $overalldescr = '';
878 $overall = $this->criteria[BADGE_CRITERIA_TYPE_OVERALL];
879 if (!empty($overall->description)) {
880 $overalldescr = format_text($overall->description, $overall->descriptionformat,
881 array('context' => $this->get_context())) . '\n';
883 // Get the condition string.
884 if (count($this->criteria) == 2) {
885 $condition = get_string('criteria_descr', 'badges');
886 } else {
887 $condition = get_string('criteria_descr_' . BADGE_CRITERIA_TYPE_OVERALL, 'badges',
888 core_text::strtoupper($agg[$this->get_aggregation_method()]));
890 unset($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]);
891 $items = array();
892 // If only one criterion left, make sure its description goe to the top.
893 if (count($this->criteria) == 1) {
894 $c = reset($this->criteria);
895 if (!empty($c->description)) {
896 $overalldescr = $c->description . '\n';
898 if (count($c->params) == 1) {
899 $items[] = ' * ' . get_string('criteria_descr_single_' . $c->criteriatype, 'badges') .
900 $c->get_details();
901 } else {
902 $items[] = '* ' . get_string('criteria_descr_' . $c->criteriatype, 'badges',
903 core_text::strtoupper($agg[$this->get_aggregation_method($c->criteriatype)])) .
904 $c->get_details();
906 } else {
907 foreach ($this->criteria as $type => $c) {
908 $criteriadescr = '';
909 if (!empty($c->description)) {
910 $criteriadescr = $c->description;
912 if (count($c->params) == 1) {
913 $items[] = ' * ' . get_string('criteria_descr_single_' . $type, 'badges') .
914 $c->get_details() . $criteriadescr;
915 } else {
916 $items[] = '* ' . get_string('criteria_descr_' . $type, 'badges',
917 core_text::strtoupper($agg[$this->get_aggregation_method($type)])) .
918 $c->get_details() . $criteriadescr;
922 return strip_tags($overalldescr . $condition . html_writer::alist($items, array(), 'ul'));
926 * Define issuer information by format Open Badges specification version 2.
928 * @return array Issuer informations of the badge.
930 public function get_badge_issuer() {
931 $issuer = array();
932 $issuerurl = new moodle_url('/badges/badge_json.php', array('id' => $this->id, 'action' => 0));
933 $issuer['name'] = $this->issuername;
934 $issuer['url'] = $this->issuerurl;
935 $issuer['email'] = $this->issuercontact;
936 $issuer['@context'] = OPEN_BADGES_V2_CONTEXT;
937 $issuer['id'] = $this->issuerurl;
938 $issuer['type'] = OPEN_BADGES_V2_TYPE_ISSUER;
939 return $issuer;