[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/ -> badgeslib.php (source)

   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  
  96  /*
  97   * URL of backpack. Currently only the Open Badges backpack is supported.
  98   */
  99  define('BADGE_BACKPACKURL', 'https://backpack.openbadges.org');
 100  
 101  /**
 102   * Class that represents badge.
 103   *
 104   */
 105  class badge {
 106      /** @var int Badge id */
 107      public $id;
 108  
 109      /** Values from the table 'badge' */
 110      public $name;
 111      public $description;
 112      public $timecreated;
 113      public $timemodified;
 114      public $usercreated;
 115      public $usermodified;
 116      public $issuername;
 117      public $issuerurl;
 118      public $issuercontact;
 119      public $expiredate;
 120      public $expireperiod;
 121      public $type;
 122      public $courseid;
 123      public $message;
 124      public $messagesubject;
 125      public $attachment;
 126      public $notification;
 127      public $status = 0;
 128      public $nextcron;
 129  
 130      /** @var array Badge criteria */
 131      public $criteria = array();
 132  
 133      /**
 134       * Constructs with badge details.
 135       *
 136       * @param int $badgeid badge ID.
 137       */
 138      public function __construct($badgeid) {
 139          global $DB;
 140          $this->id = $badgeid;
 141  
 142          $data = $DB->get_record('badge', array('id' => $badgeid));
 143  
 144          if (empty($data)) {
 145              print_error('error:nosuchbadge', 'badges', $badgeid);
 146          }
 147  
 148          foreach ((array)$data as $field => $value) {
 149              if (property_exists($this, $field)) {
 150                  $this->{$field} = $value;
 151              }
 152          }
 153  
 154          $this->criteria = self::get_criteria();
 155      }
 156  
 157      /**
 158       * Use to get context instance of a badge.
 159       * @return context instance.
 160       */
 161      public function get_context() {
 162          if ($this->type == BADGE_TYPE_SITE) {
 163              return context_system::instance();
 164          } else if ($this->type == BADGE_TYPE_COURSE) {
 165              return context_course::instance($this->courseid);
 166          } else {
 167              debugging('Something is wrong...');
 168          }
 169      }
 170  
 171      /**
 172       * Return array of aggregation methods
 173       * @return array
 174       */
 175      public static function get_aggregation_methods() {
 176          return array(
 177                  BADGE_CRITERIA_AGGREGATION_ALL => get_string('all', 'badges'),
 178                  BADGE_CRITERIA_AGGREGATION_ANY => get_string('any', 'badges'),
 179          );
 180      }
 181  
 182      /**
 183       * Return array of accepted criteria types for this badge
 184       * @return array
 185       */
 186      public function get_accepted_criteria() {
 187          $criteriatypes = array();
 188  
 189          if ($this->type == BADGE_TYPE_COURSE) {
 190              $criteriatypes = array(
 191                      BADGE_CRITERIA_TYPE_OVERALL,
 192                      BADGE_CRITERIA_TYPE_MANUAL,
 193                      BADGE_CRITERIA_TYPE_COURSE,
 194                      BADGE_CRITERIA_TYPE_ACTIVITY
 195              );
 196          } else if ($this->type == BADGE_TYPE_SITE) {
 197              $criteriatypes = array(
 198                      BADGE_CRITERIA_TYPE_OVERALL,
 199                      BADGE_CRITERIA_TYPE_MANUAL,
 200                      BADGE_CRITERIA_TYPE_COURSESET,
 201                      BADGE_CRITERIA_TYPE_PROFILE,
 202              );
 203          }
 204  
 205          return $criteriatypes;
 206      }
 207  
 208      /**
 209       * Save/update badge information in 'badge' table only.
 210       * Cannot be used for updating awards and criteria settings.
 211       *
 212       * @return bool Returns true on success.
 213       */
 214      public function save() {
 215          global $DB;
 216  
 217          $fordb = new stdClass();
 218          foreach (get_object_vars($this) as $k => $v) {
 219              $fordb->{$k} = $v;
 220          }
 221          unset($fordb->criteria);
 222  
 223          $fordb->timemodified = time();
 224          if ($DB->update_record_raw('badge', $fordb)) {
 225              // Trigger event, badge updated.
 226              $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
 227              $event = \core\event\badge_updated::create($eventparams);
 228              $event->trigger();
 229              return true;
 230          } else {
 231              throw new moodle_exception('error:save', 'badges');
 232              return false;
 233          }
 234      }
 235  
 236      /**
 237       * Creates and saves a clone of badge with all its properties.
 238       * Clone is not active by default and has 'Copy of' attached to its name.
 239       *
 240       * @return int ID of new badge.
 241       */
 242      public function make_clone() {
 243          global $DB, $USER, $PAGE;
 244  
 245          $fordb = new stdClass();
 246          foreach (get_object_vars($this) as $k => $v) {
 247              $fordb->{$k} = $v;
 248          }
 249  
 250          $fordb->name = get_string('copyof', 'badges', $this->name);
 251          $fordb->status = BADGE_STATUS_INACTIVE;
 252          $fordb->usercreated = $USER->id;
 253          $fordb->usermodified = $USER->id;
 254          $fordb->timecreated = time();
 255          $fordb->timemodified = time();
 256          unset($fordb->id);
 257  
 258          if ($fordb->notification > 1) {
 259              $fordb->nextcron = badges_calculate_message_schedule($fordb->notification);
 260          }
 261  
 262          $criteria = $fordb->criteria;
 263          unset($fordb->criteria);
 264  
 265          if ($new = $DB->insert_record('badge', $fordb, true)) {
 266              $newbadge = new badge($new);
 267  
 268              // Copy badge image.
 269              $fs = get_file_storage();
 270              if ($file = $fs->get_file($this->get_context()->id, 'badges', 'badgeimage', $this->id, '/', 'f1.png')) {
 271                  if ($imagefile = $file->copy_content_to_temp()) {
 272                      badges_process_badge_image($newbadge, $imagefile);
 273                  }
 274              }
 275  
 276              // Copy badge criteria.
 277              foreach ($this->criteria as $crit) {
 278                  $crit->make_clone($new);
 279              }
 280  
 281              // Trigger event, badge duplicated.
 282              $eventparams = array('objectid' => $new, 'context' => $PAGE->context);
 283              $event = \core\event\badge_duplicated::create($eventparams);
 284              $event->trigger();
 285  
 286              return $new;
 287          } else {
 288              throw new moodle_exception('error:clone', 'badges');
 289              return false;
 290          }
 291      }
 292  
 293      /**
 294       * Checks if badges is active.
 295       * Used in badge award.
 296       *
 297       * @return bool A status indicating badge is active
 298       */
 299      public function is_active() {
 300          if (($this->status == BADGE_STATUS_ACTIVE) ||
 301              ($this->status == BADGE_STATUS_ACTIVE_LOCKED)) {
 302              return true;
 303          }
 304          return false;
 305      }
 306  
 307      /**
 308       * Use to get the name of badge status.
 309       *
 310       */
 311      public function get_status_name() {
 312          return get_string('badgestatus_' . $this->status, 'badges');
 313      }
 314  
 315      /**
 316       * Use to set badge status.
 317       * Only active badges can be earned/awarded/issued.
 318       *
 319       * @param int $status Status from BADGE_STATUS constants
 320       */
 321      public function set_status($status = 0) {
 322          $this->status = $status;
 323          $this->save();
 324          if ($status == BADGE_STATUS_ACTIVE) {
 325              // Trigger event, badge enabled.
 326              $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
 327              $event = \core\event\badge_enabled::create($eventparams);
 328              $event->trigger();
 329          } else if ($status == BADGE_STATUS_INACTIVE) {
 330              // Trigger event, badge disabled.
 331              $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
 332              $event = \core\event\badge_disabled::create($eventparams);
 333              $event->trigger();
 334          }
 335      }
 336  
 337      /**
 338       * Checks if badges is locked.
 339       * Used in badge award and editing.
 340       *
 341       * @return bool A status indicating badge is locked
 342       */
 343      public function is_locked() {
 344          if (($this->status == BADGE_STATUS_ACTIVE_LOCKED) ||
 345                  ($this->status == BADGE_STATUS_INACTIVE_LOCKED)) {
 346              return true;
 347          }
 348          return false;
 349      }
 350  
 351      /**
 352       * Checks if badge has been awarded to users.
 353       * Used in badge editing.
 354       *
 355       * @return bool A status indicating badge has been awarded at least once
 356       */
 357      public function has_awards() {
 358          global $DB;
 359          $awarded = $DB->record_exists_sql('SELECT b.uniquehash
 360                      FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
 361                      WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
 362  
 363          return $awarded;
 364      }
 365  
 366      /**
 367       * Gets list of users who have earned an instance of this badge.
 368       *
 369       * @return array An array of objects with information about badge awards.
 370       */
 371      public function get_awards() {
 372          global $DB;
 373  
 374          $awards = $DB->get_records_sql(
 375                  'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname
 376                      FROM {badge_issued} b INNER JOIN {user} u
 377                          ON b.userid = u.id
 378                      WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
 379  
 380          return $awards;
 381      }
 382  
 383      /**
 384       * Indicates whether badge has already been issued to a user.
 385       *
 386       */
 387      public function is_issued($userid) {
 388          global $DB;
 389          return $DB->record_exists('badge_issued', array('badgeid' => $this->id, 'userid' => $userid));
 390      }
 391  
 392      /**
 393       * Issue a badge to user.
 394       *
 395       * @param int $userid User who earned the badge
 396       * @param bool $nobake Not baking actual badges (for testing purposes)
 397       */
 398      public function issue($userid, $nobake = false) {
 399          global $DB, $CFG;
 400  
 401          $now = time();
 402          $issued = new stdClass();
 403          $issued->badgeid = $this->id;
 404          $issued->userid = $userid;
 405          $issued->uniquehash = sha1(rand() . $userid . $this->id . $now);
 406          $issued->dateissued = $now;
 407  
 408          if ($this->can_expire()) {
 409              $issued->dateexpire = $this->calculate_expiry($now);
 410          } else {
 411              $issued->dateexpire = null;
 412          }
 413  
 414          // Take into account user badges privacy settings.
 415          // If none set, badges default visibility is set to public.
 416          $issued->visible = get_user_preferences('badgeprivacysetting', 1, $userid);
 417  
 418          $result = $DB->insert_record('badge_issued', $issued, true);
 419  
 420          if ($result) {
 421              // Trigger badge awarded event.
 422              $eventdata = array (
 423                  'context' => $this->get_context(),
 424                  'objectid' => $this->id,
 425                  'relateduserid' => $userid,
 426                  'other' => array('dateexpire' => $issued->dateexpire, 'badgeissuedid' => $result)
 427              );
 428              \core\event\badge_awarded::create($eventdata)->trigger();
 429  
 430              // Lock the badge, so that its criteria could not be changed any more.
 431              if ($this->status == BADGE_STATUS_ACTIVE) {
 432                  $this->set_status(BADGE_STATUS_ACTIVE_LOCKED);
 433              }
 434  
 435              // Update details in criteria_met table.
 436              $compl = $this->get_criteria_completions($userid);
 437              foreach ($compl as $c) {
 438                  $obj = new stdClass();
 439                  $obj->id = $c->id;
 440                  $obj->issuedid = $result;
 441                  $DB->update_record('badge_criteria_met', $obj, true);
 442              }
 443  
 444              if (!$nobake) {
 445                  // Bake a badge image.
 446                  $pathhash = badges_bake($issued->uniquehash, $this->id, $userid, true);
 447  
 448                  // Notify recipients and badge creators.
 449                  badges_notify_badge_award($this, $userid, $issued->uniquehash, $pathhash);
 450              }
 451          }
 452      }
 453  
 454      /**
 455       * Reviews all badge criteria and checks if badge can be instantly awarded.
 456       *
 457       * @return int Number of awards
 458       */
 459      public function review_all_criteria() {
 460          global $DB, $CFG;
 461          $awards = 0;
 462  
 463          // Raise timelimit as this could take a while for big web sites.
 464          core_php_time_limit::raise();
 465          raise_memory_limit(MEMORY_HUGE);
 466  
 467          foreach ($this->criteria as $crit) {
 468              // Overall criterion is decided when other criteria are reviewed.
 469              if ($crit->criteriatype == BADGE_CRITERIA_TYPE_OVERALL) {
 470                  continue;
 471              }
 472  
 473              list($extrajoin, $extrawhere, $extraparams) = $crit->get_completed_criteria_sql();
 474              // For site level badges, get all active site users who can earn this badge and haven't got it yet.
 475              if ($this->type == BADGE_TYPE_SITE) {
 476                  $sql = "SELECT DISTINCT u.id, bi.badgeid
 477                          FROM {user} u
 478                          {$extrajoin}
 479                          LEFT JOIN {badge_issued} bi
 480                              ON u.id = bi.userid AND bi.badgeid = :badgeid
 481                          WHERE bi.badgeid IS NULL AND u.id != :guestid AND u.deleted = 0 " . $extrawhere;
 482                  $params = array_merge(array('badgeid' => $this->id, 'guestid' => $CFG->siteguest), $extraparams);
 483                  $toearn = $DB->get_fieldset_sql($sql, $params);
 484              } else {
 485                  // For course level badges, get all users who already earned the badge in this course.
 486                  // Then find the ones who are enrolled in the course and don't have a badge yet.
 487                  $earned = $DB->get_fieldset_select('badge_issued', 'userid AS id', 'badgeid = :badgeid', array('badgeid' => $this->id));
 488                  $wheresql = '';
 489                  $earnedparams = array();
 490                  if (!empty($earned)) {
 491                      list($earnedsql, $earnedparams) = $DB->get_in_or_equal($earned, SQL_PARAMS_NAMED, 'u', false);
 492                      $wheresql = ' WHERE u.id ' . $earnedsql;
 493                  }
 494                  list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true);
 495                  $sql = "SELECT DISTINCT u.id
 496                          FROM {user} u
 497                          {$extrajoin}
 498                          JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere;
 499                  $params = array_merge($enrolledparams, $earnedparams, $extraparams);
 500                  $toearn = $DB->get_fieldset_sql($sql, $params);
 501              }
 502  
 503              foreach ($toearn as $uid) {
 504                  $reviewoverall = false;
 505                  if ($crit->review($uid, true)) {
 506                      $crit->mark_complete($uid);
 507                      if ($this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->method == BADGE_CRITERIA_AGGREGATION_ANY) {
 508                          $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
 509                          $this->issue($uid);
 510                          $awards++;
 511                      } else {
 512                          $reviewoverall = true;
 513                      }
 514                  } else {
 515                      // Will be reviewed some other time.
 516                      $reviewoverall = false;
 517                  }
 518                  // Review overall if it is required.
 519                  if ($reviewoverall && $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($uid)) {
 520                      $this->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($uid);
 521                      $this->issue($uid);
 522                      $awards++;
 523                  }
 524              }
 525          }
 526  
 527          return $awards;
 528      }
 529  
 530      /**
 531       * Gets an array of completed criteria from 'badge_criteria_met' table.
 532       *
 533       * @param int $userid Completions for a user
 534       * @return array Records of criteria completions
 535       */
 536      public function get_criteria_completions($userid) {
 537          global $DB;
 538          $completions = array();
 539          $sql = "SELECT bcm.id, bcm.critid
 540                  FROM {badge_criteria_met} bcm
 541                      INNER JOIN {badge_criteria} bc ON bcm.critid = bc.id
 542                  WHERE bc.badgeid = :badgeid AND bcm.userid = :userid ";
 543          $completions = $DB->get_records_sql($sql, array('badgeid' => $this->id, 'userid' => $userid));
 544  
 545          return $completions;
 546      }
 547  
 548      /**
 549       * Checks if badges has award criteria set up.
 550       *
 551       * @return bool A status indicating badge has at least one criterion
 552       */
 553      public function has_criteria() {
 554          if (count($this->criteria) > 0) {
 555              return true;
 556          }
 557          return false;
 558      }
 559  
 560      /**
 561       * Returns badge award criteria
 562       *
 563       * @return array An array of badge criteria
 564       */
 565      public function get_criteria() {
 566          global $DB;
 567          $criteria = array();
 568  
 569          if ($records = (array)$DB->get_records('badge_criteria', array('badgeid' => $this->id))) {
 570              foreach ($records as $record) {
 571                  $criteria[$record->criteriatype] = award_criteria::build((array)$record);
 572              }
 573          }
 574  
 575          return $criteria;
 576      }
 577  
 578      /**
 579       * Get aggregation method for badge criteria
 580       *
 581       * @param int $criteriatype If none supplied, get overall aggregation method (optional)
 582       * @return int One of BADGE_CRITERIA_AGGREGATION_ALL or BADGE_CRITERIA_AGGREGATION_ANY
 583       */
 584      public function get_aggregation_method($criteriatype = 0) {
 585          global $DB;
 586          $params = array('badgeid' => $this->id, 'criteriatype' => $criteriatype);
 587          $aggregation = $DB->get_field('badge_criteria', 'method', $params, IGNORE_MULTIPLE);
 588  
 589          if (!$aggregation) {
 590              return BADGE_CRITERIA_AGGREGATION_ALL;
 591          }
 592  
 593          return $aggregation;
 594      }
 595  
 596      /**
 597       * Checks if badge has expiry period or date set up.
 598       *
 599       * @return bool A status indicating badge can expire
 600       */
 601      public function can_expire() {
 602          if ($this->expireperiod || $this->expiredate) {
 603              return true;
 604          }
 605          return false;
 606      }
 607  
 608      /**
 609       * Calculates badge expiry date based on either expirydate or expiryperiod.
 610       *
 611       * @param int $timestamp Time of badge issue
 612       * @return int A timestamp
 613       */
 614      public function calculate_expiry($timestamp) {
 615          $expiry = null;
 616  
 617          if (isset($this->expiredate)) {
 618              $expiry = $this->expiredate;
 619          } else if (isset($this->expireperiod)) {
 620              $expiry = $timestamp + $this->expireperiod;
 621          }
 622  
 623          return $expiry;
 624      }
 625  
 626      /**
 627       * Checks if badge has manual award criteria set.
 628       *
 629       * @return bool A status indicating badge can be awarded manually
 630       */
 631      public function has_manual_award_criteria() {
 632          foreach ($this->criteria as $criterion) {
 633              if ($criterion->criteriatype == BADGE_CRITERIA_TYPE_MANUAL) {
 634                  return true;
 635              }
 636          }
 637          return false;
 638      }
 639  
 640      /**
 641       * Fully deletes the badge or marks it as archived.
 642       *
 643       * @param $archive bool Achive a badge without actual deleting of any data.
 644       */
 645      public function delete($archive = true) {
 646          global $DB;
 647  
 648          if ($archive) {
 649              $this->status = BADGE_STATUS_ARCHIVED;
 650              $this->save();
 651  
 652              // Trigger event, badge archived.
 653              $eventparams = array('objectid' => $this->id, 'context' => $this->get_context());
 654              $event = \core\event\badge_archived::create($eventparams);
 655              $event->trigger();
 656              return;
 657          }
 658  
 659          $fs = get_file_storage();
 660  
 661          // Remove all issued badge image files and badge awards.
 662          // Cannot bulk remove area files here because they are issued in user context.
 663          $awards = $this->get_awards();
 664          foreach ($awards as $award) {
 665              $usercontext = context_user::instance($award->userid);
 666              $fs->delete_area_files($usercontext->id, 'badges', 'userbadge', $this->id);
 667          }
 668          $DB->delete_records('badge_issued', array('badgeid' => $this->id));
 669  
 670          // Remove all badge criteria.
 671          $criteria = $this->get_criteria();
 672          foreach ($criteria as $criterion) {
 673              $criterion->delete();
 674          }
 675  
 676          // Delete badge images.
 677          $badgecontext = $this->get_context();
 678          $fs->delete_area_files($badgecontext->id, 'badges', 'badgeimage', $this->id);
 679  
 680          // Finally, remove badge itself.
 681          $DB->delete_records('badge', array('id' => $this->id));
 682  
 683          // Trigger event, badge deleted.
 684          $eventparams = array('objectid' => $this->id,
 685              'context' => $this->get_context(),
 686              'other' => array('badgetype' => $this->type, 'courseid' => $this->courseid)
 687              );
 688          $event = \core\event\badge_deleted::create($eventparams);
 689          $event->trigger();
 690      }
 691  }
 692  
 693  /**
 694   * Sends notifications to users about awarded badges.
 695   *
 696   * @param badge $badge Badge that was issued
 697   * @param int $userid Recipient ID
 698   * @param string $issued Unique hash of an issued badge
 699   * @param string $filepathhash File path hash of an issued badge for attachments
 700   */
 701  function badges_notify_badge_award(badge $badge, $userid, $issued, $filepathhash) {
 702      global $CFG, $DB;
 703  
 704      $admin = get_admin();
 705      $userfrom = new stdClass();
 706      $userfrom->id = $admin->id;
 707      $userfrom->email = !empty($CFG->badges_defaultissuercontact) ? $CFG->badges_defaultissuercontact : $admin->email;
 708      foreach (get_all_user_name_fields() as $addname) {
 709          $userfrom->$addname = !empty($CFG->badges_defaultissuername) ? '' : $admin->$addname;
 710      }
 711      $userfrom->firstname = !empty($CFG->badges_defaultissuername) ? $CFG->badges_defaultissuername : $admin->firstname;
 712      $userfrom->maildisplay = true;
 713  
 714      $issuedlink = html_writer::link(new moodle_url('/badges/badge.php', array('hash' => $issued)), $badge->name);
 715      $userto = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
 716  
 717      $params = new stdClass();
 718      $params->badgename = $badge->name;
 719      $params->username = fullname($userto);
 720      $params->badgelink = $issuedlink;
 721      $message = badge_message_from_template($badge->message, $params);
 722      $plaintext = html_to_text($message);
 723  
 724      // Notify recipient.
 725      $eventdata = new stdClass();
 726      $eventdata->component         = 'moodle';
 727      $eventdata->name              = 'badgerecipientnotice';
 728      $eventdata->userfrom          = $userfrom;
 729      $eventdata->userto            = $userto;
 730      $eventdata->notification      = 1;
 731      $eventdata->subject           = $badge->messagesubject;
 732      $eventdata->fullmessage       = $plaintext;
 733      $eventdata->fullmessageformat = FORMAT_HTML;
 734      $eventdata->fullmessagehtml   = $message;
 735      $eventdata->smallmessage      = '';
 736  
 737      // Attach badge image if possible.
 738      if (!empty($CFG->allowattachments) && $badge->attachment && is_string($filepathhash)) {
 739          $fs = get_file_storage();
 740          $file = $fs->get_file_by_hash($filepathhash);
 741          $eventdata->attachment = $file;
 742          $eventdata->attachname = str_replace(' ', '_', $badge->name) . ".png";
 743  
 744          message_send($eventdata);
 745      } else {
 746          message_send($eventdata);
 747      }
 748  
 749      // Notify badge creator about the award if they receive notifications every time.
 750      if ($badge->notification == 1) {
 751          $userfrom = core_user::get_noreply_user();
 752          $userfrom->maildisplay = true;
 753  
 754          $creator = $DB->get_record('user', array('id' => $badge->usercreated), '*', MUST_EXIST);
 755          $a = new stdClass();
 756          $a->user = fullname($userto);
 757          $a->link = $issuedlink;
 758          $creatormessage = get_string('creatorbody', 'badges', $a);
 759          $creatorsubject = get_string('creatorsubject', 'badges', $badge->name);
 760  
 761          $eventdata = new stdClass();
 762          $eventdata->component         = 'moodle';
 763          $eventdata->name              = 'badgecreatornotice';
 764          $eventdata->userfrom          = $userfrom;
 765          $eventdata->userto            = $creator;
 766          $eventdata->notification      = 1;
 767          $eventdata->subject           = $creatorsubject;
 768          $eventdata->fullmessage       = html_to_text($creatormessage);
 769          $eventdata->fullmessageformat = FORMAT_HTML;
 770          $eventdata->fullmessagehtml   = $creatormessage;
 771          $eventdata->smallmessage      = '';
 772  
 773          message_send($eventdata);
 774          $DB->set_field('badge_issued', 'issuernotified', time(), array('badgeid' => $badge->id, 'userid' => $userid));
 775      }
 776  }
 777  
 778  /**
 779   * Caclulates date for the next message digest to badge creators.
 780   *
 781   * @param in $schedule Type of message schedule BADGE_MESSAGE_DAILY|BADGE_MESSAGE_WEEKLY|BADGE_MESSAGE_MONTHLY.
 782   * @return int Timestamp for next cron
 783   */
 784  function badges_calculate_message_schedule($schedule) {
 785      $nextcron = 0;
 786  
 787      switch ($schedule) {
 788          case BADGE_MESSAGE_DAILY:
 789              $nextcron = time() + 60 * 60 * 24;
 790              break;
 791          case BADGE_MESSAGE_WEEKLY:
 792              $nextcron = time() + 60 * 60 * 24 * 7;
 793              break;
 794          case BADGE_MESSAGE_MONTHLY:
 795              $nextcron = time() + 60 * 60 * 24 * 7 * 30;
 796              break;
 797      }
 798  
 799      return $nextcron;
 800  }
 801  
 802  /**
 803   * Replaces variables in a message template and returns text ready to be emailed to a user.
 804   *
 805   * @param string $message Message body.
 806   * @return string Message with replaced values
 807   */
 808  function badge_message_from_template($message, $params) {
 809      $msg = $message;
 810      foreach ($params as $key => $value) {
 811          $msg = str_replace("%$key%", $value, $msg);
 812      }
 813  
 814      return $msg;
 815  }
 816  
 817  /**
 818   * Get all badges.
 819   *
 820   * @param int Type of badges to return
 821   * @param int Course ID for course badges
 822   * @param string $sort An SQL field to sort by
 823   * @param string $dir The sort direction ASC|DESC
 824   * @param int $page The page or records to return
 825   * @param int $perpage The number of records to return per page
 826   * @param int $user User specific search
 827   * @return array $badge Array of records matching criteria
 828   */
 829  function badges_get_badges($type, $courseid = 0, $sort = '', $dir = '', $page = 0, $perpage = BADGE_PERPAGE, $user = 0) {
 830      global $DB;
 831      $records = array();
 832      $params = array();
 833      $where = "b.status != :deleted AND b.type = :type ";
 834      $params['deleted'] = BADGE_STATUS_ARCHIVED;
 835  
 836      $userfields = array('b.id, b.name, b.status');
 837      $usersql = "";
 838      if ($user != 0) {
 839          $userfields[] = 'bi.dateissued';
 840          $userfields[] = 'bi.uniquehash';
 841          $usersql = " LEFT JOIN {badge_issued} bi ON b.id = bi.badgeid AND bi.userid = :userid ";
 842          $params['userid'] = $user;
 843          $where .= " AND (b.status = 1 OR b.status = 3) ";
 844      }
 845      $fields = implode(', ', $userfields);
 846  
 847      if ($courseid != 0 ) {
 848          $where .= "AND b.courseid = :courseid ";
 849          $params['courseid'] = $courseid;
 850      }
 851  
 852      $sorting = (($sort != '' && $dir != '') ? 'ORDER BY ' . $sort . ' ' . $dir : '');
 853      $params['type'] = $type;
 854  
 855      $sql = "SELECT $fields FROM {badge} b $usersql WHERE $where $sorting";
 856      $records = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
 857  
 858      $badges = array();
 859      foreach ($records as $r) {
 860          $badge = new badge($r->id);
 861          $badges[$r->id] = $badge;
 862          if ($user != 0) {
 863              $badges[$r->id]->dateissued = $r->dateissued;
 864              $badges[$r->id]->uniquehash = $r->uniquehash;
 865          } else {
 866              $badges[$r->id]->awards = $DB->count_records_sql('SELECT COUNT(b.userid)
 867                                          FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
 868                                          WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $badge->id));
 869              $badges[$r->id]->statstring = $badge->get_status_name();
 870          }
 871      }
 872      return $badges;
 873  }
 874  
 875  /**
 876   * Get badges for a specific user.
 877   *
 878   * @param int $userid User ID
 879   * @param int $courseid Badges earned by a user in a specific course
 880   * @param int $page The page or records to return
 881   * @param int $perpage The number of records to return per page
 882   * @param string $search A simple string to search for
 883   * @param bool $onlypublic Return only public badges
 884   * @return array of badges ordered by decreasing date of issue
 885   */
 886  function badges_get_user_badges($userid, $courseid = 0, $page = 0, $perpage = 0, $search = '', $onlypublic = false) {
 887      global $DB;
 888  
 889      $params = array(
 890          'userid' => $userid
 891      );
 892      $sql = 'SELECT
 893                  bi.uniquehash,
 894                  bi.dateissued,
 895                  bi.dateexpire,
 896                  bi.id as issuedid,
 897                  bi.visible,
 898                  u.email,
 899                  b.*
 900              FROM
 901                  {badge} b,
 902                  {badge_issued} bi,
 903                  {user} u
 904              WHERE b.id = bi.badgeid
 905                  AND u.id = bi.userid
 906                  AND bi.userid = :userid';
 907  
 908      if (!empty($search)) {
 909          $sql .= ' AND (' . $DB->sql_like('b.name', ':search', false) . ') ';
 910          $params['search'] = '%'.$DB->sql_like_escape($search).'%';
 911      }
 912      if ($onlypublic) {
 913          $sql .= ' AND (bi.visible = 1) ';
 914      }
 915  
 916      if ($courseid != 0) {
 917          $sql .= ' AND (b.courseid = :courseid) ';
 918          $params['courseid'] = $courseid;
 919      }
 920      $sql .= ' ORDER BY bi.dateissued DESC';
 921      $badges = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage);
 922  
 923      return $badges;
 924  }
 925  
 926  /**
 927   * Extends the course administration navigation with the Badges page
 928   *
 929   * @param navigation_node $coursenode
 930   * @param object $course
 931   */
 932  function badges_add_course_navigation(navigation_node $coursenode, stdClass $course) {
 933      global $CFG, $SITE;
 934  
 935      $coursecontext = context_course::instance($course->id);
 936      $isfrontpage = (!$coursecontext || $course->id == $SITE->id);
 937      $canmanage = has_any_capability(array('moodle/badges:viewawarded',
 938                                            'moodle/badges:createbadge',
 939                                            'moodle/badges:awardbadge',
 940                                            'moodle/badges:configurecriteria',
 941                                            'moodle/badges:configuremessages',
 942                                            'moodle/badges:configuredetails',
 943                                            'moodle/badges:deletebadge'), $coursecontext);
 944  
 945      if (!empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) && !$isfrontpage && $canmanage) {
 946          $coursenode->add(get_string('coursebadges', 'badges'), null,
 947                  navigation_node::TYPE_CONTAINER, null, 'coursebadges',
 948                  new pix_icon('i/badge', get_string('coursebadges', 'badges')));
 949  
 950          $url = new moodle_url('/badges/index.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
 951  
 952          $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
 953              navigation_node::TYPE_SETTING, null, 'coursebadges');
 954  
 955          if (has_capability('moodle/badges:createbadge', $coursecontext)) {
 956              $url = new moodle_url('/badges/newbadge.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
 957  
 958              $coursenode->get('coursebadges')->add(get_string('newbadge', 'badges'), $url,
 959                      navigation_node::TYPE_SETTING, null, 'newbadge');
 960          }
 961      }
 962  }
 963  
 964  /**
 965   * Triggered when badge is manually awarded.
 966   *
 967   * @param   object      $data
 968   * @return  boolean
 969   */
 970  function badges_award_handle_manual_criteria_review(stdClass $data) {
 971      $criteria = $data->crit;
 972      $userid = $data->userid;
 973      $badge = new badge($criteria->badgeid);
 974  
 975      if (!$badge->is_active() || $badge->is_issued($userid)) {
 976          return true;
 977      }
 978  
 979      if ($criteria->review($userid)) {
 980          $criteria->mark_complete($userid);
 981  
 982          if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
 983              $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
 984              $badge->issue($userid);
 985          }
 986      }
 987  
 988      return true;
 989  }
 990  
 991  /**
 992   * Process badge image from form data
 993   *
 994   * @param badge $badge Badge object
 995   * @param string $iconfile Original file
 996   */
 997  function badges_process_badge_image(badge $badge, $iconfile) {
 998      global $CFG, $USER;
 999      require_once($CFG->libdir. '/gdlib.php');
1000  
1001      if (!empty($CFG->gdversion)) {
1002          process_new_icon($badge->get_context(), 'badges', 'badgeimage', $badge->id, $iconfile, true);
1003          @unlink($iconfile);
1004  
1005          // Clean up file draft area after badge image has been saved.
1006          $context = context_user::instance($USER->id, MUST_EXIST);
1007          $fs = get_file_storage();
1008          $fs->delete_area_files($context->id, 'user', 'draft');
1009      }
1010  }
1011  
1012  /**
1013   * Print badge image.
1014   *
1015   * @param badge $badge Badge object
1016   * @param stdClass $context
1017   * @param string $size
1018   */
1019  function print_badge_image(badge $badge, stdClass $context, $size = 'small') {
1020      $fsize = ($size == 'small') ? 'f2' : 'f1';
1021  
1022      $imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', $fsize, false);
1023      // Appending a random parameter to image link to forse browser reload the image.
1024      $imageurl->param('refresh', rand(1, 10000));
1025      $attributes = array('src' => $imageurl, 'alt' => s($badge->name), 'class' => 'activatebadge');
1026  
1027      return html_writer::empty_tag('img', $attributes);
1028  }
1029  
1030  /**
1031   * Bake issued badge.
1032   *
1033   * @param string $hash Unique hash of an issued badge.
1034   * @param int $badgeid ID of the original badge.
1035   * @param int $userid ID of badge recipient (optional).
1036   * @param boolean $pathhash Return file pathhash instead of image url (optional).
1037   * @return string|url Returns either new file path hash or new file URL
1038   */
1039  function badges_bake($hash, $badgeid, $userid = 0, $pathhash = false) {
1040      global $CFG, $USER;
1041      require_once (__DIR__ . '/../badges/lib/bakerlib.php');
1042  
1043      $badge = new badge($badgeid);
1044      $badge_context = $badge->get_context();
1045      $userid = ($userid) ? $userid : $USER->id;
1046      $user_context = context_user::instance($userid);
1047  
1048      $fs = get_file_storage();
1049      if (!$fs->file_exists($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash . '.png')) {
1050          if ($file = $fs->get_file($badge_context->id, 'badges', 'badgeimage', $badge->id, '/', 'f1.png')) {
1051              $contents = $file->get_content();
1052  
1053              $filehandler = new PNG_MetaDataHandler($contents);
1054              $assertion = new moodle_url('/badges/assertion.php', array('b' => $hash));
1055              if ($filehandler->check_chunks("tEXt", "openbadges")) {
1056                  // Add assertion URL tExt chunk.
1057                  $newcontents = $filehandler->add_chunks("tEXt", "openbadges", $assertion->out(false));
1058                  $fileinfo = array(
1059                          'contextid' => $user_context->id,
1060                          'component' => 'badges',
1061                          'filearea' => 'userbadge',
1062                          'itemid' => $badge->id,
1063                          'filepath' => '/',
1064                          'filename' => $hash . '.png',
1065                  );
1066  
1067                  // Create a file with added contents.
1068                  $newfile = $fs->create_file_from_string($fileinfo, $newcontents);
1069                  if ($pathhash) {
1070                      return $newfile->get_pathnamehash();
1071                  }
1072              }
1073          } else {
1074              debugging('Error baking badge image!', DEBUG_DEVELOPER);
1075              return;
1076          }
1077      }
1078  
1079      // If file exists and we just need its path hash, return it.
1080      if ($pathhash) {
1081          $file = $fs->get_file($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash . '.png');
1082          return $file->get_pathnamehash();
1083      }
1084  
1085      $fileurl = moodle_url::make_pluginfile_url($user_context->id, 'badges', 'userbadge', $badge->id, '/', $hash, true);
1086      return $fileurl;
1087  }
1088  
1089  /**
1090   * Returns external backpack settings and badges from this backpack.
1091   *
1092   * This function first checks if badges for the user are cached and
1093   * tries to retrieve them from the cache. Otherwise, badges are obtained
1094   * through curl request to the backpack.
1095   *
1096   * @param int $userid Backpack user ID.
1097   * @param boolean $refresh Refresh badges collection in cache.
1098   * @return null|object Returns null is there is no backpack or object with backpack settings.
1099   */
1100  function get_backpack_settings($userid, $refresh = false) {
1101      global $DB;
1102      require_once (__DIR__ . '/../badges/lib/backpacklib.php');
1103  
1104      // Try to get badges from cache first.
1105      $badgescache = cache::make('core', 'externalbadges');
1106      $out = $badgescache->get($userid);
1107      if ($out !== false && !$refresh) {
1108          return $out;
1109      }
1110      // Get badges through curl request to the backpack.
1111      $record = $DB->get_record('badge_backpack', array('userid' => $userid));
1112      if ($record) {
1113          $backpack = new OpenBadgesBackpackHandler($record);
1114          $out = new stdClass();
1115          $out->backpackurl = $backpack->get_url();
1116  
1117          if ($collections = $DB->get_records('badge_external', array('backpackid' => $record->id))) {
1118              $out->totalcollections = count($collections);
1119              $out->totalbadges = 0;
1120              $out->badges = array();
1121              foreach ($collections as $collection) {
1122                  $badges = $backpack->get_badges($collection->collectionid);
1123                  if (isset($badges->badges)) {
1124                      $out->badges = array_merge($out->badges, $badges->badges);
1125                      $out->totalbadges += count($badges->badges);
1126                  } else {
1127                      $out->badges = array_merge($out->badges, array());
1128                  }
1129              }
1130          } else {
1131              $out->totalbadges = 0;
1132              $out->totalcollections = 0;
1133          }
1134  
1135          $badgescache->set($userid, $out);
1136          return $out;
1137      }
1138  
1139      return null;
1140  }
1141  
1142  /**
1143   * Download all user badges in zip archive.
1144   *
1145   * @param int $userid ID of badge owner.
1146   */
1147  function badges_download($userid) {
1148      global $CFG, $DB;
1149      $context = context_user::instance($userid);
1150      $records = $DB->get_records('badge_issued', array('userid' => $userid));
1151  
1152      // Get list of files to download.
1153      $fs = get_file_storage();
1154      $filelist = array();
1155      foreach ($records as $issued) {
1156          $badge = new badge($issued->badgeid);
1157          // Need to make image name user-readable and unique using filename safe characters.
1158          $name =  $badge->name . ' ' . userdate($issued->dateissued, '%d %b %Y') . ' ' . hash('crc32', $badge->id);
1159          $name = str_replace(' ', '_', $name);
1160          if ($file = $fs->get_file($context->id, 'badges', 'userbadge', $issued->badgeid, '/', $issued->uniquehash . '.png')) {
1161              $filelist[$name . '.png'] = $file;
1162          }
1163      }
1164  
1165      // Zip files and sent them to a user.
1166      $tempzip = tempnam($CFG->tempdir.'/', 'mybadges');
1167      $zipper = new zip_packer();
1168      if ($zipper->archive_to_pathname($filelist, $tempzip)) {
1169          send_temp_file($tempzip, 'badges.zip');
1170      } else {
1171          debugging("Problems with archiving the files.", DEBUG_DEVELOPER);
1172          die;
1173      }
1174  }
1175  
1176  /**
1177   * Checks if badges can be pushed to external backpack.
1178   *
1179   * @return string Code of backpack accessibility status.
1180   */
1181  function badges_check_backpack_accessibility() {
1182      global $CFG;
1183      include_once $CFG->libdir . '/filelib.php';
1184  
1185      // Using fake assertion url to check whether backpack can access the web site.
1186      $fakeassertion = new moodle_url('/badges/assertion.php', array('b' => 'abcd1234567890'));
1187  
1188      // Curl request to backpack baker.
1189      $curl = new curl();
1190      $options = array(
1191          'FRESH_CONNECT' => true,
1192          'RETURNTRANSFER' => true,
1193          'HEADER' => 0,
1194          'CONNECTTIMEOUT' => 2,
1195      );
1196      $location = BADGE_BACKPACKURL . '/baker';
1197      $out = $curl->get($location, array('assertion' => $fakeassertion->out(false)), $options);
1198  
1199      $data = json_decode($out);
1200      if (!empty($curl->error)) {
1201          return 'curl-request-timeout';
1202      } else {
1203          if (isset($data->code) && $data->code == 'http-unreachable') {
1204              return 'http-unreachable';
1205          } else {
1206              return 'available';
1207          }
1208      }
1209  
1210      return false;
1211  }
1212  
1213  /**
1214   * Checks if user has external backpack connected.
1215   *
1216   * @param int $userid ID of a user.
1217   * @return bool True|False whether backpack connection exists.
1218   */
1219  function badges_user_has_backpack($userid) {
1220      global $DB;
1221      return $DB->record_exists('badge_backpack', array('userid' => $userid));
1222  }
1223  
1224  /**
1225   * Handles what happens to the course badges when a course is deleted.
1226   *
1227   * @param int $courseid course ID.
1228   * @return void.
1229   */
1230  function badges_handle_course_deletion($courseid) {
1231      global $CFG, $DB;
1232      include_once $CFG->libdir . '/filelib.php';
1233  
1234      $systemcontext = context_system::instance();
1235      $coursecontext = context_course::instance($courseid);
1236      $fs = get_file_storage();
1237  
1238      // Move badges images to the system context.
1239      $fs->move_area_files_to_new_context($coursecontext->id, $systemcontext->id, 'badges', 'badgeimage');
1240  
1241      // Get all course badges.
1242      $badges = $DB->get_records('badge', array('type' => BADGE_TYPE_COURSE, 'courseid' => $courseid));
1243      foreach ($badges as $badge) {
1244          // Archive badges in this course.
1245          $toupdate = new stdClass();
1246          $toupdate->id = $badge->id;
1247          $toupdate->type = BADGE_TYPE_SITE;
1248          $toupdate->courseid = null;
1249          $toupdate->status = BADGE_STATUS_ARCHIVED;
1250          $DB->update_record('badge', $toupdate);
1251      }
1252  }
1253  
1254  /**
1255   * Loads JS files required for backpack support.
1256   *
1257   * @uses   $CFG, $PAGE
1258   * @return void
1259   */
1260  function badges_setup_backpack_js() {
1261      global $CFG, $PAGE;
1262      if (!empty($CFG->badges_allowexternalbackpack)) {
1263          $PAGE->requires->string_for_js('error:backpackproblem', 'badges');
1264          $PAGE->requires->js(new moodle_url(BADGE_BACKPACKURL . '/issuer.js'), true);
1265          $PAGE->requires->js('/badges/backpack.js', true);
1266      }
1267  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1