Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]
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 * Badge assertion library. 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 namespace core_badges; 28 29 defined('MOODLE_INTERNAL') || die(); 30 31 require_once($CFG->dirroot.'/lib/badgeslib.php'); 32 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; 43 44 /** 45 * Class that represents badge. 46 * 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 49 */ 50 class badge { 51 /** @var int Badge id */ 52 public $id; 53 54 /** @var string Badge name */ 55 public $name; 56 57 /** @var string Badge description */ 58 public $description; 59 60 /** @var integer Timestamp this badge was created */ 61 public $timecreated; 62 63 /** @var integer Timestamp this badge was modified */ 64 public $timemodified; 65 66 /** @var int The user who created this badge */ 67 public $usercreated; 68 69 /** @var int The user who modified this badge */ 70 public $usermodified; 71 72 /** @var string The name of the issuer of this badge */ 73 public $issuername; 74 75 /** @var string The url of the issuer of this badge */ 76 public $issuerurl; 77 78 /** @var string The email of the issuer of this badge */ 79 public $issuercontact; 80 81 /** @var integer Timestamp this badge will expire */ 82 public $expiredate; 83 84 /** @var integer Duration this badge is valid for */ 85 public $expireperiod; 86 87 /** @var integer Site or course badge */ 88 public $type; 89 90 /** @var integer The course this badge belongs to */ 91 public $courseid; 92 93 /** @var string The message this badge includes. */ 94 public $message; 95 96 /** @var string The subject of the message for this badge */ 97 public $messagesubject; 98 99 /** @var int Is this badge image baked. */ 100 public $attachment; 101 102 /** @var int Send a message when this badge is awarded. */ 103 public $notification; 104 105 /** @var int Lifecycle status for this badge. */ 106 public $status = 0; 107 108 /** @var int Timestamp to next run cron for this badge. */ 109 public $nextcron; 110 111 /** @var int What backpack api version to use for this badge. */ 112 public $version; 113 114 /** @var string What language is this badge written in. */ 115 public $language; 116 117 /** @var string The author of the image for this badge. */ 118 public $imageauthorname; 119 120 /** @var string The email of the author of the image for this badge. */ 121 public $imageauthoremail; 122 123 /** @var string The url of the author of the image for this badge. */ 124 public $imageauthorurl; 125 126 /** @var string The caption of the image for this badge. */ 127 public $imagecaption; 128 129 /** @var array Badge criteria */ 130 public $criteria = array(); 131 132 /** @var int|null Total users which have the award. Called from badges_get_badges() */ 133 public $awards; 134 135 /** @var string|null The name of badge status. Called from badges_get_badges() */ 136 public $statstring; 137 138 /** @var int|null The date the badges were issued. Called from badges_get_badges() */ 139 public $dateissued; 140 141 /** @var string|null Unique hash. Called from badges_get_badges() */ 142 public $uniquehash; 143 144 /** @var string|null Message format. Called from file_prepare_standard_editor() */ 145 public $messageformat; 146 147 /** @var array Message editor. Called from file_prepare_standard_editor() */ 148 public $message_editor = []; 149 150 /** 151 * Constructs with badge details. 152 * 153 * @param int $badgeid badge ID. 154 */ 155 public function __construct($badgeid) { 156 global $DB; 157 $this->id = $badgeid; 158 159 $data = $DB->get_record('badge', array('id' => $badgeid)); 160 161 if (empty($data)) { 162 throw new moodle_exception('error:nosuchbadge', 'badges', '', $badgeid); 163 } 164 165 foreach ((array)$data as $field => $value) { 166 if (property_exists($this, $field)) { 167 $this->{$field} = $value; 168 } 169 } 170 171 if (badges_open_badges_backpack_api() != OPEN_BADGES_V1) { 172 // For Open Badges 2 we need to use a single site issuer with no exceptions. 173 $issuer = badges_get_default_issuer(); 174 $this->issuername = $issuer['name']; 175 $this->issuercontact = $issuer['email']; 176 $this->issuerurl = $issuer['url']; 177 } 178 179 $this->criteria = self::get_criteria(); 180 } 181 182 /** 183 * Use to get context instance of a badge. 184 * 185 * @return \context|void instance. 186 */ 187 public function get_context() { 188 if ($this->type == BADGE_TYPE_SITE) { 189 return context_system::instance(); 190 } else if ($this->type == BADGE_TYPE_COURSE) { 191 return context_course::instance($this->courseid); 192 } else { 193 debugging('Something is wrong...'); 194 } 195 } 196 197 /** 198 * Return array of aggregation methods 199 * 200 * @return array 201 */ 202 public static function get_aggregation_methods() { 203 return array( 204 BADGE_CRITERIA_AGGREGATION_ALL => get_string('all', 'badges'), 205 BADGE_CRITERIA_AGGREGATION_ANY => get_string('any', 'badges'), 206 ); 207 } 208 209 /** 210 * Return array of accepted criteria types for this badge 211 * 212 * @return array 213 */ 214 public function get_accepted_criteria() { 215 global $CFG; 216 $criteriatypes = array(); 217 218 if ($this->type == BADGE_TYPE_COURSE) { 219 $criteriatypes = array( 220 BADGE_CRITERIA_TYPE_OVERALL, 221 BADGE_CRITERIA_TYPE_MANUAL, 222 BADGE_CRITERIA_TYPE_COURSE, 223 BADGE_CRITERIA_TYPE_BADGE, 224 BADGE_CRITERIA_TYPE_ACTIVITY, 225 BADGE_CRITERIA_TYPE_COMPETENCY 226 ); 227 } else if ($this->type == BADGE_TYPE_SITE) { 228 $criteriatypes = array( 229 BADGE_CRITERIA_TYPE_OVERALL, 230 BADGE_CRITERIA_TYPE_MANUAL, 231 BADGE_CRITERIA_TYPE_COURSESET, 232 BADGE_CRITERIA_TYPE_BADGE, 233 BADGE_CRITERIA_TYPE_PROFILE, 234 BADGE_CRITERIA_TYPE_COHORT, 235 BADGE_CRITERIA_TYPE_COMPETENCY 236 ); 237 } 238 $alltypes = badges_list_criteria(); 239 foreach ($criteriatypes as $index => $type) { 240 if (!isset($alltypes[$type])) { 241 unset($criteriatypes[$index]); 242 } 243 } 244 245 return $criteriatypes; 246 } 247 248 /** 249 * Save/update badge information in 'badge' table only. 250 * Cannot be used for updating awards and criteria settings. 251 * 252 * @return boolean Returns true on success. 253 */ 254 public function save() { 255 global $DB; 256 257 $fordb = new stdClass(); 258 foreach (get_object_vars($this) as $k => $v) { 259 $fordb->{$k} = $v; 260 } 261 // TODO: We need to making it more simple. 262 // Since the variables are not exist in the badge table, 263 // unsetting them is a must to avoid errors. 264 unset($fordb->criteria); 265 unset($fordb->awards); 266 unset($fordb->statstring); 267 unset($fordb->dateissued); 268 unset($fordb->uniquehash); 269 unset($fordb->messageformat); 270 unset($fordb->message_editor); 271 272 $fordb->timemodified = time(); 273 if ($DB->update_record_raw('badge', $fordb)) { 274 // Trigger event, badge updated. 275 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context()); 276 $event = \core\event\badge_updated::create($eventparams); 277 $event->trigger(); 278 return true; 279 } else { 280 throw new moodle_exception('error:save', 'badges'); 281 return false; 282 } 283 } 284 285 /** 286 * Creates and saves a clone of badge with all its properties. 287 * Clone is not active by default and has 'Copy of' attached to its name. 288 * 289 * @return int ID of new badge. 290 */ 291 public function make_clone() { 292 global $DB, $USER, $PAGE; 293 294 $fordb = new stdClass(); 295 foreach (get_object_vars($this) as $k => $v) { 296 $fordb->{$k} = $v; 297 } 298 299 $fordb->name = get_string('copyof', 'badges', $this->name); 300 $fordb->status = BADGE_STATUS_INACTIVE; 301 $fordb->usercreated = $USER->id; 302 $fordb->usermodified = $USER->id; 303 $fordb->timecreated = time(); 304 $fordb->timemodified = time(); 305 unset($fordb->id); 306 307 if ($fordb->notification > 1) { 308 $fordb->nextcron = badges_calculate_message_schedule($fordb->notification); 309 } 310 311 $criteria = $fordb->criteria; 312 unset($fordb->criteria); 313 314 if ($new = $DB->insert_record('badge', $fordb, true)) { 315 $newbadge = new badge($new); 316 317 // Copy badge image. 318 $fs = get_file_storage(); 319 if ($file = $fs->get_file($this->get_context()->id, 'badges', 'badgeimage', $this->id, '/', 'f3.png')) { 320 if ($imagefile = $file->copy_content_to_temp()) { 321 badges_process_badge_image($newbadge, $imagefile); 322 } 323 } 324 325 // Copy badge criteria. 326 foreach ($this->criteria as $crit) { 327 $crit->make_clone($new); 328 } 329 330 // Trigger event, badge duplicated. 331 $eventparams = array('objectid' => $new, 'context' => $PAGE->context); 332 $event = \core\event\badge_duplicated::create($eventparams); 333 $event->trigger(); 334 335 return $new; 336 } else { 337 throw new moodle_exception('error:clone', 'badges'); 338 return false; 339 } 340 } 341 342 /** 343 * Checks if badges is active. 344 * Used in badge award. 345 * 346 * @return boolean A status indicating badge is active 347 */ 348 public function is_active() { 349 if (($this->status == BADGE_STATUS_ACTIVE) || 350 ($this->status == BADGE_STATUS_ACTIVE_LOCKED)) { 351 return true; 352 } 353 return false; 354 } 355 356 /** 357 * Use to get the name of badge status. 358 * 359 * @return string 360 */ 361 public function get_status_name() { 362 return get_string('badgestatus_' . $this->status, 'badges'); 363 } 364 365 /** 366 * Use to set badge status. 367 * Only active badges can be earned/awarded/issued. 368 * 369 * @param int $status Status from BADGE_STATUS constants 370 */ 371 public function set_status($status = 0) { 372 $this->status = $status; 373 $this->save(); 374 if ($status == BADGE_STATUS_ACTIVE) { 375 // Trigger event, badge enabled. 376 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context()); 377 $event = \core\event\badge_enabled::create($eventparams); 378 $event->trigger(); 379 } else if ($status == BADGE_STATUS_INACTIVE) { 380 // Trigger event, badge disabled. 381 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context()); 382 $event = \core\event\badge_disabled::create($eventparams); 383 $event->trigger(); 384 } 385 } 386 387 /** 388 * Checks if badges is locked. 389 * Used in badge award and editing. 390 * 391 * @return boolean A status indicating badge is locked 392 */ 393 public function is_locked() { 394 if (($this->status == BADGE_STATUS_ACTIVE_LOCKED) || 395 ($this->status == BADGE_STATUS_INACTIVE_LOCKED)) { 396 return true; 397 } 398 return false; 399 } 400 401 /** 402 * Checks if badge has been awarded to users. 403 * Used in badge editing. 404 * 405 * @return boolean A status indicating badge has been awarded at least once 406 */ 407 public function has_awards() { 408 global $DB; 409 $awarded = $DB->record_exists_sql('SELECT b.uniquehash 410 FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id 411 WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id)); 412 413 return $awarded; 414 } 415 416 /** 417 * Gets list of users who have earned an instance of this badge. 418 * 419 * @return array An array of objects with information about badge awards. 420 */ 421 public function get_awards() { 422 global $DB; 423 424 $awards = $DB->get_records_sql( 425 'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname 426 FROM {badge_issued} b INNER JOIN {user} u 427 ON b.userid = u.id 428 WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id)); 429 430 return $awards; 431 } 432 433 /** 434 * Indicates whether badge has already been issued to a user. 435 * 436 * @param int $userid User to check 437 * @return boolean 438 */ 439 public function is_issued($userid) { 440 global $DB; 441 return $DB->record_exists('badge_issued', array('badgeid' => $this->id, 'userid' => $userid)); 442 } 443 444 /** 445 * Issue a badge to user. 446 * 447 * @param int $userid User who earned the badge 448 * @param boolean $nobake Not baking actual badges (for testing purposes) 449 */ 450 public function issue($userid, $nobake = false) { 451 global $DB, $CFG; 452 453 $now = time(); 454 $issued = new stdClass(); 455 $issued->badgeid = $this->id; 456 $issued->userid = $userid; 457 $issued->uniquehash = sha1(rand() . $userid . $this->id . $now); 458 $issued->dateissued = $now; 459 460 if ($this->can_expire()) { 461 $issued->dateexpire = $this->calculate_expiry($now); 462 } else { 463 $issued->dateexpire = null; 464 } 465 466 // Take into account user badges privacy settings. 467 // If none set, badges default visibility is set to public. 468 $issued->visible = get_user_preferences('badgeprivacysetting', 1, $userid); 469 470 $result = $DB->insert_record('badge_issued', $issued, true); 471 472 if ($result) { 473 // Trigger badge awarded event. 474 $eventdata = array ( 475 'context' => $this->get_context(), 476 'objectid' => $this->id, 477 'relateduserid' => $userid, 478 'other' => array('dateexpire' => $issued->dateexpire, 'badgeissuedid' => $result) 479 ); 480 \core\event\badge_awarded::create($eventdata)->trigger(); 481 482 // Lock the badge, so that its criteria could not be changed any more. 483 if ($this->status == BADGE_STATUS_ACTIVE) { 484 $this->set_status(BADGE_STATUS_ACTIVE_LOCKED); 485 } 486 487 // Update details in criteria_met table. 488 $compl = $this->get_criteria_completions($userid); 489 foreach ($compl as $c) { 490 $obj = new stdClass(); 491 $obj->id = $c->id; 492 $obj->issuedid = $result; 493 $DB->update_record('badge_criteria_met', $obj, true); 494 } 495 496 if (!$nobake) { 497 // Bake a badge image. 498 $pathhash = badges_bake($issued->uniquehash, $this->id, $userid, true); 499 500 // Notify recipients and badge creators. 501 badges_notify_badge_award($this, $userid, $issued->uniquehash, $pathhash); 502 } 503 } 504 } 505 506 /** 507 * Reviews all badge criteria and checks if badge can be instantly awarded. 508 * 509 * @return int Number of awards 510 */ 511 public function review_all_criteria() { 512 global $DB, $CFG; 513 $awards = 0; 514 515 // Raise timelimit as this could take a while for big web sites. 516 core_php_time_limit::raise(); 517 raise_memory_limit(MEMORY_HUGE); 518 519 foreach ($this->criteria as $crit) { 520 // Overall criterion is decided when other criteria are reviewed. 521 if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) { 522 continue; 523 } 524 525 list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql(); 526 // For site level badges, get all active site users who can earn this badge and haven't got it yet. 527 if ($this->type == BADGE_TYPE_SITE) { 528 $sql = "SELECT DISTINCT u.id, bi.badgeid 529 FROM {user} u 530 {$extrajoin} 531 LEFT JOIN {badge_issued} bi 532 ON u.id = bi.userid AND bi.badgeid = :badgeid 533 WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0 " . $extrawhere; 534 $params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams); 535 $toearn = $DB->get_fieldset_sql($sql, $params); 536 } else { 537 // For course level badges, get all users who already earned the badge in this course. 538 // Then find the ones who are enrolled in the course and don't have a badge yet. 539 $earned = $DB->get_fieldset_select( 540 'badge_issued', 541 'userid AS id', 542 'badgeid = :badgeid', 543 array('badgeid' => $this->id) 544 ); 545 546 $wheresql = ''; 547 $earnedparams = array(); 548 if (!empty($earned)) { 549 list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false); 550 $wheresql = ' WHERE u.id ' . $earnedsql; 551 } 552 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true); 553 $sql = "SELECT DISTINCT u.id 554 FROM {user} u 555 {$extrajoin} 556 JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere; 557 $params = array_merge($enrolledparams, $earnedparams, $extraparams); 558 $toearn = $DB->get_fieldset_sql($sql, $params); 559 } 560 561 foreach ($toearn as $uid) { 562 $reviewoverall = false; 563 if ($crit->review($uid, true)) { 564 $crit->mark_complete($uid); 565 if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) { 566 $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid); 567 $this->issue($uid); 568 $awards++; 569 } else { 570 $reviewoverall = true; 571 } 572 } else { 573 // Will be reviewed some other time. 574 $reviewoverall = false; 575 } 576 // Review overall if it is required. 577 if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) { 578 $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid); 579 $this->issue($uid); 580 $awards++; 581 } 582 } 583 } 584 585 return $awards; 586 } 587 588 /** 589 * Gets an array of completed criteria from 'badge_criteria_met' table. 590 * 591 * @param int $userid Completions for a user 592 * @return array Records of criteria completions 593 */ 594 public function get_criteria_completions($userid) { 595 global $DB; 596 $completions = array(); 597 $sql = "SELECT bcm.id, bcm.critid 598 FROM {badge_criteria_met} bcm 599 INNER JOIN {badge_criteria} bc ON bcm.critid = bc.id 600 WHERE bc.badgeid = :badgeid AND bcm.userid = :userid "; 601 $completions = $DB->get_records_sql($sql, array('badgeid' => $this->id, 'userid' => $userid)); 602 603 return $completions; 604 } 605 606 /** 607 * Checks if badges has award criteria set up. 608 * 609 * @return boolean A status indicating badge has at least one criterion 610 */ 611 public function has_criteria() { 612 if (count($this->criteria) > 0) { 613 return true; 614 } 615 return false; 616 } 617 618 /** 619 * Returns badge award criteria 620 * 621 * @return array An array of badge criteria 622 */ 623 public function get_criteria() { 624 global $DB; 625 $criteria = array(); 626 627 if ($records = (array)$DB->get_records('badge_criteria', array('badgeid' => $this->id))) { 628 foreach ($records as $record) { 629 $criteria[$record->criteriatype] = award_criteria::build((array)$record); 630 } 631 } 632 633 return $criteria; 634 } 635 636 /** 637 * Get aggregation method for badge criteria 638 * 639 * @param int $criteriatype If none supplied, get overall aggregation method (optional) 640 * @return int One of BADGE_CRITERIA_AGGREGATION_ALL or BADGE_CRITERIA_AGGREGATION_ANY 641 */ 642 public function get_aggregation_method($criteriatype = 0) { 643 global $DB; 644 $params = array('badgeid' => $this->id, 'criteriatype' => $criteriatype); 645 $aggregation = $DB->get_field('badge_criteria', 'method', $params, IGNORE_MULTIPLE); 646 647 if (!$aggregation) { 648 return BADGE_CRITERIA_AGGREGATION_ALL; 649 } 650 651 return $aggregation; 652 } 653 654 /** 655 * Checks if badge has expiry period or date set up. 656 * 657 * @return boolean A status indicating badge can expire 658 */ 659 public function can_expire() { 660 if ($this->expireperiod || $this->expiredate) { 661 return true; 662 } 663 return false; 664 } 665 666 /** 667 * Calculates badge expiry date based on either expirydate or expiryperiod. 668 * 669 * @param int $timestamp Time of badge issue 670 * @return int A timestamp 671 */ 672 public function calculate_expiry($timestamp) { 673 $expiry = null; 674 675 if (isset($this->expiredate)) { 676 $expiry = $this->expiredate; 677 } else if (isset($this->expireperiod)) { 678 $expiry = $timestamp + $this->expireperiod; 679 } 680 681 return $expiry; 682 } 683 684 /** 685 * Checks if badge has manual award criteria set. 686 * 687 * @return boolean A status indicating badge can be awarded manually 688 */ 689 public function has_manual_award_criteria() { 690 foreach ($this->criteria as $criterion) { 691 if ($criterion->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) { 692 return true; 693 } 694 } 695 return false; 696 } 697 698 /** 699 * Fully deletes the badge or marks it as archived. 700 * 701 * @param boolean $archive Achive a badge without actual deleting of any data. 702 */ 703 public function delete($archive = true) { 704 global $DB; 705 706 if ($archive) { 707 $this->status = BADGE_STATUS_ARCHIVED; 708 $this->save(); 709 710 // Trigger event, badge archived. 711 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context()); 712 $event = \core\event\badge_archived::create($eventparams); 713 $event->trigger(); 714 return; 715 } 716 717 $fs = get_file_storage(); 718 719 // Remove all issued badge image files and badge awards. 720 // Cannot bulk remove area files here because they are issued in user context. 721 $awards = $this->get_awards(); 722 foreach ($awards as $award) { 723 $usercontext = context_user::instance($award->userid); 724 $fs->delete_area_files($usercontext->id, 'badges', 'userbadge', $this->id); 725 } 726 $DB->delete_records('badge_issued', array('badgeid' => $this->id)); 727 728 // Remove all badge criteria. 729 $criteria = $this->get_criteria(); 730 foreach ($criteria as $criterion) { 731 $criterion->delete(); 732 } 733 734 // Delete badge images. 735 $badgecontext = $this->get_context(); 736 $fs->delete_area_files($badgecontext->id, 'badges', 'badgeimage', $this->id); 737 738 // Delete endorsements, competencies and related badges. 739 $DB->delete_records('badge_endorsement', array('badgeid' => $this->id)); 740 $relatedsql = 'badgeid = :badgeid OR relatedbadgeid = :relatedbadgeid'; 741 $relatedparams = array( 742 'badgeid' => $this->id, 743 'relatedbadgeid' => $this->id 744 ); 745 $DB->delete_records_select('badge_related', $relatedsql, $relatedparams); 746 $DB->delete_records('badge_alignment', array('badgeid' => $this->id)); 747 748 // Finally, remove badge itself. 749 $DB->delete_records('badge', array('id' => $this->id)); 750 751 // Trigger event, badge deleted. 752 $eventparams = array('objectid' => $this->id, 753 'context' => $this->get_context(), 754 'other' => array('badgetype' => $this->type, 'courseid' => $this->courseid) 755 ); 756 $event = \core\event\badge_deleted::create($eventparams); 757 $event->trigger(); 758 } 759 760 /** 761 * Add multiple related badges. 762 * 763 * @param array $relatedids Id of badges. 764 */ 765 public function add_related_badges($relatedids) { 766 global $DB; 767 $relatedbadges = array(); 768 foreach ($relatedids as $relatedid) { 769 $relatedbadge = new stdClass(); 770 $relatedbadge->badgeid = $this->id; 771 $relatedbadge->relatedbadgeid = $relatedid; 772 $relatedbadges[] = $relatedbadge; 773 } 774 $DB->insert_records('badge_related', $relatedbadges); 775 } 776 777 /** 778 * Delete an related badge. 779 * 780 * @param int $relatedid Id related badge. 781 * @return boolean A status for delete an related badge. 782 */ 783 public function delete_related_badge($relatedid) { 784 global $DB; 785 $sql = "(badgeid = :badgeid AND relatedbadgeid = :relatedid) OR " . 786 "(badgeid = :relatedid2 AND relatedbadgeid = :badgeid2)"; 787 $params = ['badgeid' => $this->id, 'badgeid2' => $this->id, 'relatedid' => $relatedid, 'relatedid2' => $relatedid]; 788 return $DB->delete_records_select('badge_related', $sql, $params); 789 } 790 791 /** 792 * Checks if badge has related badges. 793 * 794 * @return boolean A status related badge. 795 */ 796 public function has_related() { 797 global $DB; 798 $sql = "SELECT DISTINCT b.id 799 FROM {badge_related} br 800 JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id) 801 WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3"; 802 return $DB->record_exists_sql($sql, ['badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id]); 803 } 804 805 /** 806 * Get related badges of badge. 807 * 808 * @param boolean $activeonly Do not get the inactive badges when is true. 809 * @return array Related badges information. 810 */ 811 public function get_related_badges($activeonly = false) { 812 global $DB; 813 814 $params = array('badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id); 815 $query = "SELECT DISTINCT b.id, b.name, b.version, b.language, b.type 816 FROM {badge_related} br 817 JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id) 818 WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3"; 819 if ($activeonly) { 820 $query .= " AND b.status <> :status"; 821 $params['status'] = BADGE_STATUS_INACTIVE; 822 } 823 $relatedbadges = $DB->get_records_sql($query, $params); 824 return $relatedbadges; 825 } 826 827 /** 828 * Insert/update alignment information of badge. 829 * 830 * @param stdClass $alignment Data of a alignment. 831 * @param int $alignmentid ID alignment. 832 * @return bool|int A status/ID when insert or update data. 833 */ 834 public function save_alignment($alignment, $alignmentid = 0) { 835 global $DB; 836 837 $record = $DB->record_exists('badge_alignment', array('id' => $alignmentid)); 838 if ($record) { 839 $alignment->id = $alignmentid; 840 return $DB->update_record('badge_alignment', $alignment); 841 } else { 842 return $DB->insert_record('badge_alignment', $alignment, true); 843 } 844 } 845 846 /** 847 * Delete a alignment of badge. 848 * 849 * @param int $alignmentid ID alignment. 850 * @return boolean A status for delete a alignment. 851 */ 852 public function delete_alignment($alignmentid) { 853 global $DB; 854 return $DB->delete_records('badge_alignment', array('badgeid' => $this->id, 'id' => $alignmentid)); 855 } 856 857 /** 858 * Get alignments of badge. 859 * 860 * @return array List content alignments. 861 */ 862 public function get_alignments() { 863 global $DB; 864 return $DB->get_records('badge_alignment', array('badgeid' => $this->id)); 865 } 866 867 /** 868 * Insert/update Endorsement information of badge. 869 * 870 * @param stdClass $endorsement Data of an endorsement. 871 * @return bool|int A status/ID when insert or update data. 872 */ 873 public function save_endorsement($endorsement) { 874 global $DB; 875 $record = $DB->get_record('badge_endorsement', array('badgeid' => $this->id)); 876 if ($record) { 877 $endorsement->id = $record->id; 878 return $DB->update_record('badge_endorsement', $endorsement); 879 } else { 880 return $DB->insert_record('badge_endorsement', $endorsement, true); 881 } 882 } 883 884 /** 885 * Get endorsement of badge. 886 * 887 * @return array|stdClass Endorsement information. 888 */ 889 public function get_endorsement() { 890 global $DB; 891 return $DB->get_record('badge_endorsement', array('badgeid' => $this->id)); 892 } 893 894 /** 895 * Markdown language support for criteria. 896 * 897 * @return string $output Markdown content to output. 898 */ 899 public function markdown_badge_criteria() { 900 $agg = $this->get_aggregation_methods(); 901 if (empty($this->criteria)) { 902 return get_string('nocriteria', 'badges'); 903 } 904 $overalldescr = ''; 905 $overall = $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]; 906 if (!empty($overall->description)) { 907 $overalldescr = format_text($overall->description, $overall->descriptionformat, 908 array('context' => $this->get_context())) . '\n'; 909 } 910 // Get the condition string. 911 if (count($this->criteria) == 2) { 912 $condition = get_string('criteria_descr', 'badges'); 913 } else { 914 $condition = get_string('criteria_descr_' . BADGE_CRITERIA_TYPE_OVERALL, 'badges', 915 core_text::strtoupper($agg[$this->get_aggregation_method()])); 916 } 917 unset($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]); 918 $items = array(); 919 // If only one criterion left, make sure its description goe to the top. 920 if (count($this->criteria) == 1) { 921 $c = reset($this->criteria); 922 if (!empty($c->description)) { 923 $overalldescr = $c->description . '\n'; 924 } 925 if (count($c->params) == 1) { 926 $items[] = ' * ' . get_string('criteria_descr_single_' . $c->criteriatype, 'badges') . 927 $c->get_details(); 928 } else { 929 $items[] = '* ' . get_string('criteria_descr_' . $c->criteriatype, 'badges', 930 core_text::strtoupper($agg[$this->get_aggregation_method($c->criteriatype)])) . 931 $c->get_details(); 932 } 933 } else { 934 foreach ($this->criteria as $type => $c) { 935 $criteriadescr = ''; 936 if (!empty($c->description)) { 937 $criteriadescr = $c->description; 938 } 939 if (count($c->params) == 1) { 940 $items[] = ' * ' . get_string('criteria_descr_single_' . $type, 'badges') . 941 $c->get_details() . $criteriadescr; 942 } else { 943 $items[] = '* ' . get_string('criteria_descr_' . $type, 'badges', 944 core_text::strtoupper($agg[$this->get_aggregation_method($type)])) . 945 $c->get_details() . $criteriadescr; 946 } 947 } 948 } 949 return strip_tags($overalldescr . $condition . html_writer::alist($items, array(), 'ul')); 950 } 951 952 /** 953 * Define issuer information by format Open Badges specification version 2. 954 * 955 * @param int $obversion OB version to use. 956 * @return array Issuer informations of the badge. 957 */ 958 public function get_badge_issuer(?int $obversion = null) { 959 global $DB; 960 961 $issuer = []; 962 if ($obversion == OPEN_BADGES_V1) { 963 $data = $DB->get_record('badge', ['id' => $this->id]); 964 $issuer['name'] = $data->issuername; 965 $issuer['url'] = $data->issuerurl; 966 $issuer['email'] = $data->issuercontact; 967 } else { 968 $issuer['name'] = $this->issuername; 969 $issuer['url'] = $this->issuerurl; 970 $issuer['email'] = $this->issuercontact; 971 $issuer['@context'] = OPEN_BADGES_V2_CONTEXT; 972 $issueridurl = new moodle_url('/badges/issuer_json.php', array('id' => $this->id)); 973 $issuer['id'] = $issueridurl->out(false); 974 $issuer['type'] = OPEN_BADGES_V2_TYPE_ISSUER; 975 } 976 977 return $issuer; 978 } 979 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body