Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [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 $tags = $this->get_badge_tags(); 306 unset($fordb->id); 307 308 if ($fordb->notification > 1) { 309 $fordb->nextcron = badges_calculate_message_schedule($fordb->notification); 310 } 311 312 $criteria = $fordb->criteria; 313 unset($fordb->criteria); 314 315 if ($new = $DB->insert_record('badge', $fordb, true)) { 316 $newbadge = new badge($new); 317 // Copy badge tags. 318 \core_tag_tag::set_item_tags('core_badges', 'badge', $newbadge->id, $this->get_context(), $tags); 319 320 // Copy badge image. 321 $fs = get_file_storage(); 322 if ($file = $fs->get_file($this->get_context()->id, 'badges', 'badgeimage', $this->id, '/', 'f3.png')) { 323 if ($imagefile = $file->copy_content_to_temp()) { 324 badges_process_badge_image($newbadge, $imagefile); 325 } 326 } 327 328 // Copy badge criteria. 329 foreach ($this->criteria as $crit) { 330 $crit->make_clone($new); 331 } 332 333 // Trigger event, badge duplicated. 334 $eventparams = array('objectid' => $new, 'context' => $PAGE->context); 335 $event = \core\event\badge_duplicated::create($eventparams); 336 $event->trigger(); 337 338 return $new; 339 } else { 340 throw new moodle_exception('error:clone', 'badges'); 341 return false; 342 } 343 } 344 345 /** 346 * Checks if badges is active. 347 * Used in badge award. 348 * 349 * @return boolean A status indicating badge is active 350 */ 351 public function is_active() { 352 if (($this->status == BADGE_STATUS_ACTIVE) || 353 ($this->status == BADGE_STATUS_ACTIVE_LOCKED)) { 354 return true; 355 } 356 return false; 357 } 358 359 /** 360 * Use to get the name of badge status. 361 * 362 * @return string 363 */ 364 public function get_status_name() { 365 return get_string('badgestatus_' . $this->status, 'badges'); 366 } 367 368 /** 369 * Use to set badge status. 370 * Only active badges can be earned/awarded/issued. 371 * 372 * @param int $status Status from BADGE_STATUS constants 373 */ 374 public function set_status($status = 0) { 375 $this->status = $status; 376 $this->save(); 377 if ($status == BADGE_STATUS_ACTIVE) { 378 // Trigger event, badge enabled. 379 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context()); 380 $event = \core\event\badge_enabled::create($eventparams); 381 $event->trigger(); 382 } else if ($status == BADGE_STATUS_INACTIVE) { 383 // Trigger event, badge disabled. 384 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context()); 385 $event = \core\event\badge_disabled::create($eventparams); 386 $event->trigger(); 387 } 388 } 389 390 /** 391 * Checks if badges is locked. 392 * Used in badge award and editing. 393 * 394 * @return boolean A status indicating badge is locked 395 */ 396 public function is_locked() { 397 if (($this->status == BADGE_STATUS_ACTIVE_LOCKED) || 398 ($this->status == BADGE_STATUS_INACTIVE_LOCKED)) { 399 return true; 400 } 401 return false; 402 } 403 404 /** 405 * Checks if badge has been awarded to users. 406 * Used in badge editing. 407 * 408 * @return boolean A status indicating badge has been awarded at least once 409 */ 410 public function has_awards() { 411 global $DB; 412 $awarded = $DB->record_exists_sql('SELECT b.uniquehash 413 FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id 414 WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id)); 415 416 return $awarded; 417 } 418 419 /** 420 * Gets list of users who have earned an instance of this badge. 421 * 422 * @return array An array of objects with information about badge awards. 423 */ 424 public function get_awards() { 425 global $DB; 426 427 $awards = $DB->get_records_sql( 428 'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname 429 FROM {badge_issued} b INNER JOIN {user} u 430 ON b.userid = u.id 431 WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id)); 432 433 return $awards; 434 } 435 436 /** 437 * Indicates whether badge has already been issued to a user. 438 * 439 * @param int $userid User to check 440 * @return boolean 441 */ 442 public function is_issued($userid) { 443 global $DB; 444 return $DB->record_exists('badge_issued', array('badgeid' => $this->id, 'userid' => $userid)); 445 } 446 447 /** 448 * Issue a badge to user. 449 * 450 * @param int $userid User who earned the badge 451 * @param boolean $nobake Not baking actual badges (for testing purposes) 452 */ 453 public function issue($userid, $nobake = false) { 454 global $DB, $CFG; 455 456 $now = time(); 457 $issued = new stdClass(); 458 $issued->badgeid = $this->id; 459 $issued->userid = $userid; 460 $issued->uniquehash = sha1(rand() . $userid . $this->id . $now); 461 $issued->dateissued = $now; 462 463 if ($this->can_expire()) { 464 $issued->dateexpire = $this->calculate_expiry($now); 465 } else { 466 $issued->dateexpire = null; 467 } 468 469 // Take into account user badges privacy settings. 470 // If none set, badges default visibility is set to public. 471 $issued->visible = get_user_preferences('badgeprivacysetting', 1, $userid); 472 473 $result = $DB->insert_record('badge_issued', $issued, true); 474 475 if ($result) { 476 // Trigger badge awarded event. 477 $eventdata = array ( 478 'context' => $this->get_context(), 479 'objectid' => $this->id, 480 'relateduserid' => $userid, 481 'other' => array('dateexpire' => $issued->dateexpire, 'badgeissuedid' => $result) 482 ); 483 \core\event\badge_awarded::create($eventdata)->trigger(); 484 485 // Lock the badge, so that its criteria could not be changed any more. 486 if ($this->status == BADGE_STATUS_ACTIVE) { 487 $this->set_status(BADGE_STATUS_ACTIVE_LOCKED); 488 } 489 490 // Update details in criteria_met table. 491 $compl = $this->get_criteria_completions($userid); 492 foreach ($compl as $c) { 493 $obj = new stdClass(); 494 $obj->id = $c->id; 495 $obj->issuedid = $result; 496 $DB->update_record('badge_criteria_met', $obj, true); 497 } 498 499 if (!$nobake) { 500 // Bake a badge image. 501 $pathhash = badges_bake($issued->uniquehash, $this->id, $userid, true); 502 503 // Notify recipients and badge creators. 504 badges_notify_badge_award($this, $userid, $issued->uniquehash, $pathhash); 505 } 506 } 507 } 508 509 /** 510 * Reviews all badge criteria and checks if badge can be instantly awarded. 511 * 512 * @return int Number of awards 513 */ 514 public function review_all_criteria() { 515 global $DB, $CFG; 516 $awards = 0; 517 518 // Raise timelimit as this could take a while for big web sites. 519 core_php_time_limit::raise(); 520 raise_memory_limit(MEMORY_HUGE); 521 522 foreach ($this->criteria as $crit) { 523 // Overall criterion is decided when other criteria are reviewed. 524 if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) { 525 continue; 526 } 527 528 list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql(); 529 // For site level badges, get all active site users who can earn this badge and haven't got it yet. 530 if ($this->type == BADGE_TYPE_SITE) { 531 $sql = "SELECT DISTINCT u.id, bi.badgeid 532 FROM {user} u 533 {$extrajoin} 534 LEFT JOIN {badge_issued} bi 535 ON u.id = bi.userid AND bi.badgeid = :badgeid 536 WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0 " . $extrawhere; 537 $params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams); 538 $toearn = $DB->get_fieldset_sql($sql, $params); 539 } else { 540 // For course level badges, get all users who already earned the badge in this course. 541 // Then find the ones who are enrolled in the course and don't have a badge yet. 542 $earned = $DB->get_fieldset_select( 543 'badge_issued', 544 'userid AS id', 545 'badgeid = :badgeid', 546 array('badgeid' => $this->id) 547 ); 548 549 $wheresql = ''; 550 $earnedparams = array(); 551 if (!empty($earned)) { 552 list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false); 553 $wheresql = ' WHERE u.id ' . $earnedsql; 554 } 555 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true); 556 $sql = "SELECT DISTINCT u.id 557 FROM {user} u 558 {$extrajoin} 559 JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere; 560 $params = array_merge($enrolledparams, $earnedparams, $extraparams); 561 $toearn = $DB->get_fieldset_sql($sql, $params); 562 } 563 564 foreach ($toearn as $uid) { 565 $reviewoverall = false; 566 if ($crit->review($uid, true)) { 567 $crit->mark_complete($uid); 568 if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) { 569 $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid); 570 $this->issue($uid); 571 $awards++; 572 } else { 573 $reviewoverall = true; 574 } 575 } else { 576 // Will be reviewed some other time. 577 $reviewoverall = false; 578 } 579 // Review overall if it is required. 580 if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) { 581 $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid); 582 $this->issue($uid); 583 $awards++; 584 } 585 } 586 } 587 588 return $awards; 589 } 590 591 /** 592 * Gets an array of completed criteria from 'badge_criteria_met' table. 593 * 594 * @param int $userid Completions for a user 595 * @return array Records of criteria completions 596 */ 597 public function get_criteria_completions($userid) { 598 global $DB; 599 $completions = array(); 600 $sql = "SELECT bcm.id, bcm.critid 601 FROM {badge_criteria_met} bcm 602 INNER JOIN {badge_criteria} bc ON bcm.critid = bc.id 603 WHERE bc.badgeid = :badgeid AND bcm.userid = :userid "; 604 $completions = $DB->get_records_sql($sql, array('badgeid' => $this->id, 'userid' => $userid)); 605 606 return $completions; 607 } 608 609 /** 610 * Checks if badges has award criteria set up. 611 * 612 * @return boolean A status indicating badge has at least one criterion 613 */ 614 public function has_criteria() { 615 if (count($this->criteria) > 0) { 616 return true; 617 } 618 return false; 619 } 620 621 /** 622 * Returns badge award criteria 623 * 624 * @return array An array of badge criteria 625 */ 626 public function get_criteria() { 627 global $DB; 628 $criteria = array(); 629 630 if ($records = (array)$DB->get_records('badge_criteria', array('badgeid' => $this->id))) { 631 foreach ($records as $record) { 632 $criteria[$record->criteriatype] = award_criteria::build((array)$record); 633 } 634 } 635 636 return $criteria; 637 } 638 639 /** 640 * Get aggregation method for badge criteria 641 * 642 * @param int $criteriatype If none supplied, get overall aggregation method (optional) 643 * @return int One of BADGE_CRITERIA_AGGREGATION_ALL or BADGE_CRITERIA_AGGREGATION_ANY 644 */ 645 public function get_aggregation_method($criteriatype = 0) { 646 global $DB; 647 $params = array('badgeid' => $this->id, 'criteriatype' => $criteriatype); 648 $aggregation = $DB->get_field('badge_criteria', 'method', $params, IGNORE_MULTIPLE); 649 650 if (!$aggregation) { 651 return BADGE_CRITERIA_AGGREGATION_ALL; 652 } 653 654 return $aggregation; 655 } 656 657 /** 658 * Checks if badge has expiry period or date set up. 659 * 660 * @return boolean A status indicating badge can expire 661 */ 662 public function can_expire() { 663 if ($this->expireperiod || $this->expiredate) { 664 return true; 665 } 666 return false; 667 } 668 669 /** 670 * Calculates badge expiry date based on either expirydate or expiryperiod. 671 * 672 * @param int $timestamp Time of badge issue 673 * @return int A timestamp 674 */ 675 public function calculate_expiry($timestamp) { 676 $expiry = null; 677 678 if (isset($this->expiredate)) { 679 $expiry = $this->expiredate; 680 } else if (isset($this->expireperiod)) { 681 $expiry = $timestamp + $this->expireperiod; 682 } 683 684 return $expiry; 685 } 686 687 /** 688 * Checks if badge has manual award criteria set. 689 * 690 * @return boolean A status indicating badge can be awarded manually 691 */ 692 public function has_manual_award_criteria() { 693 foreach ($this->criteria as $criterion) { 694 if ($criterion->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) { 695 return true; 696 } 697 } 698 return false; 699 } 700 701 /** 702 * Fully deletes the badge or marks it as archived. 703 * 704 * @param boolean $archive Achive a badge without actual deleting of any data. 705 */ 706 public function delete($archive = true) { 707 global $DB; 708 709 if ($archive) { 710 $this->status = BADGE_STATUS_ARCHIVED; 711 $this->save(); 712 713 // Trigger event, badge archived. 714 $eventparams = array('objectid' => $this->id, 'context' => $this->get_context()); 715 $event = \core\event\badge_archived::create($eventparams); 716 $event->trigger(); 717 return; 718 } 719 720 $fs = get_file_storage(); 721 722 // Remove all issued badge image files and badge awards. 723 // Cannot bulk remove area files here because they are issued in user context. 724 $awards = $this->get_awards(); 725 foreach ($awards as $award) { 726 $usercontext = context_user::instance($award->userid); 727 $fs->delete_area_files($usercontext->id, 'badges', 'userbadge', $this->id); 728 } 729 $DB->delete_records('badge_issued', array('badgeid' => $this->id)); 730 731 // Remove all badge criteria. 732 $criteria = $this->get_criteria(); 733 foreach ($criteria as $criterion) { 734 $criterion->delete(); 735 } 736 737 // Delete badge images. 738 $badgecontext = $this->get_context(); 739 $fs->delete_area_files($badgecontext->id, 'badges', 'badgeimage', $this->id); 740 741 // Delete endorsements, competencies and related badges. 742 $DB->delete_records('badge_endorsement', array('badgeid' => $this->id)); 743 $relatedsql = 'badgeid = :badgeid OR relatedbadgeid = :relatedbadgeid'; 744 $relatedparams = array( 745 'badgeid' => $this->id, 746 'relatedbadgeid' => $this->id 747 ); 748 $DB->delete_records_select('badge_related', $relatedsql, $relatedparams); 749 $DB->delete_records('badge_alignment', array('badgeid' => $this->id)); 750 751 // Delete all tags. 752 \core_tag_tag::remove_all_item_tags('core_badges', 'badge', $this->id); 753 754 // Finally, remove badge itself. 755 $DB->delete_records('badge', array('id' => $this->id)); 756 757 // Trigger event, badge deleted. 758 $eventparams = array('objectid' => $this->id, 759 'context' => $this->get_context(), 760 'other' => array('badgetype' => $this->type, 'courseid' => $this->courseid) 761 ); 762 $event = \core\event\badge_deleted::create($eventparams); 763 $event->trigger(); 764 } 765 766 /** 767 * Add multiple related badges. 768 * 769 * @param array $relatedids Id of badges. 770 */ 771 public function add_related_badges($relatedids) { 772 global $DB; 773 $relatedbadges = array(); 774 foreach ($relatedids as $relatedid) { 775 $relatedbadge = new stdClass(); 776 $relatedbadge->badgeid = $this->id; 777 $relatedbadge->relatedbadgeid = $relatedid; 778 $relatedbadges[] = $relatedbadge; 779 } 780 $DB->insert_records('badge_related', $relatedbadges); 781 } 782 783 /** 784 * Delete an related badge. 785 * 786 * @param int $relatedid Id related badge. 787 * @return boolean A status for delete an related badge. 788 */ 789 public function delete_related_badge($relatedid) { 790 global $DB; 791 $sql = "(badgeid = :badgeid AND relatedbadgeid = :relatedid) OR " . 792 "(badgeid = :relatedid2 AND relatedbadgeid = :badgeid2)"; 793 $params = ['badgeid' => $this->id, 'badgeid2' => $this->id, 'relatedid' => $relatedid, 'relatedid2' => $relatedid]; 794 return $DB->delete_records_select('badge_related', $sql, $params); 795 } 796 797 /** 798 * Checks if badge has related badges. 799 * 800 * @return boolean A status related badge. 801 */ 802 public function has_related() { 803 global $DB; 804 $sql = "SELECT DISTINCT b.id 805 FROM {badge_related} br 806 JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id) 807 WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3"; 808 return $DB->record_exists_sql($sql, ['badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id]); 809 } 810 811 /** 812 * Get related badges of badge. 813 * 814 * @param boolean $activeonly Do not get the inactive badges when is true. 815 * @return array Related badges information. 816 */ 817 public function get_related_badges($activeonly = false) { 818 global $DB; 819 820 $params = array('badgeid' => $this->id, 'badgeid2' => $this->id, 'badgeid3' => $this->id); 821 $query = "SELECT DISTINCT b.id, b.name, b.version, b.language, b.type 822 FROM {badge_related} br 823 JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id) 824 WHERE (br.badgeid = :badgeid OR br.relatedbadgeid = :badgeid2) AND b.id != :badgeid3"; 825 if ($activeonly) { 826 $query .= " AND b.status <> :status"; 827 $params['status'] = BADGE_STATUS_INACTIVE; 828 } 829 $relatedbadges = $DB->get_records_sql($query, $params); 830 return $relatedbadges; 831 } 832 833 /** 834 * Insert/update alignment information of badge. 835 * 836 * @param stdClass $alignment Data of a alignment. 837 * @param int $alignmentid ID alignment. 838 * @return bool|int A status/ID when insert or update data. 839 */ 840 public function save_alignment($alignment, $alignmentid = 0) { 841 global $DB; 842 843 $record = $DB->record_exists('badge_alignment', array('id' => $alignmentid)); 844 if ($record) { 845 $alignment->id = $alignmentid; 846 return $DB->update_record('badge_alignment', $alignment); 847 } else { 848 return $DB->insert_record('badge_alignment', $alignment, true); 849 } 850 } 851 852 /** 853 * Delete a alignment of badge. 854 * 855 * @param int $alignmentid ID alignment. 856 * @return boolean A status for delete a alignment. 857 */ 858 public function delete_alignment($alignmentid) { 859 global $DB; 860 return $DB->delete_records('badge_alignment', array('badgeid' => $this->id, 'id' => $alignmentid)); 861 } 862 863 /** 864 * Get alignments of badge. 865 * 866 * @return array List content alignments. 867 */ 868 public function get_alignments() { 869 global $DB; 870 return $DB->get_records('badge_alignment', array('badgeid' => $this->id)); 871 } 872 873 /** 874 * Insert/update Endorsement information of badge. 875 * 876 * @param stdClass $endorsement Data of an endorsement. 877 * @return bool|int A status/ID when insert or update data. 878 */ 879 public function save_endorsement($endorsement) { 880 global $DB; 881 $record = $DB->get_record('badge_endorsement', array('badgeid' => $this->id)); 882 if ($record) { 883 $endorsement->id = $record->id; 884 return $DB->update_record('badge_endorsement', $endorsement); 885 } else { 886 return $DB->insert_record('badge_endorsement', $endorsement, true); 887 } 888 } 889 890 /** 891 * Get endorsement of badge. 892 * 893 * @return array|stdClass Endorsement information. 894 */ 895 public function get_endorsement() { 896 global $DB; 897 return $DB->get_record('badge_endorsement', array('badgeid' => $this->id)); 898 } 899 900 /** 901 * Markdown language support for criteria. 902 * 903 * @return string $output Markdown content to output. 904 */ 905 public function markdown_badge_criteria() { 906 $agg = $this->get_aggregation_methods(); 907 if (empty($this->criteria)) { 908 return get_string('nocriteria', 'badges'); 909 } 910 $overalldescr = ''; 911 $overall = $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]; 912 if (!empty($overall->description)) { 913 $overalldescr = format_text($overall->description, $overall->descriptionformat, 914 array('context' => $this->get_context())) . '\n'; 915 } 916 // Get the condition string. 917 if (count($this->criteria) == 2) { 918 $condition = get_string('criteria_descr', 'badges'); 919 } else { 920 $condition = get_string('criteria_descr_' . BADGE_CRITERIA_TYPE_OVERALL, 'badges', 921 core_text::strtoupper($agg[$this->get_aggregation_method()])); 922 } 923 unset($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]); 924 $items = array(); 925 // If only one criterion left, make sure its description goe to the top. 926 if (count($this->criteria) == 1) { 927 $c = reset($this->criteria); 928 if (!empty($c->description)) { 929 $overalldescr = $c->description . '\n'; 930 } 931 if (count($c->params) == 1) { 932 $items[] = ' * ' . get_string('criteria_descr_single_' . $c->criteriatype, 'badges') . 933 $c->get_details(); 934 } else { 935 $items[] = '* ' . get_string('criteria_descr_' . $c->criteriatype, 'badges', 936 core_text::strtoupper($agg[$this->get_aggregation_method($c->criteriatype)])) . 937 $c->get_details(); 938 } 939 } else { 940 foreach ($this->criteria as $type => $c) { 941 $criteriadescr = ''; 942 if (!empty($c->description)) { 943 $criteriadescr = $c->description; 944 } 945 if (count($c->params) == 1) { 946 $items[] = ' * ' . get_string('criteria_descr_single_' . $type, 'badges') . 947 $c->get_details() . $criteriadescr; 948 } else { 949 $items[] = '* ' . get_string('criteria_descr_' . $type, 'badges', 950 core_text::strtoupper($agg[$this->get_aggregation_method($type)])) . 951 $c->get_details() . $criteriadescr; 952 } 953 } 954 } 955 return strip_tags($overalldescr . $condition . html_writer::alist($items, array(), 'ul')); 956 } 957 958 /** 959 * Define issuer information by format Open Badges specification version 2. 960 * 961 * @param int $obversion OB version to use. 962 * @return array Issuer informations of the badge. 963 */ 964 public function get_badge_issuer(?int $obversion = null) { 965 global $DB; 966 967 $issuer = []; 968 if ($obversion == OPEN_BADGES_V1) { 969 $data = $DB->get_record('badge', ['id' => $this->id]); 970 $issuer['name'] = $data->issuername; 971 $issuer['url'] = $data->issuerurl; 972 $issuer['email'] = $data->issuercontact; 973 } else { 974 $issuer['name'] = $this->issuername; 975 $issuer['url'] = $this->issuerurl; 976 $issuer['email'] = $this->issuercontact; 977 $issuer['@context'] = OPEN_BADGES_V2_CONTEXT; 978 $issueridurl = new moodle_url('/badges/issuer_json.php', array('id' => $this->id)); 979 $issuer['id'] = $issueridurl->out(false); 980 $issuer['type'] = OPEN_BADGES_V2_TYPE_ISSUER; 981 } 982 983 return $issuer; 984 } 985 986 /** 987 * Get tags of badge. 988 * 989 * @return array Badge tags. 990 */ 991 public function get_badge_tags(): array { 992 return array_values(\core_tag_tag::get_item_tags_array('core_badges', 'badge', $this->id)); 993 } 994 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body